diff --git a/README.md b/README.md index e228e7b..536afab 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ Discord: https://discord.gg/5gP22447kc Matrix: https://matrix.to/#/#straywild:matrix.makearmy.io -Repo: https://forge.makearmy.io/woofmeow/straywild +Repo: https://forge.makearmy.io/woofmeow/netfishing -Releases: https://forge.makearmy.io/woofmeow/straywild/releases +Releases: https://forge.makearmy.io/woofmeow/netfishing/releases ## Installing release builds diff --git a/docs/README.md b/docs/README.md index 444ebc7..5327b30 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,10 +17,10 @@ for local README files. ## Related repositories -- [Discovery service API](https://forge.makearmy.io/woofmeow/straywild-discovery-server/src/branch/main/docs/API.md) +- [Discovery service API](https://forge.makearmy.io/woofmeow/netfishing-discovery-server/src/branch/main/docs/API.md) stays with the service implementation and defines its versioned HTTP contract. -- [Dedicated-server packaging](https://forge.makearmy.io/woofmeow/straywild-dedicated-server/src/branch/main/README.md) +- [Dedicated-server packaging](https://forge.makearmy.io/woofmeow/netfishing-dedicated-server/src/branch/main/README.md) stays with its installer and update scripts. It consumes this repository's attribution and legal notices from the exact pinned game commit instead of maintaining copies. diff --git a/fishing/fishing_spot.gd b/fishing/fishing_spot.gd index 8e3e169..2559af5 100644 --- a/fishing/fishing_spot.gd +++ b/fishing/fishing_spot.gd @@ -150,7 +150,6 @@ var _network_session: NetworkSessionType var _network_fishing: NetworkFishingServiceType var _world_time: WorldTimeServiceType var _world_weather: WorldWeatherServiceType -var _fishable_water_regions_resolver := Callable() var _active_player: PlayerType var _state_time_remaining: float = 0.0 var _cast_charge: float = 0.0 @@ -223,7 +222,6 @@ func setup( network_fishing: NetworkFishingServiceType = null, world_time: WorldTimeServiceType = null, world_weather: WorldWeatherServiceType = null, - fishable_water_regions_resolver: Callable = Callable(), ) -> void: _local_player = local_player _local_inventory = local_inventory @@ -244,7 +242,6 @@ func setup( _network_fishing = network_fishing _world_time = world_time _world_weather = world_weather - _fishable_water_regions_resolver = fishable_water_regions_resolver if ( _network_session != null and not _network_session.state_changed.is_connected( @@ -1667,47 +1664,6 @@ func build_network_context( return _build_fishing_context(region) -func is_species_available_now(fish: FishDataType) -> bool: - if fish == null or not fish.is_selectable(): - return false - var season := ( - _world_time.get_season() - if _world_time != null - else CalendarSeason.UNKNOWN - ) - if not fish.is_available_in_season(season): - return false - if fish.collection_method != FishDataType.CollectionMethod.FISHING: - return true - for region: FishableWaterRegionType in _get_logbook_water_regions(): - if ( - region == null - or region.fish_pool == null - or region.fish_pool.get_fish_by_id(fish.id) == null - ): - continue - var context := _build_fishing_context(region) - if not fish.is_allowed_in_water(context.water_type): - continue - if fish.availability == null or fish.availability.is_available(context): - return true - return false - - -func _get_logbook_water_regions() -> Array[FishableWaterRegionType]: - var regions: Array[FishableWaterRegionType] = [] - if not _fishable_water_regions_resolver.is_valid(): - return regions - var resolved: Variant = _fishable_water_regions_resolver.call() - if resolved is not Array: - return regions - for value: Variant in resolved: - var region := value as FishableWaterRegionType - if region != null and is_instance_valid(region): - regions.append(region) - return regions - - func _get_active_bait_tags() -> Array[StringName]: if _local_player == null or _item_catalog == null: return [] diff --git a/gathering/world_gatherable.gd b/gathering/world_gatherable.gd index 1a71477..8a07ba0 100644 --- a/gathering/world_gatherable.gd +++ b/gathering/world_gatherable.gd @@ -29,16 +29,12 @@ func configure( entity_id = configured_entity_id data = configured_data type_id = data.type_id if data != null else StringName() - # Establish the authoritative transform before constructing transient visuals. - # Effects emitted before this point would begin at the default world origin. - apply_network_state(position, yaw, true) if data != null and data.is_stationary_hotspot(): _ensure_water_spurt_visual() _water_spurt_elapsed = ( float(abs(hash(entity_id)) % 1000) / 1000.0 * WATER_SPURT_INTERVAL_SECONDS ) - _emit_water_spurt() else: _ensure_visual() if data != null and data.catch_data != null and _sprite != null: @@ -54,6 +50,7 @@ func configure( ) ) _sprite.rotation_degrees.x = data.sprite_tilt_degrees + apply_network_state(position, yaw, true) func apply_network_state( @@ -150,6 +147,7 @@ func _ensure_water_spurt_visual() -> void: hole.mesh = hole_mesh hole.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF _water_spurt_root.add_child(hole) + _emit_water_spurt() func _emit_water_spurt() -> void: @@ -161,7 +159,6 @@ func _emit_water_spurt() -> void: particles.amount = 6 particles.lifetime = 0.42 particles.one_shot = true - particles.local_coords = true particles.explosiveness = 1.0 particles.direction = Vector3.UP particles.spread = 32.0 diff --git a/main/main.gd b/main/main.gd index e9dc78f..442c97c 100644 --- a/main/main.gd +++ b/main/main.gd @@ -920,7 +920,6 @@ func _initialize_application(dedicated: bool) -> void: _network_fishing, _world_time, _world_weather, - Callable(_test_world, "get_fishable_water_regions"), ) if dedicated: return @@ -1007,8 +1006,7 @@ func _initialize_application(dedicated: bool) -> void: _game_ui, _game_ui.get_screen_fade(), _test_world.get_player_water_triggers(), - _test_world.get_safe_respawn_points(), - Callable(_test_world, "get_water_recovery_position"), + _test_world.get_safe_respawn_points() ) _ui_pixelation.effective_pixel_size_changed.connect( _on_effective_ui_pixel_size_changed @@ -2346,9 +2344,17 @@ func _on_remote_recovery_requested( peer_id, "Fishing attempt ended." ) - var target_position := _test_world.get_water_recovery_position( - entry_position - ) + var target_position: Vector3 = _test_world.get_player_spawn_transform().origin + var nearest_distance: float = INF + for point: SafeRespawnPoint in _test_world.get_safe_respawn_points(): + if point == null or not point.enabled: + continue + var distance: float = point.get_horizontal_distance_squared( + entry_position + ) + if distance < nearest_distance: + nearest_distance = distance + target_position = point.global_position target_position.y += _water_recovery.respawn_height_offset avatar.global_position = target_position avatar.velocity = Vector3.ZERO @@ -2426,7 +2432,6 @@ func _apply_world( spawn_transform, _test_world.get_player_water_triggers(), _test_world.get_safe_respawn_points(), - Callable(_test_world, "get_water_recovery_position"), ) _shoreline_ambience.configure( _player, diff --git a/scripts/build_playtest.sh b/scripts/build_playtest.sh index 2347d3d..1c74943 100755 --- a/scripts/build_playtest.sh +++ b/scripts/build_playtest.sh @@ -9,7 +9,7 @@ readonly WINDOWS_DIR="${BUILD_ROOT}/windows-x86_64" readonly LINUX_DIR="${BUILD_ROOT}/linux-x86_64" readonly README_SOURCE="${PROJECT_ROOT}/docs/README-PLAYTEST.txt" readonly SOURCE_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse HEAD)" -readonly SOURCE_URL="https://forge.makearmy.io/woofmeow/straywild" +readonly SOURCE_URL="https://forge.makearmy.io/woofmeow/netfishing" readonly WINDOWS_ZIP="${BUILD_ROOT}/straywild-v0.19.0-alpha-windows-x86_64.zip" readonly LINUX_ZIP="${BUILD_ROOT}/straywild-v0.19.0-alpha-linux-x86_64.zip" readonly GODOT_BIN="${GODOT_BIN:-godot}" diff --git a/scripts/build_portmaster.sh b/scripts/build_portmaster.sh index a40e0cd..0487b5b 100755 --- a/scripts/build_portmaster.sh +++ b/scripts/build_portmaster.sh @@ -193,7 +193,7 @@ cat >"${GAME_ROOT}/licenses/SOURCE-CODE.md" < void: ) assert(normal_camera.is_position_behind(chat_anchor)) assert(not free_camera.is_position_behind(chat_anchor)) - var shop_prompt := game_ui.get_node("%ShopPrompt") as Control - var storage_prompt := game_ui.get_node("%StoragePrompt") as Control - game_ui.set_shop_prompt_visible(true, chat_anchor) - game_ui.set_storage_prompt_visible(true, chat_anchor) - assert(shop_prompt.visible) - assert(storage_prompt.visible) chat_ui.show_local_speech("hello there friend") await process_frame assert(bool(player.get("_speech_mouth_active"))) diff --git a/tests/controller_menu_accessibility_validation.gd b/tests/controller_menu_accessibility_validation.gd index 37e43d5..aa2f9e2 100644 --- a/tests/controller_menu_accessibility_validation.gd +++ b/tests/controller_menu_accessibility_validation.gd @@ -1356,24 +1356,6 @@ func _validate_player_menu_nested_controller_back() -> void: == LogbookPageType.ControllerZone.ENTRIES, "Global player-menu Back did not return Logbook to creature selection.", ) - logbook_page.call("_set_index_open", true, false) - logbook_page.set( - "_controller_zone", - LogbookPageType.ControllerZone.INDEX, - ) - _expect( - bool(menu.call("consume_escape")), - "Global player-menu Back did not consume the Logbook index zone.", - ) - _expect( - menu.visible and not bool(logbook_page.get("_index_open")), - "Global player-menu Back did not return from the Logbook index page.", - ) - _expect( - int(logbook_page.get("_controller_zone")) - == LogbookPageType.ControllerZone.TABS, - "Global player-menu Back did not return Logbook index focus to tabs.", - ) var net_page := menu.get("_the_net_page") as Control net_page.set("_active", true) diff --git a/tests/fishing_authority_validation.gd b/tests/fishing_authority_validation.gd index b3c17dd..e7c87bb 100644 --- a/tests/fishing_authority_validation.gd +++ b/tests/fishing_authority_validation.gd @@ -265,15 +265,6 @@ func _run() -> void: assert(bool(water_recovery.get("_prior_movement_enabled"))) assert(bool(water_recovery.get("_prior_camera_input_enabled"))) assert(not pause_menu.visible) - var test_world := main.get_node("TestWorld") as TestWorld - assert(test_world != null) - var recovery_entry := water_recovery.get("_entry_position") as Vector3 - var resolved_recovery := water_recovery.call( - "_resolve_recovery_position" - ) as Vector3 - assert(resolved_recovery.is_equal_approx( - test_world.get_water_recovery_position(recovery_entry) - )) water_recovery.call("_finish_recovery") assert(player.is_movement_enabled()) diff --git a/tests/generated_world_runtime_validation.gd b/tests/generated_world_runtime_validation.gd index 6a3f6c8..f487ae7 100644 --- a/tests/generated_world_runtime_validation.gd +++ b/tests/generated_world_runtime_validation.gd @@ -536,11 +536,6 @@ func _validate_generated_region( -INF, 0.35, ) - _validate_water_recovery_positions( - region, - grass_prop_surface_triangles, - sand_prop_surface_triangles, - ) var anchors := region.get_node( "GatherableAnchors/ReachableTreeTrunks" ) as GatherableAnchorSet3D @@ -1273,76 +1268,6 @@ func _triangle_surface_height_at( return highest -func _validate_water_recovery_positions( - region: GeneratedWorldRegion, - grass_triangles: Array[PackedVector3Array], - sand_triangles: Array[PackedVector3Array], -) -> void: - var fresh_water_root := region.get_node( - "WaterBodies/FreshWaterBodies" - ) as Node3D - assert(fresh_water_root != null and fresh_water_root.get_child_count() > 0) - var fresh_water := fresh_water_root.get_child(0) as WaterBodyAuthoring - assert(fresh_water != null) - var entry_position := fresh_water.global_position - var fallback_position := region.get_player_spawn_transform().origin - var recovery_position := region.get_water_recovery_position( - entry_position, - fallback_position, - ) - assert(recovery_position.is_finite()) - assert( - _horizontal_distance_squared(entry_position, recovery_position) - < _horizontal_distance_squared(entry_position, fallback_position), - "Fresh-water recovery returned the distant generated-world spawn.", - ) - assert( - recovery_position.y - > GeneratedWorldRegion.WATER_HEIGHT - + GeneratedWorldRegion.WATER_RECOVERY_MINIMUM_GROUND_CLEARANCE - ) - var recovery_triangles: Array[PackedVector3Array] = [] - recovery_triangles.append_array(grass_triangles) - recovery_triangles.append_array(sand_triangles) - var center_height := _triangle_surface_height_at( - recovery_position, - recovery_triangles, - ) - assert(center_height > -INF) - assert(absf(center_height - recovery_position.y) <= 0.01) - for direction: Vector2 in GeneratedWorldRegion.PROP_SURFACE_SAMPLE_DIRECTIONS: - var footprint_position := recovery_position + Vector3( - direction.x * GeneratedWorldRegion.WATER_RECOVERY_FOOTPRINT_RADIUS, - 0.0, - direction.y * GeneratedWorldRegion.WATER_RECOVERY_FOOTPRINT_RADIUS, - ) - var footprint_height := _triangle_surface_height_at( - footprint_position, - recovery_triangles, - ) - assert(footprint_height > -INF) - assert( - absf(footprint_height - recovery_position.y) - <= GeneratedWorldRegion.PROP_MAXIMUM_SUPPORT_HEIGHT_DIFFERENCE - ) - assert( - region.get_water_recovery_position( - entry_position, - fallback_position, - ).is_equal_approx(recovery_position) - ) - assert( - region.get_water_recovery_position( - Vector3(INF, 0.0, 0.0), - fallback_position, - ).is_equal_approx(fallback_position) - ) - - -func _horizontal_distance_squared(a: Vector3, b: Vector3) -> float: - return Vector2(a.x - b.x, a.z - b.z).length_squared() - - func _validate_tree_gatherable_anchors( region: GeneratedWorldRegion, decorations: Node3D, diff --git a/tests/logbook_runtime_validation.gd b/tests/logbook_runtime_validation.gd index 47e795d..75fc47f 100644 --- a/tests/logbook_runtime_validation.gd +++ b/tests/logbook_runtime_validation.gd @@ -2,7 +2,6 @@ extends SceneTree const MainScene = preload("res://main/main.tscn") const FishCatchType = preload("res://fish/fish_catch.gd") -const MENU_TRANSITION_SETTLE_SECONDS := 3.0 func _initialize() -> void: @@ -37,16 +36,12 @@ func _run() -> void: var logbook := player_menu.get_node("%CatalogLogbook") as LogbookPage var backdrop := main.get_node("%PlayerMenuBackdrop") as ColorRect var hotbar := game_ui.get_node("%Hotbar") as Control - var fishing_spot := main.get("_fishing_spot") as FishingSpot - assert(logbook.get("_fishing_spot") == fishing_spot) - assert(logbook.get("_world_time") == main.get("_world_time")) - assert(logbook.get("_world_weather") == main.get("_world_weather")) var shortcut := InputEventKey.new() shortcut.pressed = true shortcut.physical_keycode = KEY_L assert(bool(player_menu.call("_handle_direct_page_shortcut", shortcut))) - await create_timer(MENU_TRANSITION_SETTLE_SECONDS).timeout + await create_timer(2.2).timeout assert(player_menu.visible) assert(logbook.visible) assert(backdrop.visible) @@ -56,18 +51,6 @@ func _run() -> void: var entry_buttons: Dictionary = logbook.get("_entry_buttons") assert(entry_buttons.size() == 108) await _capture_if_requested("-unknown") - logbook.call("_set_index_open", true, false) - await process_frame - assert(bool(logbook.get("_index_open"))) - assert((logbook.get("_index_body") as Control).visible) - await _capture_if_requested("-index") - logbook.set_compact_presentation(true) - await process_frame - await _capture_if_requested("-index-compact") - logbook.set_compact_presentation(false) - await process_frame - logbook.call("_set_index_open", false, false) - await process_frame logbook.call( "_select_category", WaterType.Type.FRESH_WATER ) @@ -85,29 +68,13 @@ func _run() -> void: logbook.call("_select_entry", &"bluegill", &"bluegill") await create_timer(0.2).timeout await _capture_if_requested("-known") - var bluegill := ( - main.get("fish_catalog") as FishPool - ).get_fish_by_id(&"bluegill") - var bluegill_available_now := fishing_spot.is_species_available_now(bluegill) - var index_options := logbook.get("_index_options") as Dictionary - index_options["availability"] = ( - LogbookCatalog.AvailabilityFilter.AVAILABLE_NOW - ) - logbook.notify_availability_context_changed() - await process_frame - assert( - (logbook.get("_entry_buttons") as Dictionary).has(&"bluegill") - == bluegill_available_now - ) - logbook.call("_reset_index") - await process_frame assert(bool(player_menu.call("_handle_direct_page_shortcut", shortcut))) - await create_timer(MENU_TRANSITION_SETTLE_SECONDS).timeout + await create_timer(2.2).timeout assert(not player_menu.visible) assert(bool(player_menu.call("_handle_direct_page_shortcut", shortcut))) - await create_timer(MENU_TRANSITION_SETTLE_SECONDS).timeout + await create_timer(2.2).timeout player_menu.call( "_show_section_immediate", PlayerMenu.Section.PROFILE ) diff --git a/tests/logbook_validation.gd b/tests/logbook_validation.gd index 431ed2e..4810e0a 100644 --- a/tests/logbook_validation.gd +++ b/tests/logbook_validation.gd @@ -17,8 +17,6 @@ const CatalogResource: FishPoolType = preload( "res://fish/pools/fish_catalog.tres" ) -var _query_available_id: StringName - func _initialize() -> void: call_deferred("_run") @@ -37,7 +35,6 @@ func _run() -> void: quit() return _validate_catalog() - _validate_catalog_query() await _validate_page() print("Logbook validation: PASS") quit() @@ -71,348 +68,6 @@ func _validate_catalog() -> void: ) -func _validate_catalog_query() -> void: - var collection := CollectionLogType.new() - var inventory := FishInventoryType.new() - var fresh_species: Array[FishDataType] = [] - for fish: FishDataType in LogbookCatalog.ordered_species( - CatalogResource.candidates - ): - if LogbookCatalog.category_for(fish) == LogbookCatalog.Category.FRESH_WATER: - fresh_species.append(fish) - assert(fresh_species.size() == 108) - var first := fresh_species[0] - var second := fresh_species[1] - var unknown := fresh_species[2] - for quality: int in FishQualityType.TIER_COUNT: - collection.record_catch(first.id, quality) - collection.record_catch(second.id, FishQualityType.Tier.BORING) - var held := _make_test_catch(first, true) - assert(inventory.add_catch(held)) - - var options := _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - var filtered := LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered.size() == 2) - assert(first in filtered and second in filtered) - - options["show"] = LogbookCatalog.DiscoveryFilter.MISSING - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered.size() == fresh_species.size() - 2) - options["sort"] = LogbookCatalog.SortMode.CATALOG_NUMBER - options["descending"] = true - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered.front() == fresh_species.back()) - assert(filtered.back() == unknown) - - options["show"] = LogbookCatalog.DiscoveryFilter.INCOMPLETE - options["descending"] = false - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered == [second]) - - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["sort"] = LogbookCatalog.SortMode.CATCH_COUNT - options["descending"] = true - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered == [first, second]) - - options = _default_query_options() - options["search"] = unknown.display_name - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered.is_empty()) - options["search"] = str(unknown.catalog_number) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered == [unknown]) - - options = _default_query_options() - options["holding"] = LogbookCatalog.HoldingFilter.FAVORITED - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered == [first]) - options["holding"] = LogbookCatalog.HoldingFilter.IN_COOLER - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered == [first]) - - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["group"] = String(first.collection_group) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(first in filtered) - for fish: FishDataType in filtered: - assert(fish.collection_group == first.collection_group) - - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["rarity"] = ( - int(first.rarity) + LogbookCatalog.RarityFilter.COMMON - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(first in filtered) - for fish: FishDataType in filtered: - assert(fish.rarity == first.rarity) - - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["method"] = ( - int(first.collection_method) + LogbookCatalog.MethodFilter.FISHING - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(first in filtered) - for fish: FishDataType in filtered: - assert(fish.collection_method == first.collection_method) - - var first_season := -1 - for season: int in 4: - if first.is_available_in_season(season): - first_season = season - break - assert(first_season >= 0) - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["season"] = ( - first_season + LogbookCatalog.SeasonFilter.SPRING - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(first in filtered) - for fish: FishDataType in filtered: - assert(fish.is_available_in_season(first_season)) - - var first_is_available_by_day := ( - first.availability == null or first.availability.allow_day - ) - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["time"] = ( - LogbookCatalog.TimeFilter.DAY - if first_is_available_by_day - else LogbookCatalog.TimeFilter.NIGHT - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(first in filtered) - - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["sort"] = LogbookCatalog.SortMode.QUALITY_PROGRESS - options["descending"] = true - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered == [first, second]) - for sort_mode: LogbookCatalog.SortMode in [ - LogbookCatalog.SortMode.NAME, - LogbookCatalog.SortMode.RARITY, - LogbookCatalog.SortMode.VALUE, - LogbookCatalog.SortMode.WEIGHT, - ]: - options = _default_query_options() - options["sort"] = sort_mode - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered[0] in [first, second]) - assert(filtered[1] in [first, second]) - assert(filtered[0] != filtered[1]) - assert(filtered[2] == unknown) - - _query_available_id = second.id - options = _default_query_options() - options["availability"] = ( - LogbookCatalog.AvailabilityFilter.AVAILABLE_NOW - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - Callable(self, "_query_species_available_now"), - ) - assert(filtered == [second]) - - _query_available_id = unknown.id - options["show"] = LogbookCatalog.DiscoveryFilter.MISSING - options["rarity"] = ( - int(unknown.rarity) + LogbookCatalog.RarityFilter.COMMON - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - Callable(self, "_query_species_available_now"), - ) - assert(filtered == [unknown]) - - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.MISSING - options["group"] = String(unknown.collection_group) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(unknown in filtered) - assert( - String(unknown.collection_group) - in LogbookCatalog.available_groups( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - ) - ) - - var unknown_is_available_by_day := ( - unknown.availability == null or unknown.availability.allow_day - ) - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.MISSING - options["time"] = ( - LogbookCatalog.TimeFilter.DAY - if unknown_is_available_by_day - else LogbookCatalog.TimeFilter.NIGHT - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(unknown in filtered) - inventory.free() - collection.free() - - -func _default_query_options() -> Dictionary: - return { - "search": "", - "show": LogbookCatalog.DiscoveryFilter.ALL, - "sort": LogbookCatalog.SortMode.CATALOG_NUMBER, - "descending": false, - "availability": LogbookCatalog.AvailabilityFilter.ANY, - "season": LogbookCatalog.SeasonFilter.ANY, - "time": LogbookCatalog.TimeFilter.ANY, - "rarity": LogbookCatalog.RarityFilter.ANY, - "method": LogbookCatalog.MethodFilter.ANY, - "holding": LogbookCatalog.HoldingFilter.ANY, - "group": "", - } - - -func _make_test_catch(fish: FishDataType, favorited: bool) -> FishCatchType: - var fish_catch := FishCatchType.new() - fish_catch.fish = fish - fish_catch.fish_id = fish.id - fish_catch.weight_lb = fish.get_minimum_weight() - fish_catch.display_scale = fish.get_display_scale_for_weight( - fish_catch.weight_lb - ) - fish_catch.quality = FishQualityType.Tier.BORING - fish_catch.sale_value = fish.get_sale_value_for_weight(fish_catch.weight_lb) - fish_catch.is_favorited = favorited - fish_catch.ensure_identity() - return fish_catch - - -func _query_species_available_now(fish: FishDataType) -> bool: - return fish != null and fish.id == _query_available_id - - func _validate_page() -> void: var collection := CollectionLogType.new() var inventory := FishInventoryType.new() @@ -436,10 +91,6 @@ func _validate_page() -> void: assert(outer_margin.get_theme_constant("margin_right") == 160) assert(outer_margin.get_theme_constant("margin_bottom") == 106) assert(is_equal_approx(book.offset_top, 65.0)) - page.call("_set_index_open", true, false) - await process_frame - _validate_index_content_bounds(page) - page.call("_set_index_open", false, false) page.set_compact_presentation(false) await process_frame assert(outer_margin.get_theme_constant("margin_left") == 52) @@ -507,39 +158,6 @@ func _validate_page() -> void: ) var initial_detail_body := page.get("_detail_body") as VBoxContainer assert(not initial_detail_body.get_parent() is ScrollContainer) - var index_tab := page.get("_index_tab") as Button - var index_body := page.get("_index_body") as VBoxContainer - assert(index_tab != null and index_body != null) - assert(index_tab.size == LogbookPage.INDEX_TAB_SIZE) - assert( - not index_tab.get_global_rect().intersects( - (category_tabs.back() as Button).get_global_rect() - ) - ) - page.call("_set_index_open", true, false) - await process_frame - assert(index_body.visible) - assert(not initial_detail_body.visible) - assert((page.get("_index_option_buttons") as Dictionary).size() == 10) - _validate_index_content_bounds(page) - page.call( - "_cycle_index_option", - &"show", - 1, - ) - await process_frame - assert((page.get("_entry_buttons") as Dictionary).is_empty()) - assert( - (page.get("_empty_state") as Label).text - == "No entries match this index." - ) - page.call("_reset_index") - await process_frame - assert((page.get("_entry_buttons") as Dictionary).size() == 108) - page.call("_set_index_open", false, false) - await process_frame - assert(not index_body.visible) - assert(initial_detail_body.visible) assert( page.call("_entry_label_text", "flathead catfish") == "flathead\ncatfish" @@ -840,19 +458,8 @@ func _validate_page() -> void: (page.get("_portrait_overlay_backdrop") as Button).pressed.emit() await process_frame - var live_index_options := page.get("_index_options") as Dictionary - live_index_options["holding"] = LogbookCatalog.HoldingFilter.IN_COOLER - page.call("_refresh_catalog") - await process_frame - assert( - (page.get("_entry_buttons") as Dictionary).keys() - == [&"bluegill"] - ) inventory.remove_catch_by_id(fish_catch.catch_id) await process_frame - assert((page.get("_entry_buttons") as Dictionary).is_empty()) - page.call("_reset_index") - await process_frame assert(not _detail_text(page).contains("number owned")) page.queue_free() collection.queue_free() @@ -867,54 +474,6 @@ func _detail_text(page: LogbookPage) -> String: return "\n".join(values) -func _validate_index_content_bounds(page: LogbookPage) -> void: - var body := page.get("_index_body") as VBoxContainer - assert(body != null and body.visible) - var right_page := body.get_parent() as Control - assert(right_page != null) - var page_rect := right_page.get_global_rect() - var body_rect := body.get_global_rect() - var protected_rect := Rect2( - page_rect.position + Vector2( - LogbookPage.INDEX_CONTENT_LEFT_INSET, - LogbookPage.INDEX_CONTENT_TOP_INSET, - ), - page_rect.size - Vector2( - LogbookPage.INDEX_CONTENT_LEFT_INSET - + LogbookPage.INDEX_CONTENT_RIGHT_INSET, - LogbookPage.INDEX_CONTENT_TOP_INSET - + LogbookPage.INDEX_CONTENT_BOTTOM_INSET, - ), - ) - assert( - body_rect.position.x - >= page_rect.position.x + LogbookPage.INDEX_CONTENT_LEFT_INSET - 1.0 - ) - assert( - body_rect.end.x - <= page_rect.end.x - LogbookPage.INDEX_CONTENT_RIGHT_INSET + 1.0 - ) - assert( - body_rect.position.y >= page_rect.position.y, - "Index starts above the authored page: body=%s page=%s" - % [body_rect, page_rect], - ) - assert( - body_rect.end.y <= page_rect.end.y, - "Index extends below the authored page: body=%s page=%s" - % [body_rect, page_rect], - ) - var controls: Array = page.get("_index_controls") as Array - for value: Variant in controls: - var control := value as Control - if control != null and control.visible: - assert( - protected_rect.grow(2.0).encloses(control.get_global_rect()), - "Index control crossed the authored right-page boundary: %s" - % control.name, - ) - - func _descendant_label_text(parent: Node) -> String: var values: PackedStringArray = [] for node: Node in parent.find_children("*", "Label", true, false): diff --git a/tests/world_layout_validation.gd b/tests/world_layout_validation.gd index e6eec39..dd40fcf 100644 --- a/tests/world_layout_validation.gd +++ b/tests/world_layout_validation.gd @@ -100,14 +100,6 @@ func _validate_world_switching() -> void: assert(world.get_fishing_shop() != null) assert(world.get_player_storage() != null) assert(not world.get_fishable_water_regions().is_empty()) - var starter_safe_points := world.get_safe_respawn_points() - assert(not starter_safe_points.is_empty()) - var authored_safe_point: SafeRespawnPoint = starter_safe_points.back() - assert( - world.get_water_recovery_position( - authored_safe_point.global_position - ).is_equal_approx(authored_safe_point.global_position) - ) assert(world.activate_world(WorldLayout.GENERATED, 24680)) assert(world.get_world_layout() == WorldLayout.GENERATED) assert(world.get_generation_seed() == 24680) diff --git a/tests/world_spawn_protocol_validation.gd b/tests/world_spawn_protocol_validation.gd index d100260..a8365a1 100644 --- a/tests/world_spawn_protocol_validation.gd +++ b/tests/world_spawn_protocol_validation.gd @@ -16,10 +16,6 @@ const CalendarSeasonType = preload("res://world/calendar_season.gd") func _initialize() -> void: - call_deferred(&"_run") - - -func _run() -> void: assert(NetworkProtocol.PROTOCOL_VERSION == 11) assert( NetworkWorldSpawnProtocol.CAPABILITY @@ -27,7 +23,7 @@ func _run() -> void: ) assert(NetworkWorldSpawnProtocol.SNAPSHOT_ENTITIES_PER_ENVELOPE <= 4) _validate_catalog_statuses() - await _validate_billboard_presentation() + _validate_billboard_presentation() _validate_envelopes() print("World spawn protocol validation: PASS") quit() @@ -152,27 +148,8 @@ func _validate_billboard_presentation() -> void: assert(not sprite.shaded) gatherable.free() - var unconfigured_hotspot := WorldGatherableType.new() - unconfigured_hotspot.call("_ensure_water_spurt_visual") - assert( - unconfigured_hotspot.get_node_or_null( - "WaterSpurt/WaterDroplets" - ) == null - ) - unconfigured_hotspot.free() - - var clam: GatherableData = Gatherables.get_entry(&"clam_manila") - assert(clam != null) var hotspot := WorldGatherableType.new() - root.add_child(hotspot) - var configured_position := Vector3(24.0, 0.3, -11.0) - hotspot.configure( - "world:clam-presentation-regression", - clam, - configured_position, - 0.0, - ) - assert(hotspot.global_position.is_equal_approx(configured_position)) + hotspot.call("_ensure_water_spurt_visual") var hole := hotspot.get_node("WaterSpurt/BurrowMark") as MeshInstance3D assert(hole != null) assert(hole.cast_shadow == GeometryInstance3D.SHADOW_CASTING_SETTING_OFF) @@ -181,18 +158,6 @@ func _validate_billboard_presentation() -> void: assert(hole_material.shading_mode == BaseMaterial3D.SHADING_MODE_UNSHADED) assert(hole_material.transparency == BaseMaterial3D.TRANSPARENCY_DISABLED) assert(is_equal_approx(hole_material.albedo_color.a, 1.0)) - var particles := hotspot.get_node( - "WaterSpurt/WaterDroplets" - ) as CPUParticles3D - assert(particles != null and particles.emitting) - assert(particles.local_coords) - assert(particles.global_position.is_equal_approx( - configured_position + Vector3.UP * 0.035 - )) - hotspot.set_process(false) - await create_timer(0.6).timeout - await process_frame - assert(hotspot.get_node_or_null("WaterSpurt/WaterDroplets") == null) hotspot.queue_free() diff --git a/ui/game_ui.gd b/ui/game_ui.gd index 3ce0d17..6e4241b 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -260,9 +260,6 @@ func _ready() -> void: _player_menu.controller_hotbar_management_ended.connect( _on_controller_hotbar_management_ended ) - _player_menu.controller_text_entry_requested.connect( - request_controller_text_entry_for - ) _title_settings_panel.panel_visibility_changed.connect( _on_settings_visibility_changed ) @@ -462,7 +459,6 @@ func setup( discovery, player_jobs, world_time, - world_weather, world_environment, world_sun, ) @@ -1896,7 +1892,7 @@ func _position_storage_prompt(world_anchor: Vector3) -> void: if _player == null: _storage_prompt.hide() return - var camera := _player.get_active_gameplay_camera() + var camera := _player.get_gameplay_camera() if camera == null or camera.is_position_behind(world_anchor): _storage_prompt.hide() return @@ -2010,7 +2006,7 @@ func _position_shop_prompt(world_anchor: Vector3) -> void: if _player == null: _shop_prompt.hide() return - var camera: Camera3D = _player.get_active_gameplay_camera() + var camera: Camera3D = _player.get_gameplay_camera() if camera == null or camera.is_position_behind(world_anchor): _shop_prompt.hide() return diff --git a/ui/logbook_catalog.gd b/ui/logbook_catalog.gd index ac7a910..d5dc639 100644 --- a/ui/logbook_catalog.gd +++ b/ui/logbook_catalog.gd @@ -2,9 +2,6 @@ class_name LogbookCatalog extends RefCounted const FishDataType = preload("res://fish/fish_data.gd") -const FishInventoryType = preload("res://inventory/fish_inventory.gd") -const CollectionLogType = preload("res://collection/collection_log.gd") -const CalendarSeasonType = preload("res://world/calendar_season.gd") enum Category { FRESH_WATER, @@ -13,66 +10,6 @@ enum Category { SHELLFISH, } -enum DiscoveryFilter { - ALL, - CAUGHT, - MISSING, - INCOMPLETE, -} - -enum SortMode { - CATALOG_NUMBER, - NAME, - RARITY, - CATCH_COUNT, - QUALITY_PROGRESS, - VALUE, - WEIGHT, -} - -enum AvailabilityFilter { - ANY, - AVAILABLE_NOW, -} - -enum SeasonFilter { - ANY, - CURRENT, - SPRING, - SUMMER, - FALL, - WINTER, -} - -enum TimeFilter { - ANY, - CURRENT, - DAY, - NIGHT, -} - -enum RarityFilter { - ANY, - COMMON, - UNCOMMON, - RARE, - EPIC, - LEGENDARY, -} - -enum MethodFilter { - ANY, - FISHING, - NET, - DIGGING, -} - -enum HoldingFilter { - ANY, - IN_COOLER, - FAVORITED, -} - static func category_for(fish: FishDataType) -> Category: if fish == null: @@ -136,278 +73,3 @@ static func ordered_species( return a.catalog_number < b.catalog_number ) return ordered - - -static func filtered_species( - candidates: Array[FishDataType], - category: Category, - collection_log: CollectionLogType, - inventory: FishInventoryType, - options: Dictionary, - available_now_resolver: Callable = Callable(), - current_season: int = CalendarSeasonType.UNKNOWN, - current_is_night: bool = false, -) -> Array[FishDataType]: - var filtered: Array[FishDataType] = [] - for fish: FishDataType in ordered_species(candidates): - if category_for(fish) != category: - continue - if not _matches_filters( - fish, - collection_log, - inventory, - options, - available_now_resolver, - current_season, - current_is_night, - ): - continue - filtered.append(fish) - var sort_mode := int(options.get("sort", SortMode.CATALOG_NUMBER)) - var descending := bool(options.get("descending", false)) - filtered.sort_custom( - func(first: FishDataType, second: FishDataType) -> bool: - return _species_less_than( - first, - second, - collection_log, - sort_mode, - descending, - ) - ) - return filtered - - -static func available_groups( - candidates: Array[FishDataType], - category: Category, - _collection_log: CollectionLogType, -) -> Array[String]: - var groups: Array[String] = [] - for fish: FishDataType in ordered_species(candidates): - if category_for(fish) != category: - continue - var group := _group_label(fish) - if not group.is_empty() and group not in groups: - groups.append(group) - groups.sort_custom( - func(first: String, second: String) -> bool: - return first.naturalnocasecmp_to(second) < 0 - ) - return groups - - -static func quality_progress( - collection_log: CollectionLogType, - fish_id: StringName, -) -> int: - if collection_log == null: - return 0 - var mask: int = collection_log.get_quality_mask(fish_id) - var count: int = 0 - while mask > 0: - count += mask & 1 - mask >>= 1 - return count - - -static func _matches_filters( - fish: FishDataType, - collection_log: CollectionLogType, - inventory: FishInventoryType, - options: Dictionary, - available_now_resolver: Callable, - current_season: int, - current_is_night: bool, -) -> bool: - var discovered := ( - collection_log != null and collection_log.has_discovered(fish.id) - ) - match int(options.get("show", DiscoveryFilter.ALL)): - DiscoveryFilter.CAUGHT: - if not discovered: - return false - DiscoveryFilter.MISSING: - if discovered: - return false - DiscoveryFilter.INCOMPLETE: - if ( - not discovered - or collection_log.has_mastered(fish.id) - ): - return false - - var search_text := str(options.get("search", "")).strip_edges().to_lower() - if not search_text.is_empty(): - var number_text := str(catalog_number(fish)) - var number_query := search_text.trim_prefix("#") - var number_matches := ( - number_query.is_valid_int() - and number_text == number_query - ) - var identity_matches := false - if discovered: - identity_matches = " ".join([ - fish.display_name, - _group_label(fish), - fish.get_habitat_label(), - ]).to_lower().contains(search_text) - if not number_matches and not identity_matches: - return false - - if ( - int(options.get("availability", AvailabilityFilter.ANY)) - == AvailabilityFilter.AVAILABLE_NOW - ): - if available_now_resolver.is_valid(): - var resolved: Variant = available_now_resolver.call(fish) - if typeof(resolved) != TYPE_BOOL or not bool(resolved): - return false - elif not _matches_season(fish, current_season): - return false - - var season_filter := int(options.get("season", SeasonFilter.ANY)) - if season_filter != SeasonFilter.ANY: - var requested_season := current_season - if season_filter >= SeasonFilter.SPRING: - requested_season = season_filter - SeasonFilter.SPRING - if not _matches_season(fish, requested_season): - return false - - var time_filter := int(options.get("time", TimeFilter.ANY)) - if time_filter != TimeFilter.ANY: - var requested_night := current_is_night - if time_filter == TimeFilter.DAY: - requested_night = false - elif time_filter == TimeFilter.NIGHT: - requested_night = true - if not _matches_time(fish, requested_night): - return false - - var rarity_filter := int(options.get("rarity", RarityFilter.ANY)) - if ( - rarity_filter != RarityFilter.ANY - and fish.rarity != rarity_filter - RarityFilter.COMMON - ): - return false - - var method_filter := int(options.get("method", MethodFilter.ANY)) - if ( - method_filter != MethodFilter.ANY - and fish.collection_method != method_filter - MethodFilter.FISHING - ): - return false - - var holding_filter := int(options.get("holding", HoldingFilter.ANY)) - if holding_filter != HoldingFilter.ANY: - var favorites_only := holding_filter == HoldingFilter.FAVORITED - if not _has_current_catch(inventory, fish.id, favorites_only): - return false - - var requested_group := str(options.get("group", "")) - if ( - not requested_group.is_empty() - and _group_label(fish).nocasecmp_to(requested_group) != 0 - ): - return false - return true - - -static func _species_less_than( - first: FishDataType, - second: FishDataType, - collection_log: CollectionLogType, - sort_mode: int, - descending: bool, -) -> bool: - var first_discovered := ( - collection_log != null and collection_log.has_discovered(first.id) - ) - var second_discovered := ( - collection_log != null and collection_log.has_discovered(second.id) - ) - if sort_mode != SortMode.CATALOG_NUMBER: - if first_discovered != second_discovered: - return first_discovered - if not first_discovered and not second_discovered: - return _catalog_less_than(first, second) - - var comparison: int = 0 - match sort_mode: - SortMode.NAME: - comparison = first.display_name.naturalnocasecmp_to( - second.display_name - ) - SortMode.RARITY: - comparison = first.rarity - second.rarity - SortMode.CATCH_COUNT: - comparison = ( - collection_log.get_catch_count(first.id) - - collection_log.get_catch_count(second.id) - ) - SortMode.QUALITY_PROGRESS: - comparison = ( - quality_progress(collection_log, first.id) - - quality_progress(collection_log, second.id) - ) - SortMode.VALUE: - comparison = first.sell_value_max - second.sell_value_max - SortMode.WEIGHT: - comparison = _compare_floats( - first.get_maximum_weight(), - second.get_maximum_weight(), - ) - _: - comparison = first.catalog_number - second.catalog_number - if comparison != 0: - return comparison > 0 if descending else comparison < 0 - return _catalog_less_than(first, second) - - -static func _catalog_less_than(first: FishDataType, second: FishDataType) -> bool: - if first.catalog_number == second.catalog_number: - return String(first.id) < String(second.id) - return first.catalog_number < second.catalog_number - - -static func _compare_floats(first: float, second: float) -> int: - if is_equal_approx(first, second): - return 0 - return -1 if first < second else 1 - - -static func _matches_season(fish: FishDataType, season: int) -> bool: - return fish.is_available_in_season(season) - - -static func _matches_time(fish: FishDataType, requested_night: bool) -> bool: - if ( - fish.collection_method != FishDataType.CollectionMethod.FISHING - or fish.availability == null - ): - return true - return ( - fish.availability.allow_night - if requested_night - else fish.availability.allow_day - ) - - -static func _has_current_catch( - inventory: FishInventoryType, - fish_id: StringName, - favorites_only: bool, -) -> bool: - if inventory == null: - return false - for fish_catch in inventory.get_catches_by_fish_id(fish_id): - if fish_catch != null and (not favorites_only or fish_catch.is_favorited): - return true - return false - - -static func _group_label(fish: FishDataType) -> String: - if fish == null: - return "" - var group := String(fish.collection_group).strip_edges() - return group if not group.is_empty() else fish.get_habitat_label() diff --git a/ui/logbook_page.gd b/ui/logbook_page.gd index cc490a0..6319dd6 100644 --- a/ui/logbook_page.gd +++ b/ui/logbook_page.gd @@ -6,21 +6,10 @@ const FishDataType = preload("res://fish/fish_data.gd") const FishQualityType = preload("res://fish/fish_quality.gd") const FishInventoryType = preload("res://inventory/fish_inventory.gd") const FishPoolType = preload("res://fish/fish_pool.gd") -const FishingSpotType = preload("res://fishing/fishing_spot.gd") -const WorldTimeServiceType = preload("res://world/world_time_service.gd") -const WorldWeatherServiceType = preload( - "res://world/world_weather_service.gd" -) const SILHOUETTE_SHADER: Shader = preload( "res://ui/logbook_silhouette.gdshader" ) const OrganizerTabType = preload("res://ui/components/organizer_tab.gd") -const ControllerFocusNavigationType = preload( - "res://ui/controller_focus_navigation.gd" -) -const NOTEPAD_INK_ACTION_SCENE: PackedScene = preload( - "res://ui/components/bubble_menu/notepad_ink_action.tscn" -) const InventoryNotepadType = preload( "res://ui/components/inventory_notepad.gd" ) @@ -59,8 +48,6 @@ const LOGBOOK_TAB_LEFT_INSET: float = 92.0 const LOGBOOK_TAB_SIZE := Vector2(108.0, 38.0) const LOGBOOK_TAB_SEPARATION: float = 6.0 const LOGBOOK_TAB_TEXT_SIDE_INSET: float = 10.0 -const INDEX_TAB_SIZE := Vector2(88.0, 38.0) -const INDEX_TAB_RIGHT_INSET: float = 72.0 const PAGE_CONTENT_SCALE: float = 0.97 const WIDE_OUTER_MARGINS := Rect2(52.0, 85.0, 52.0, 18.0) const COMPACT_OUTER_MARGINS := Rect2(160.0, 85.0, 160.0, 106.0) @@ -74,53 +61,17 @@ const DETAIL_SECTION_SEPARATION: int = 10 const DETAIL_QUALITY_SECTION_SIZE := Vector2(300.0, 104.0) const DETAIL_STATS_SECTION_SIZE := Vector2(400.0, 160.0) const DETAIL_STATS_ROW_SEPARATION: int = 8 -const INDEX_OPTION_HEIGHT: float = 34.0 -const INDEX_CONTENT_LEFT_INSET: float = 44.0 -const INDEX_CONTENT_TOP_INSET: float = 4.0 -const INDEX_CONTENT_RIGHT_INSET: float = 22.0 -const INDEX_CONTENT_BOTTOM_INSET: float = 10.0 -const INDEX_SHOW_LABELS := [ - "all entries", "caught", "missing", "needs quality", -] -const INDEX_SORT_LABELS := [ - "catalog no.", "name", "rarity", "number caught", - "quality progress", "value", "weight", -] -const INDEX_AVAILABILITY_LABELS := [ - "any time", "available now", -] -const INDEX_SEASON_LABELS := [ - "any season", "current season", "spring", "summer", "fall", "winter", -] -const INDEX_TIME_LABELS := [ - "any time", "current time", "day", "night", -] -const INDEX_RARITY_LABELS := [ - "any rarity", "common", "uncommon", "rare", "epic", "legendary", -] -const INDEX_METHOD_LABELS := [ - "any method", "fishing", "netting", "digging", -] -const INDEX_HOLDING_LABELS := [ - "any ownership", "in cooler", "favorited", -] - -signal controller_text_entry_requested(control: Control) enum ControllerZone { TABS, ENTRIES, DETAILS, - INDEX, OVERLAY, } var _collection_log: CollectionLogType var _inventory: FishInventoryType var _catalog: FishPoolType -var _fishing_spot: FishingSpotType -var _world_time: WorldTimeServiceType -var _world_weather: WorldWeatherServiceType var _category: LogbookCatalog.Category = ( LogbookCatalog.Category.FRESH_WATER ) @@ -160,31 +111,6 @@ var _portrait_overlay_stats_view: Control var _portrait_overlay_stats: GridContainer var _overlay_return_focus: Control var _controller_zone: ControllerZone = ControllerZone.TABS -var _index_tab: OrganizerTabType -var _index_body: VBoxContainer -var _index_search: LineEdit -var _index_summary: Label -var _index_note: Label -var _index_reset: Button -var _index_option_buttons: Dictionary[StringName, Button] = {} -var _index_controls: Array[Control] = [] -var _index_open: bool = false -var _index_update_in_progress: bool = false -var _index_visible_count: int = 0 -var _index_category_total: int = 0 -var _index_options: Dictionary = { - "search": "", - "show": LogbookCatalog.DiscoveryFilter.ALL, - "sort": LogbookCatalog.SortMode.CATALOG_NUMBER, - "descending": false, - "availability": LogbookCatalog.AvailabilityFilter.ANY, - "season": LogbookCatalog.SeasonFilter.ANY, - "time": LogbookCatalog.TimeFilter.ANY, - "rarity": LogbookCatalog.RarityFilter.ANY, - "method": LogbookCatalog.MethodFilter.ANY, - "holding": LogbookCatalog.HoldingFilter.ANY, - "group": "", -} func _ready() -> void: @@ -198,16 +124,10 @@ func setup( collection_log: CollectionLogType, inventory: FishInventoryType, catalog: FishPoolType, - fishing_spot: FishingSpotType = null, - world_time: WorldTimeServiceType = null, - world_weather: WorldWeatherServiceType = null, ) -> void: _collection_log = collection_log _inventory = inventory _catalog = catalog - _fishing_spot = fishing_spot - _world_time = world_time - _world_weather = world_weather if not _collection_log.fish_discovered.is_connected( _on_fish_discovered ): @@ -218,28 +138,12 @@ func setup( _collection_log.collection_changed.connect(_on_collection_changed) if not _inventory.catches_changed.is_connected(_on_inventory_changed): _inventory.catches_changed.connect(_on_inventory_changed) - if ( - _world_time != null - and not _world_time.time_changed.is_connected( - _on_logbook_context_changed - ) - ): - _world_time.time_changed.connect(_on_logbook_context_changed) - _world_time.calendar_date_changed.connect(_on_logbook_context_changed) - if ( - _world_weather != null - and not _world_weather.weather_changed.is_connected( - _on_logbook_context_changed - ) - ): - _world_weather.weather_changed.connect(_on_logbook_context_changed) _refresh_catalog() func set_compact_presentation(compact: bool) -> void: _compact_presentation = compact _apply_presentation_margins() - _apply_index_presentation() func activate() -> void: @@ -276,17 +180,6 @@ func set_interactive(value: bool) -> void: if _interactive and not tab.button_pressed else Control.MOUSE_FILTER_IGNORE ) - if _index_tab != null: - _index_tab.focus_mode = ( - Control.FOCUS_ALL - if _interactive and _controller_zone == ControllerZone.TABS - else Control.FOCUS_NONE - ) - _index_tab.mouse_filter = ( - Control.MOUSE_FILTER_STOP - if _interactive - else Control.MOUSE_FILTER_IGNORE - ) for entry: Button in _entry_buttons.values(): entry.focus_mode = ( Control.FOCUS_ALL @@ -306,19 +199,6 @@ func set_interactive(value: bool) -> void: if _interactive and _controller_zone == ControllerZone.DETAILS else Control.FOCUS_NONE ) - for index_control: Control in _index_controls: - if not is_instance_valid(index_control): - continue - index_control.focus_mode = ( - Control.FOCUS_ALL - if _interactive and _controller_zone == ControllerZone.INDEX - else Control.FOCUS_NONE - ) - index_control.mouse_filter = ( - Control.MOUSE_FILTER_STOP - if _interactive and _index_open - else Control.MOUSE_FILTER_IGNORE - ) if _portrait_overlay_backdrop != null: _portrait_overlay_backdrop.focus_mode = ( Control.FOCUS_ALL @@ -334,9 +214,6 @@ func focus_initial() -> void: _portrait_overlay_backdrop.grab_focus() return if _controller_zone == ControllerZone.TABS: - if _index_open and _index_tab != null: - _index_tab.grab_focus() - return var category_tab_index: int = _category_tab_categories.find(_category) if category_tab_index >= 0: _category_tabs[category_tab_index].grab_focus() @@ -345,10 +222,6 @@ func focus_initial() -> void: if not _detail_buttons.is_empty(): _detail_buttons.front().grab_focus() return - if _controller_zone == ControllerZone.INDEX: - if _index_search != null: - _index_search.grab_focus() - return var selected := _entry_buttons.get(_selected_entry_key) as Button if selected != null: selected.grab_focus() @@ -374,8 +247,6 @@ func handle_controller_input(event: InputEvent) -> bool: _hide_portrait_overlay() ControllerZone.DETAILS: _set_controller_zone(ControllerZone.ENTRIES) - ControllerZone.INDEX: - _set_index_open(false) ControllerZone.ENTRIES: _set_controller_zone(ControllerZone.TABS) ControllerZone.TABS: @@ -383,43 +254,15 @@ func handle_controller_input(event: InputEvent) -> bool: return true if _controller_zone == ControllerZone.TABS: if event.is_action_pressed("ui_left"): - if _index_open: - _set_index_open(false, false) - _category_tabs.back().grab_focus() - return true _select_adjacent_controller_category(-1) return true if event.is_action_pressed("ui_right"): - if ( - _category_tab_categories.find(_category) - == _category_tab_categories.size() - 1 - ): - _set_index_open(true, false) - _index_tab.grab_focus() - return true _select_adjacent_controller_category(1) return true if accept_pressed: - if _index_open: - _set_controller_zone(ControllerZone.INDEX) - elif not _entry_buttons.is_empty(): + if not _entry_buttons.is_empty(): _set_controller_zone(ControllerZone.ENTRIES) return true - if _controller_zone == ControllerZone.INDEX: - var focus_owner := get_viewport().gui_get_focus_owner() - if accept_pressed and focus_owner == _index_search: - controller_text_entry_requested.emit(_index_search) - return true - if event.is_action_pressed("ui_left"): - var previous_key := _index_key_for_control(focus_owner) - if not previous_key.is_empty(): - _cycle_index_option(previous_key, -1) - return true - if event.is_action_pressed("ui_right") or accept_pressed: - var next_key := _index_key_for_control(focus_owner) - if not next_key.is_empty(): - _cycle_index_option(next_key, 1) - return true if _controller_zone == ControllerZone.ENTRIES and accept_pressed: if not _selected_entry_key.is_empty() and not _detail_buttons.is_empty(): _set_controller_zone(ControllerZone.DETAILS) @@ -486,20 +329,6 @@ func _build_interface() -> void: tabs.add_child(tab) _category_tabs.append(tab) _category_tab_categories.append(category) - _index_tab = OrganizerTabType.new() - _index_tab.name = "CatalogIndexTab" - _index_tab.anchor_left = 1.0 - _index_tab.anchor_right = 1.0 - _index_tab.offset_left = -INDEX_TAB_RIGHT_INSET - INDEX_TAB_SIZE.x - _index_tab.offset_top = 37.0 - _index_tab.offset_right = -INDEX_TAB_RIGHT_INSET - _index_tab.offset_bottom = 37.0 + INDEX_TAB_SIZE.y - _index_tab.text = "index" - _index_tab.palette_index = 0 - _index_tab.tooltip_text = "Open catalog index" - _index_tab.accessibility_name = "Catalog index" - _index_tab.pressed.connect(_toggle_index_page) - stack.add_child(_index_tab) _configure_category_focus() var book := Control.new() @@ -589,176 +418,9 @@ func _build_interface() -> void: right_page.add_child(_detail_body) _apply_page_content_scale(_detail_body) _show_no_selection() - _build_index_page(right_page) _build_portrait_overlay() -func _build_index_page(right_page: Control) -> void: - _index_body = VBoxContainer.new() - _index_body.name = "CatalogIndex" - _index_body.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - _index_body.offset_left = INDEX_CONTENT_LEFT_INSET - _index_body.offset_top = INDEX_CONTENT_TOP_INSET - _index_body.offset_right = -INDEX_CONTENT_RIGHT_INSET - _index_body.offset_bottom = -INDEX_CONTENT_BOTTOM_INSET - _index_body.add_theme_constant_override("separation", 4) - _index_body.visible = false - right_page.add_child(_index_body) - _apply_page_content_scale(_index_body) - _apply_index_presentation() - - var heading := _label("catalog index", 28) - heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - _index_body.add_child(heading) - _index_summary = _field_label("", 14) - _index_summary.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - _index_summary.add_theme_color_override("font_color", MUTED_INK) - _index_body.add_child(_index_summary) - - var search_stack := VBoxContainer.new() - search_stack.add_theme_constant_override("separation", 0) - _index_body.add_child(search_stack) - var search_label := _field_label("find by name, group, or catalog no.", 13) - search_label.add_theme_color_override("font_color", MUTED_INK) - search_stack.add_child(search_label) - _index_search = LineEdit.new() - _index_search.name = "CatalogIndexSearch" - _index_search.custom_minimum_size.y = 34.0 - _index_search.placeholder_text = "write here..." - _index_search.max_length = 48 - _index_search.add_theme_font_override( - "font", InventoryNotepadType.HANDWRITTEN_FONT - ) - _index_search.add_theme_font_size_override("font_size", 18) - _index_search.add_theme_color_override("font_color", INK) - _index_search.add_theme_color_override("caret_color", INK) - _index_search.add_theme_color_override( - "font_placeholder_color", Color(MUTED_INK, 0.72) - ) - for style_name: StringName in [ - &"normal", &"focus", &"read_only", - ]: - _index_search.add_theme_stylebox_override( - style_name, - _index_search_style(style_name == &"focus"), - ) - _index_search.text_changed.connect(_on_index_search_changed) - search_stack.add_child(_index_search) - _index_controls.append(_index_search) - - var options_grid := GridContainer.new() - options_grid.columns = 2 - options_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL - options_grid.add_theme_constant_override("h_separation", 18) - options_grid.add_theme_constant_override("v_separation", 2) - _index_body.add_child(options_grid) - for descriptor: Dictionary in [ - {"key": &"show", "label": "show"}, - {"key": &"rarity", "label": "rarity"}, - {"key": &"sort", "label": "sort by"}, - {"key": &"availability", "label": "availability"}, - {"key": &"descending", "label": "order"}, - {"key": &"season", "label": "season"}, - {"key": &"method", "label": "method"}, - {"key": &"time", "label": "time of day"}, - {"key": &"holding", "label": "ownership"}, - {"key": &"group", "label": "collection group"}, - ]: - options_grid.add_child(_make_index_option( - descriptor["key"], - str(descriptor["label"]), - )) - - var footer := HBoxContainer.new() - footer.alignment = BoxContainer.ALIGNMENT_CENTER - _index_body.add_child(footer) - _index_reset = NOTEPAD_INK_ACTION_SCENE.instantiate() as Button - _index_reset.name = "CatalogIndexReset" - _index_reset.custom_minimum_size = Vector2(126.0, 34.0) - _index_reset.text = "clear index" - _apply_index_value_font(_index_reset) - _index_reset.tooltip_text = "Clear every catalog filter" - _index_reset.accessibility_name = "Clear catalog index filters" - _index_reset.pressed.connect(_reset_index) - footer.add_child(_index_reset) - _index_controls.append(_index_reset) - - _index_note = _field_label( - "silhouettes keep names hidden in filtered results", - 12, - ) - _index_note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - _index_note.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - _index_note.add_theme_color_override("font_color", Color(MUTED_INK, 0.82)) - _index_body.add_child(_index_note) - _update_index_controls() - _configure_index_focus.call_deferred() - - -func _make_index_option(key: StringName, label_text: String) -> Control: - var stack := VBoxContainer.new() - stack.size_flags_horizontal = Control.SIZE_EXPAND_FILL - stack.add_theme_constant_override("separation", -3) - var label := _field_label(label_text, 12) - label.add_theme_color_override("font_color", MUTED_INK) - stack.add_child(label) - var button := NOTEPAD_INK_ACTION_SCENE.instantiate() as Button - button.name = "CatalogIndex%s" % String(key).capitalize().replace(" ", "") - button.custom_minimum_size = Vector2(0.0, INDEX_OPTION_HEIGHT) - button.size_flags_horizontal = Control.SIZE_EXPAND_FILL - button.clip_text = true - _apply_index_value_font(button) - button.tooltip_text = "Change %s; use left or right on a controller" % label_text - button.pressed.connect(_cycle_index_option.bind(key, 1)) - button.gui_input.connect(_on_index_option_gui_input.bind(key)) - stack.add_child(button) - _index_option_buttons[key] = button - _index_controls.append(button) - return stack - - -func _apply_index_value_font(button: Button) -> void: - button.add_theme_font_override( - "font", InventoryNotepadType.HANDWRITTEN_FONT - ) - button.add_theme_font_size_override("font_size", 18) - for color_name: StringName in [ - &"font_color", - &"font_hover_color", - &"font_focus_color", - &"font_pressed_color", - ]: - button.add_theme_color_override(color_name, INK) - - -func _index_search_style(focused: bool) -> StyleBoxFlat: - var style := StyleBoxFlat.new() - style.bg_color = Color(1.0, 0.97, 0.82, 0.12 if focused else 0.0) - style.border_color = Color(INK, 0.72 if focused else 0.42) - style.border_width_bottom = 2 - style.content_margin_left = 6 - style.content_margin_right = 22 - style.content_margin_top = 2 - style.content_margin_bottom = 3 - return style - - -func _configure_index_focus() -> void: - var controls: Array[Control] = [] - for control: Control in _index_controls: - if control != null and is_instance_valid(control): - controls.append(control) - if not controls.is_empty(): - ControllerFocusNavigationType.configure_spatial_neighbors(controls) - - -func _apply_index_presentation() -> void: - if _index_body == null: - return - var scale_value := 0.82 if _compact_presentation else PAGE_CONTENT_SCALE - _index_body.scale = Vector2.ONE * scale_value - - func _apply_presentation_margins() -> void: if _outer_margin == null: return @@ -886,30 +548,11 @@ func _refresh_catalog() -> void: var preserved_entry_key: StringName = _selected_entry_key _clear_entries() var species: Array[FishDataType] = [] - var category_total: int = 0 if _catalog != null: - for fish: FishDataType in LogbookCatalog.ordered_species( - _catalog.candidates - ): - if LogbookCatalog.category_for(fish) == _category: - category_total += 1 - var available_now_resolver := Callable() - if _fishing_spot != null and is_instance_valid(_fishing_spot): - available_now_resolver = Callable( - _fishing_spot, - "is_species_available_now", - ) - species = LogbookCatalog.filtered_species( - _catalog.candidates, - _category, - _collection_log, - _inventory, - _index_options, - available_now_resolver, - _current_season(), - _current_is_night(), - ) + species = LogbookCatalog.ordered_species(_catalog.candidates) for fish: FishDataType in species: + if LogbookCatalog.category_for(fish) != _category: + continue var discovered: bool = _collection_log.has_discovered(fish.id) var entry: Button = _make_entry(fish, discovered) var selection_key: StringName = ( @@ -929,11 +572,7 @@ func _refresh_catalog() -> void: ) ) _catalog_grid.add_child(entry) - _empty_state.text = ( - "No entries match this index." - if _index_has_active_criteria() - else LogbookCatalog.empty_state(_category) - ) + _empty_state.text = LogbookCatalog.empty_state(_category) _empty_state.visible = _entry_buttons.is_empty() _catalog_scroll.visible = not _entry_buttons.is_empty() if ( @@ -950,269 +589,10 @@ func _refresh_catalog() -> void: _selected_entry_key = StringName() _refresh_selection_styles() _refresh_details(false) - _update_index_controls(species.size(), category_total) set_interactive(_interactive) _refresh_catalog_scroll_indicators.call_deferred() -func _toggle_index_page() -> void: - _set_index_open(not _index_open) - - -func _set_index_open(open: bool, restore_focus: bool = true) -> void: - _index_open = open - if _index_tab != null: - _index_tab.set_selected(open) - _index_tab.tooltip_text = ( - "Return to catch record" if open else "Open catalog index" - ) - if _index_body != null: - _index_body.visible = open - if _detail_body != null: - _detail_body.visible = not open - if not open: - _refresh_details(false) - _update_index_controls() - if restore_focus and _active and _interactive: - if open: - _controller_zone = ControllerZone.INDEX - set_interactive(true) - _index_search.grab_focus() - elif _controller_zone == ControllerZone.INDEX: - _controller_zone = ControllerZone.TABS - set_interactive(true) - _index_tab.grab_focus() - - -func _on_index_search_changed(value: String) -> void: - if _index_update_in_progress: - return - _index_options["search"] = value - _refresh_catalog() - - -func _cycle_index_option(key: StringName, direction: int = 1) -> void: - if not _index_options.has(key): - return - if key == &"descending": - _index_options[key] = not bool(_index_options[key]) - elif key == &"group": - var groups: Array[String] = [""] - if _catalog != null: - groups.append_array(LogbookCatalog.available_groups( - _catalog.candidates, - _category, - _collection_log, - )) - var current_index := groups.find(str(_index_options[key])) - if current_index < 0: - current_index = 0 - _index_options[key] = groups[wrapi( - current_index + direction, - 0, - groups.size(), - )] - else: - var option_count := _index_option_count(key) - if option_count <= 0: - return - _index_options[key] = wrapi( - int(_index_options[key]) + direction, - 0, - option_count, - ) - _refresh_catalog() - - -func _on_index_option_gui_input( - event: InputEvent, - key: StringName, -) -> void: - var mouse_event := event as InputEventMouseButton - if mouse_event == null or not mouse_event.pressed: - return - if mouse_event.button_index in [MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_WHEEL_UP]: - _cycle_index_option(key, -1) - get_viewport().set_input_as_handled() - elif mouse_event.button_index == MOUSE_BUTTON_WHEEL_DOWN: - _cycle_index_option(key, 1) - get_viewport().set_input_as_handled() - - -func _index_key_for_control(control: Control) -> StringName: - for key: StringName in _index_option_buttons: - if _index_option_buttons[key] == control: - return key - return StringName() - - -func _reset_index() -> void: - _index_options = { - "search": "", - "show": LogbookCatalog.DiscoveryFilter.ALL, - "sort": LogbookCatalog.SortMode.CATALOG_NUMBER, - "descending": false, - "availability": LogbookCatalog.AvailabilityFilter.ANY, - "season": LogbookCatalog.SeasonFilter.ANY, - "time": LogbookCatalog.TimeFilter.ANY, - "rarity": LogbookCatalog.RarityFilter.ANY, - "method": LogbookCatalog.MethodFilter.ANY, - "holding": LogbookCatalog.HoldingFilter.ANY, - "group": "", - } - _index_update_in_progress = true - _index_search.text = "" - _index_update_in_progress = false - _refresh_catalog() - - -func _update_index_controls( - visible_count: int = -1, - category_total: int = -1, -) -> void: - if visible_count >= 0: - _index_visible_count = visible_count - if category_total >= 0: - _index_category_total = category_total - if _index_summary != null: - _index_summary.text = "%d shown of %d" % [ - _index_visible_count, - _index_category_total, - ] - for key: StringName in _index_option_buttons: - var button := _index_option_buttons[key] - if button == null: - continue - button.text = _index_option_label(key) - button.accessibility_name = "%s: %s" % [ - String(key).replace("_", " "), - button.text, - ] - if _index_tab != null: - var marked := _index_has_active_criteria() - _index_tab.text = "index •" if marked else "index" - _index_tab.accessibility_name = ( - "Catalog index, filters active" if marked else "Catalog index" - ) - if _index_reset != null: - _index_reset.disabled = not _index_has_active_criteria() - - -func _index_option_label(key: StringName) -> String: - match key: - &"show": - return INDEX_SHOW_LABELS[int(_index_options[key])] - &"sort": - return INDEX_SORT_LABELS[int(_index_options[key])] - &"availability": - return INDEX_AVAILABILITY_LABELS[int(_index_options[key])] - &"season": - return INDEX_SEASON_LABELS[int(_index_options[key])] - &"time": - return INDEX_TIME_LABELS[int(_index_options[key])] - &"rarity": - return INDEX_RARITY_LABELS[int(_index_options[key])] - &"method": - return INDEX_METHOD_LABELS[int(_index_options[key])] - &"holding": - return INDEX_HOLDING_LABELS[int(_index_options[key])] - &"group": - var group := str(_index_options[key]) - return group if not group.is_empty() else "any group" - &"descending": - var descending := bool(_index_options[key]) - match int(_index_options["sort"]): - LogbookCatalog.SortMode.NAME: - return "z–a" if descending else "a–z" - LogbookCatalog.SortMode.CATALOG_NUMBER: - return "high–low" if descending else "low–high" - LogbookCatalog.SortMode.RARITY: - return ( - "rare–common" if descending else "common–rare" - ) - LogbookCatalog.SortMode.VALUE, LogbookCatalog.SortMode.WEIGHT: - return "high–low" if descending else "low–high" - _: - return "most first" if descending else "least first" - return "any" - - -func _index_option_count(key: StringName) -> int: - match key: - &"show": - return INDEX_SHOW_LABELS.size() - &"sort": - return INDEX_SORT_LABELS.size() - &"availability": - return INDEX_AVAILABILITY_LABELS.size() - &"season": - return INDEX_SEASON_LABELS.size() - &"time": - return INDEX_TIME_LABELS.size() - &"rarity": - return INDEX_RARITY_LABELS.size() - &"method": - return INDEX_METHOD_LABELS.size() - &"holding": - return INDEX_HOLDING_LABELS.size() - return 0 - - -func _index_has_active_criteria() -> bool: - return ( - not str(_index_options["search"]).is_empty() - or int(_index_options["show"]) - != LogbookCatalog.DiscoveryFilter.ALL - or int(_index_options["sort"]) - != LogbookCatalog.SortMode.CATALOG_NUMBER - or bool(_index_options["descending"]) - or int(_index_options["availability"]) - != LogbookCatalog.AvailabilityFilter.ANY - or int(_index_options["season"]) - != LogbookCatalog.SeasonFilter.ANY - or int(_index_options["time"]) - != LogbookCatalog.TimeFilter.ANY - or int(_index_options["rarity"]) - != LogbookCatalog.RarityFilter.ANY - or int(_index_options["method"]) - != LogbookCatalog.MethodFilter.ANY - or int(_index_options["holding"]) - != LogbookCatalog.HoldingFilter.ANY - or not str(_index_options["group"]).is_empty() - ) - - -func _current_season() -> int: - return ( - _world_time.get_season() - if _world_time != null - else CalendarSeason.UNKNOWN - ) - - -func _current_is_night() -> bool: - return _world_time != null and _world_time.is_night_period() - - -func notify_availability_context_changed() -> void: - _on_logbook_context_changed() - - -func _on_logbook_context_changed( - _first: Variant = null, - _second: Variant = null, -) -> void: - if ( - int(_index_options["availability"]) - == LogbookCatalog.AvailabilityFilter.AVAILABLE_NOW - or int(_index_options["season"]) - == LogbookCatalog.SeasonFilter.CURRENT - or int(_index_options["time"]) - == LogbookCatalog.TimeFilter.CURRENT - ): - _refresh_catalog() - - func _make_entry(fish: FishDataType, discovered: bool) -> Button: var entry := Button.new() entry.custom_minimum_size = CATALOG_ENTRY_SIZE @@ -1283,11 +663,8 @@ func _add_entry_content( func _select_category(category: LogbookCatalog.Category) -> void: if category == _category: - if _index_open: - _set_index_open(false) return _category = category - _set_index_open(false, false) _selected_id = StringName() _selected_entry_key = StringName() for index: int in _category_tabs.size(): @@ -1306,11 +683,6 @@ func _configure_category_focus() -> void: tab.focus_neighbor_right = tab.get_path_to( _category_tabs[mini(index + 1, _category_tabs.size() - 1)] ) - if not _category_tabs.is_empty() and _index_tab != null: - var final_tab: Button = _category_tabs.back() - final_tab.focus_neighbor_right = final_tab.get_path_to(_index_tab) - _index_tab.focus_neighbor_left = _index_tab.get_path_to(final_tab) - _index_tab.focus_neighbor_right = _index_tab.get_path_to(_index_tab) func _animate_tab_entry() -> void: @@ -1318,15 +690,11 @@ func _animate_tab_entry() -> void: (_category_tabs[index] as OrganizerTabType).animate_entrance( float(index) * 0.025 ) - if _index_tab != null: - _index_tab.animate_entrance(float(_category_tabs.size()) * 0.025) func _settle_tabs_for_close() -> void: for tab: OrganizerTabType in _category_tabs: tab.settle_for_close() - if _index_tab != null: - _index_tab.settle_for_close() func _begin_category_transition() -> void: @@ -1382,12 +750,9 @@ func _set_category_content_alpha(alpha: float) -> void: _empty_state.modulate.a = alpha if _detail_body != null: _detail_body.modulate.a = alpha - if _index_body != null: - _index_body.modulate.a = alpha func _select_entry(entry_key: StringName, fish_id: StringName) -> void: - _set_index_open(false, false) if fish_id.is_empty(): _selected_id = StringName() _selected_entry_key = entry_key @@ -1919,9 +1284,7 @@ func _on_collection_changed() -> void: func _on_inventory_changed() -> void: - # Ownership filters and favorite-backed views depend on the live cooler, - # while rebuilding the catalog also keeps the selected detail current. - _refresh_catalog() + _refresh_details(false) func _unknown_selection_key(fish: FishDataType) -> StringName: diff --git a/ui/player_menu.gd b/ui/player_menu.gd index 2f9f96f..3c5dc77 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -105,7 +105,6 @@ signal controller_hotbar_management_requested(initial_slot: int) signal controller_hotbar_management_ended signal menu_exit_started signal shop_cooler_modal_changed(is_open: bool) -signal controller_text_entry_requested(control: Control) enum Section { COOLER, @@ -410,9 +409,6 @@ func _ready() -> void: _logbook_tab.pressed.connect( _show_section.bind(Section.LOGBOOK) ) - _catalog_logbook.controller_text_entry_requested.connect( - controller_text_entry_requested.emit - ) _the_net_tab.pressed.connect(_show_section.bind(Section.NET)) _mail_tab.pressed.connect(_show_section.bind(Section.MAIL)) _profile_tab.pressed.connect(_show_section.bind(Section.PROFILE)) @@ -560,7 +556,6 @@ func setup( discovery: DiscoveryClient, player_jobs: PlayerJobService, world_time: WorldTimeService, - world_weather: WorldWeatherService, world_environment: WorldEnvironment, world_sun: DirectionalLight3D, ) -> void: @@ -594,14 +589,7 @@ func setup( world_sun, ) _players_page.setup(network_player_list, discovery) - _catalog_logbook.setup( - collection_log, - inventory, - catalog, - fishing_spot, - world_time, - world_weather, - ) + _catalog_logbook.setup(collection_log, inventory, catalog) _the_net_page.setup(player_jobs, world_time) _network_mail_service.unread_count_changed.connect( _on_mail_unread_count_changed @@ -2791,7 +2779,6 @@ func _toggle_active_tackle() -> void: func _on_active_bait_changed(_item_id: StringName) -> void: _update_tackle_detail() - _catalog_logbook.notify_availability_context_changed() func _on_active_lure_changed(_item_id: StringName) -> void: @@ -3746,7 +3733,6 @@ func _refresh_all() -> void: func _on_bag_changed() -> void: _refresh_bag() _refresh_tackle_box() - _catalog_logbook.notify_availability_context_changed() func _on_hotbar_changed() -> void: diff --git a/world/generation/generated_world_region.gd b/world/generation/generated_world_region.gd index 780ddb8..aa27a65 100644 --- a/world/generation/generated_world_region.gd +++ b/world/generation/generated_world_region.gd @@ -43,12 +43,6 @@ const PROP_MINIMUM_GROUND_CLEARANCE := 0.05 const PROP_MAXIMUM_SUPPORT_HEIGHT_DIFFERENCE := 0.35 const PROP_CHANCE_SCALE := 10000 const PROP_SELECTION_WEIGHT_SCALE := 1000 -const WATER_RECOVERY_MINIMUM_GROUND_CLEARANCE := 0.05 -const WATER_RECOVERY_MINIMUM_UP_DOT := 0.6 -const WATER_RECOVERY_FOOTPRINT_RADIUS := 0.6 -const WATER_RECOVERY_INSET_STEP := 0.25 -const WATER_RECOVERY_MAXIMUM_INSET := 1.5 -const WATER_RECOVERY_SEARCH_EXPANSIONS: Array[float] = [1.5, 4.0, 12.0] const PROP_SURFACE_SAMPLE_DIRECTIONS: Array[Vector2] = [ Vector2(1.0, 0.0), Vector2(0.70710678, 0.70710678), @@ -96,7 +90,6 @@ var _placed_prop_clearance_radii: Array[float] = [] var _placed_prop_groups: Array[StringName] = [] var _placed_group_coordinates: Dictionary[StringName, Array] = {} var _biome_assignments: Dictionary[Vector2i, StringName] = {} -var _water_recovery_triangles_by_coordinate: Dictionary[Vector2i, Array] = {} func _ready() -> void: @@ -197,37 +190,6 @@ func get_spawn_surface_triangles( return triangles -func get_water_recovery_position( - entry_position: Vector3, - fallback_position: Vector3, -) -> Vector3: - if ( - not entry_position.is_finite() - or _water_recovery_triangles_by_coordinate.is_empty() - ): - return super(entry_position, fallback_position) - var nearest_surface_distance := _nearest_recovery_surface_distance( - entry_position - ) - if not is_finite(nearest_surface_distance): - return super(entry_position, fallback_position) - for expansion: float in WATER_RECOVERY_SEARCH_EXPANSIONS: - var maximum_distance := nearest_surface_distance + expansion - var result: Variant = _nearest_safe_recovery_surface_position( - entry_position, - maximum_distance, - ) - if result is Vector3: - return result as Vector3 - var map_wide_result: Variant = _nearest_safe_recovery_surface_position( - entry_position, - get_playable_half_extents().length() * 2.0, - ) - if map_wide_result is Vector3: - return map_wide_result as Vector3 - return super(entry_position, fallback_position) - - func _on_generation_completed(summary: Dictionary) -> void: _diggable_beach.invalidate_surface_cache() var records: Array[Dictionary] = _generator.placement_records() @@ -363,10 +325,6 @@ func _on_generation_completed(summary: Dictionary) -> void: grass_surface_triangles_by_coordinate, sand_surface_triangles_by_coordinate, ) - _cache_water_recovery_surfaces( - grass_surface_triangles_by_coordinate, - sand_surface_triangles_by_coordinate, - ) _place_spawn_amenities(spawn_position) _configure_fresh_water(records) _configure_diggable_area() @@ -1303,203 +1261,6 @@ func _terrain_surface_triangles_by_coordinate( return triangles_by_coordinate -func _cache_water_recovery_surfaces( - grass_triangles: Dictionary[Vector2i, Array], - sand_triangles: Dictionary[Vector2i, Array], -) -> void: - _water_recovery_triangles_by_coordinate.clear() - _append_water_recovery_surfaces(grass_triangles) - _append_water_recovery_surfaces(sand_triangles) - - -func _append_water_recovery_surfaces( - source: Dictionary[Vector2i, Array], -) -> void: - for coordinate: Vector2i in source: - var eligible: Array = _water_recovery_triangles_by_coordinate.get( - coordinate, - [], - ) - for triangle: PackedVector3Array in source[coordinate]: - if triangle.size() != 3: - continue - if minf( - triangle[0].y, - minf(triangle[1].y, triangle[2].y), - ) <= WATER_HEIGHT + WATER_RECOVERY_MINIMUM_GROUND_CLEARANCE: - continue - var cross := (triangle[1] - triangle[0]).cross( - triangle[2] - triangle[0] - ) - if ( - cross.length_squared() <= 0.0000001 - or absf(cross.normalized().dot(Vector3.UP)) - < WATER_RECOVERY_MINIMUM_UP_DOT - ): - continue - eligible.append(triangle) - if not eligible.is_empty(): - _water_recovery_triangles_by_coordinate[coordinate] = eligible - - -func _nearest_recovery_surface_distance(entry_position: Vector3) -> float: - var entry_horizontal := Vector2(entry_position.x, entry_position.z) - var nearest_distance_squared := INF - for coordinate: Vector2i in _water_recovery_triangles_by_coordinate: - for triangle: PackedVector3Array in ( - _water_recovery_triangles_by_coordinate[coordinate] - ): - var closest := _closest_horizontal_point_on_triangle( - entry_horizontal, - triangle, - ) - nearest_distance_squared = minf( - nearest_distance_squared, - entry_horizontal.distance_squared_to(closest), - ) - return sqrt(nearest_distance_squared) - - -func _nearest_safe_recovery_surface_position( - entry_position: Vector3, - maximum_distance: float, -) -> Variant: - var entry_horizontal := Vector2(entry_position.x, entry_position.z) - var maximum_distance_squared := maximum_distance * maximum_distance - var best_distance_squared := INF - var best_position: Variant = null - for coordinate: Vector2i in _water_recovery_triangles_by_coordinate: - for triangle: PackedVector3Array in ( - _water_recovery_triangles_by_coordinate[coordinate] - ): - var closest := _closest_horizontal_point_on_triangle( - entry_horizontal, - triangle, - ) - if ( - entry_horizontal.distance_squared_to(closest) - > maximum_distance_squared - ): - continue - var centroid := Vector2( - (triangle[0].x + triangle[1].x + triangle[2].x) / 3.0, - (triangle[0].z + triangle[1].z + triangle[2].z) / 3.0, - ) - var inset_distance := minf( - closest.distance_to(centroid), - WATER_RECOVERY_MAXIMUM_INSET, - ) - var inset_steps := ceili( - inset_distance / WATER_RECOVERY_INSET_STEP - ) - for inset_index: int in inset_steps + 1: - var candidate_horizontal := closest.move_toward( - centroid, - minf( - float(inset_index) * WATER_RECOVERY_INSET_STEP, - inset_distance, - ), - ) - var distance_squared := entry_horizontal.distance_squared_to( - candidate_horizontal - ) - if ( - distance_squared > maximum_distance_squared - or distance_squared >= best_distance_squared - ): - continue - var candidate := Vector3( - candidate_horizontal.x, - entry_position.y, - candidate_horizontal.y, - ) - var safe_position: Variant = _safe_recovery_surface_position( - candidate - ) - if safe_position is Vector3: - best_distance_squared = distance_squared - best_position = safe_position - return best_position - - -func _safe_recovery_surface_position(candidate: Vector3) -> Variant: - var nearby_triangles := _water_recovery_surface_triangles_near(candidate) - var surface_height := _surface_height_at(candidate, nearby_triangles) - if ( - surface_height <= WATER_HEIGHT + WATER_RECOVERY_MINIMUM_GROUND_CLEARANCE - or not _surface_supports_prop_footprint( - candidate, - surface_height, - WATER_RECOVERY_FOOTPRINT_RADIUS, - nearby_triangles, - ) - or not _has_prop_clearance( - candidate, - WATER_RECOVERY_FOOTPRINT_RADIUS, - ) - ): - return null - candidate.y = surface_height - return candidate - - -func _water_recovery_surface_triangles_near( - position: Vector3, -) -> Array[PackedVector3Array]: - var result: Array[PackedVector3Array] = [] - if _generator.catalog == null or _generator.catalog.chunk_size <= 0.0: - return result - var local_position := _generator.to_local(position) - var half_grid := Vector2( - float(_generator.grid_size.x - 1) * 0.5, - float(_generator.grid_size.y - 1) * 0.5, - ) - var coordinate := Vector2i( - roundi(local_position.x / _generator.catalog.chunk_size + half_grid.x), - roundi(local_position.z / _generator.catalog.chunk_size + half_grid.y), - ) - for offset_x: int in range(-1, 2): - for offset_y: int in range(-1, 2): - var nearby_coordinate := coordinate + Vector2i(offset_x, offset_y) - result.append_array( - _water_recovery_triangles_by_coordinate.get( - nearby_coordinate, - [], - ) - ) - return result - - -func _closest_horizontal_point_on_triangle( - point: Vector2, - triangle: PackedVector3Array, -) -> Vector2: - var polygon := PackedVector2Array([ - Vector2(triangle[0].x, triangle[0].z), - Vector2(triangle[1].x, triangle[1].z), - Vector2(triangle[2].x, triangle[2].z), - ]) - if Geometry2D.is_point_in_polygon(point, polygon): - return point - var closest := Geometry2D.get_closest_point_to_segment( - point, - polygon[0], - polygon[1], - ) - var nearest_distance_squared := point.distance_squared_to(closest) - for edge_index: int in range(1, 3): - var edge_closest := Geometry2D.get_closest_point_to_segment( - point, - polygon[edge_index], - polygon[(edge_index + 1) % 3], - ) - var distance_squared := point.distance_squared_to(edge_closest) - if distance_squared < nearest_distance_squared: - nearest_distance_squared = distance_squared - closest = edge_closest - return closest - - func _terrain_coordinate_for(mesh_instance: MeshInstance3D) -> Vector2i: var current: Node = mesh_instance while current != null and current != _generator: diff --git a/world/regions/world_region.gd b/world/regions/world_region.gd index 792e4ed..a0730e4 100644 --- a/world/regions/world_region.gd +++ b/world/regions/world_region.gd @@ -72,24 +72,6 @@ func get_safe_respawn_points() -> Array[SafeRespawnPoint]: return points -func get_water_recovery_position( - entry_position: Vector3, - fallback_position: Vector3, -) -> Vector3: - if not entry_position.is_finite(): - return fallback_position - var target_position := fallback_position - var nearest_distance := INF - for point: SafeRespawnPoint in get_safe_respawn_points(): - if point == null or not is_instance_valid(point) or not point.enabled: - continue - var distance := point.get_horizontal_distance_squared(entry_position) - if distance < nearest_distance: - nearest_distance = distance - target_position = point.global_position - return target_position - - func get_diggable_areas() -> Array[DiggableArea3D]: var areas: Array[DiggableArea3D] = [] var root: Node = get_node_or_null(diggable_area_root) diff --git a/world/test_world.gd b/world/test_world.gd index 9f4da5e..d9d18dc 100644 --- a/world/test_world.gd +++ b/world/test_world.gd @@ -49,16 +49,6 @@ func get_safe_respawn_points() -> Array[SafeRespawnPoint]: ) -func get_water_recovery_position(entry_position: Vector3) -> Vector3: - if _active_region == null: - return global_position - var fallback_position := get_player_spawn_transform().origin - return _active_region.get_water_recovery_position( - entry_position, - fallback_position, - ) - - func get_fishing_shop() -> FishingShopInteractionType: return _active_region.get_fishing_shop() diff --git a/world/water_recovery_controller.gd b/world/water_recovery_controller.gd index 0d0c5aa..9626881 100644 --- a/world/water_recovery_controller.gd +++ b/world/water_recovery_controller.gd @@ -25,7 +25,6 @@ var _game_ui: GameUI var _screen_fade: ScreenFade var _water_triggers: Array[PlayerWaterTrigger] = [] var _safe_points: Array[SafeRespawnPoint] = [] -var _recovery_position_resolver := Callable() var _initial_spawn_transform: Transform3D var _entry_position: Vector3 var _bob_base_position: Vector3 @@ -44,7 +43,6 @@ func setup( screen_fade: ScreenFade, water_triggers: Array[PlayerWaterTrigger], safe_points: Array[SafeRespawnPoint], - recovery_position_resolver: Callable = Callable(), ) -> void: _player = player _fishing_spot = fishing_spot @@ -52,7 +50,6 @@ func setup( _screen_fade = screen_fade _water_triggers = water_triggers _safe_points = safe_points - _recovery_position_resolver = recovery_position_resolver _initial_spawn_transform = player.global_transform for water_trigger: PlayerWaterTrigger in _water_triggers: if ( @@ -76,7 +73,6 @@ func update_world_context( initial_spawn_transform: Transform3D, water_triggers: Array[PlayerWaterTrigger], safe_points: Array[SafeRespawnPoint], - recovery_position_resolver: Callable = Callable(), ) -> void: for water_trigger: PlayerWaterTrigger in _water_triggers: if ( @@ -92,7 +88,6 @@ func update_world_context( _initial_spawn_transform = initial_spawn_transform _water_triggers = water_triggers _safe_points = safe_points - _recovery_position_resolver = recovery_position_resolver for water_trigger: PlayerWaterTrigger in _water_triggers: if ( water_trigger != null @@ -188,7 +183,17 @@ func _on_fade_transition_completed( func _respawn_player() -> void: - var target_position := _resolve_recovery_position() + var target_position: Vector3 = _initial_spawn_transform.origin + var nearest_distance: float = INF + for point: SafeRespawnPoint in _safe_points: + if point == null or not is_instance_valid(point) or not point.enabled: + continue + var distance: float = point.get_horizontal_distance_squared( + _entry_position + ) + if distance < nearest_distance: + nearest_distance = distance + target_position = point.global_position target_position.y += respawn_height_offset var respawn_transform: Transform3D = _player.global_transform respawn_transform.basis = _recovery_root_basis @@ -199,23 +204,6 @@ func _respawn_player() -> void: local_respawn_completed.emit(_entry_position) -func _resolve_recovery_position() -> Vector3: - if _recovery_position_resolver.is_valid(): - var resolved: Variant = _recovery_position_resolver.call(_entry_position) - if resolved is Vector3 and (resolved as Vector3).is_finite(): - return resolved as Vector3 - var target_position := _initial_spawn_transform.origin - var nearest_distance := INF - for point: SafeRespawnPoint in _safe_points: - if point == null or not is_instance_valid(point) or not point.enabled: - continue - var distance := point.get_horizontal_distance_squared(_entry_position) - if distance < nearest_distance: - nearest_distance = distance - target_position = point.global_position - return target_position - - func _finish_recovery() -> void: _player.restore_gameplay_orientation_after_recovery() _player.set_water_recovery_active(false)