extends SceneTree const FishingSpotScene = preload("res://fishing/fishing_spot.tscn") const FishingSpotType = preload("res://fishing/fishing_spot.gd") const FishingPresentationType = preload( "res://fishing/fishing_presentation.gd" ) const RemoteFishingPresentationType = preload( "res://fishing/remote_fishing_presentation.gd" ) const PlayerScene = preload("res://player/player.tscn") const FishCatchType = preload("res://fish/fish_catch.gd") const FishingSurfaceResolverType = preload( "res://fishing/fishing_surface_resolver.gd" ) const FishingSurfaceSampleType = preload( "res://fishing/fishing_surface_sample.gd" ) const FishableWaterRegionType = preload( "res://world/fishable_water_region.gd" ) const FishPoolType = preload("res://fish/fish_pool.gd") const WaterBodyScene = preload("res://world/water_body.tscn") const StarterRegionScene = preload( "res://world/regions/starter_island_region.tscn" ) const PondPool: FishPoolType = preload( "res://fish/pools/starter_pond_pool.tres" ) func _initialize() -> void: call_deferred("_run") func _run() -> void: await _validate_resolver_layers() await _validate_portable_water_body() await _validate_starter_region_surfaces() await _validate_presentation() await _validate_remote_presentation() await _validate_bite_wait_distribution() print("Fishing surface validation: PASS") quit() func _validate_resolver_layers() -> void: var world := Node3D.new() root.add_child(world) _add_solid_box(world, Vector3(40.0, 2.0, 12.0), Vector3(10.0, -1.0, 0.0)) _add_water_region(world, Vector3(0.0, 2.0, 0.0), Vector2(8.0, 8.0), PondPool) _add_solid_box(world, Vector3(1.0, 1.0, 1.0), Vector3(2.0, 2.25, 0.0)) _add_water_region(world, Vector3(10.0, 5.0, 0.0), Vector2(6.0, 6.0), PondPool) _add_water_region(world, Vector3(20.0, 3.0, 0.0), Vector2(6.0, 6.0), null) await physics_frame await physics_frame var resolver := FishingSurfaceResolverType.new() var space_state: PhysicsDirectSpaceState3D = world.get_world_3d().direct_space_state var water: FishingSurfaceSampleType = resolver.resolve_surface( space_state, Vector3.ZERO, 4.0, ) assert(water.is_fishable()) assert(is_equal_approx(water.position.y, 2.0)) var covered_water: FishingSurfaceSampleType = resolver.resolve_surface( space_state, Vector3(2.0, 2.0, 0.0), 4.0, ) assert(covered_water.has_surface) assert(not covered_water.is_water_surface) assert(is_equal_approx(covered_water.position.y, 2.75)) var elevated_water: FishingSurfaceSampleType = resolver.resolve_surface( space_state, Vector3(10.0, 0.0, 0.0), 8.0, ) assert(elevated_water.is_fishable()) assert(is_equal_approx(elevated_water.position.y, 5.0)) var unstocked_water: FishingSurfaceSampleType = resolver.resolve_surface( space_state, Vector3(20.0, 0.0, 0.0), 6.0, ) assert(unstocked_water.has_surface) assert(unstocked_water.is_water_surface) assert(not unstocked_water.is_fishable()) assert(is_equal_approx(unstocked_water.position.y, 3.0)) var safe_reel: FishingSurfaceSampleType = resolver.resolve_withdrawal_surface( space_state, Vector3(0.0, 2.0, 0.0), Vector3(3.0, 2.0, 0.0), Vector3.RIGHT, 4.0, 0.4, ) assert(safe_reel.is_fishable()) assert(is_equal_approx(safe_reel.position.y, 2.0)) var shoreline_reel: FishingSurfaceSampleType = ( resolver.resolve_withdrawal_surface( space_state, Vector3(3.0, 2.0, 0.0), Vector3(3.7, 2.0, 0.0), Vector3.RIGHT, 4.0, 0.4, ) ) assert(not shoreline_reel.is_fishable()) assert(shoreline_reel.position.is_equal_approx(Vector3(3.0, 2.0, 0.0))) world.queue_free() await process_frame func _validate_portable_water_body() -> void: var water_body := WaterBodyScene.instantiate() as Node3D water_body.position = Vector3(3.0, 7.0, -2.0) water_body.set("surface_size", Vector2(12.0, 8.0)) water_body.set("fishing_depth", 6.0) water_body.set("recovery_depth", 7.0) water_body.set("fish_pool", PondPool) root.add_child(water_body) await process_frame var visual := water_body.get_node("VisualWater") as MeshInstance3D var visual_mesh := visual.mesh as PlaneMesh assert(visual_mesh.size.is_equal_approx(Vector2(12.0, 8.0))) var fishing_region := water_body.get_node( "FishingRegion" ) as FishableWaterRegionType var fishing_shape_node := water_body.get_node( "FishingRegion/Shape" ) as CollisionShape3D var fishing_shape := fishing_shape_node.shape as BoxShape3D assert(fishing_region.fish_pool == PondPool) assert(is_equal_approx(fishing_region.get_surface_height(), 7.0)) assert(is_equal_approx(fishing_shape_node.position.y, -3.0)) assert(fishing_shape.size.is_equal_approx(Vector3(12.0, 6.0, 8.0))) var recovery_region := water_body.get_node("RecoveryRegion") as Area3D assert(is_equal_approx(recovery_region.position.y, -3.5)) water_body.queue_free() await process_frame func _validate_starter_region_surfaces() -> void: var region := StarterRegionScene.instantiate() as Node3D root.add_child(region) var fishing_spot := FishingSpotScene.instantiate() as FishingSpotType root.add_child(fishing_spot) await physics_frame await physics_frame var pond: FishingSurfaceSampleType = fishing_spot.resolve_fishing_surface( Vector3(-9.4, 2.51, 2.1), 4.0, ) assert(pond.is_fishable()) assert(is_equal_approx(pond.position.y, 2.51)) var ocean: FishingSurfaceSampleType = fishing_spot.resolve_fishing_surface( Vector3(50.0, -0.45, 0.0), 4.0, ) assert(ocean.is_fishable()) assert(is_equal_approx(ocean.position.y, -0.45)) var covered_ocean: FishingSurfaceSampleType = ( fishing_spot.resolve_fishing_surface( Vector3(20.0, -0.45, 0.0), 4.0, ) ) assert(covered_ocean.has_surface) assert(not covered_ocean.is_fishable()) assert(covered_ocean.position.y > -0.45) fishing_spot.queue_free() region.queue_free() await process_frame func _validate_presentation() -> void: var fishing_spot := FishingSpotScene.instantiate() as FishingSpotType root.add_child(fishing_spot) var presentation := fishing_spot.get_node( "FishingPresentation" ) as FishingPresentationType var rod := Node3D.new() var rod_tip := Marker3D.new() rod.add_child(rod_tip) root.add_child(rod) rod_tip.position = Vector3(0.0, 1.0, 0.0) await process_frame var slope_normal := Vector3(0.0, 1.0, 1.0).normalized() presentation.begin_aim( rod_tip, rod, Vector3(0.0, 0.1, -1.0), slope_normal, false, ) var marker := presentation.get_node("CastTargetMarker") as Node3D var valid_marker := presentation.get_node( "CastTargetMarker/ValidTargetMarker" ) as MeshInstance3D var invalid_marker := presentation.get_node( "CastTargetMarker/InvalidTargetMarker" ) as Node3D assert(not valid_marker.visible) assert(invalid_marker.visible) assert(invalid_marker.get_child_count() == 2) assert(marker.global_basis.y.normalized().dot(slope_normal) > 0.999) var landing := Vector3(0.0, 0.13, -4.0) presentation.begin_cast(landing, landing, true) await presentation.cast_completed var bobber := presentation.get_node("Bobber") as MeshInstance3D assert(bobber.visible) assert(bobber.global_position.is_equal_approx(landing)) presentation.play_outcome(&"invalid") await process_frame assert(bobber.visible) await presentation.outcome_completed assert(not bobber.visible) rod.queue_free() fishing_spot.queue_free() await process_frame func _validate_bite_wait_distribution() -> void: var fishing_spot := FishingSpotScene.instantiate() as FishingSpotType root.add_child(fishing_spot) await process_frame var bite_rng := fishing_spot.get("_bite_rng") as RandomNumberGenerator bite_rng.seed = 84219 var quick_count: int = 0 var typical_or_long_count: int = 0 var very_long_count: int = 0 var total_wait_seconds: float = 0.0 for _sample_index: int in 10000: var wait_seconds: float = fishing_spot.roll_bite_wait_time() assert(wait_seconds >= 10.0) assert(wait_seconds <= 240.0) total_wait_seconds += wait_seconds if wait_seconds < 30.0: quick_count += 1 else: typical_or_long_count += 1 if wait_seconds >= 180.0: very_long_count += 1 var average_wait_seconds: float = total_wait_seconds / 10000.0 assert(quick_count >= 2300 and quick_count <= 2700) assert(typical_or_long_count > quick_count) assert(very_long_count >= 100 and very_long_count <= 300) assert(average_wait_seconds >= 57.0) assert(average_wait_seconds <= 61.0) fishing_spot.queue_free() await process_frame func _validate_remote_presentation() -> void: var player := PlayerScene.instantiate() as Player root.add_child(player) player.set_local_control(false) var presentation := RemoteFishingPresentationType.new() root.add_child(presentation) presentation.setup(player) await process_frame var origin: Vector3 = player.get_fishing_rod_tip().global_position var target: Vector3 = origin + Vector3(-4.0, -0.5, 0.0) presentation.show_cast(origin, target) var bobber := presentation.get("_bobber") as MeshInstance3D assert(bobber.visible) assert(presentation.get("_cast_tween") != null) await create_timer(0.7).timeout assert(bobber.global_position.distance_to(target) < 0.1) var first_bob_y: float = bobber.global_position.y await create_timer(0.2).timeout assert(not is_equal_approx(bobber.global_position.y, first_bob_y)) var fish_catch := FishCatchType.new() var fish: FishData = PondPool.candidates.front() fish_catch.fish = fish fish_catch.fish_id = fish.id fish_catch.catch_id = &"remote-presentation-test" fish_catch.weight_lb = 1.0 fish_catch.display_scale = 1.0 fish_catch.sale_value = 1 assert(fish_catch.is_valid()) presentation.play_return(fish_catch) await create_timer(0.6).timeout var catch_display := player.get_node("%CatchDisplay") as Node3D assert(catch_display.visible) await presentation.return_completed assert(not catch_display.visible) presentation.queue_free() player.queue_free() await process_frame func _add_water_region( parent: Node3D, position: Vector3, surface_size: Vector2, fish_pool: FishPoolType, ) -> void: var water_root := Node3D.new() water_root.position = position parent.add_child(water_root) var region := FishableWaterRegionType.new() region.collision_layer = 4 region.collision_mask = 0 region.monitoring = false region.surface_height_mode = FishableWaterRegionType.SurfaceHeightMode.PARENT_GLOBAL_Y region.fish_pool = fish_pool water_root.add_child(region) var shape_node := CollisionShape3D.new() shape_node.position.y = -2.0 var shape := BoxShape3D.new() shape.size = Vector3(surface_size.x, 4.0, surface_size.y) shape_node.shape = shape region.add_child(shape_node) func _add_solid_box( parent: Node3D, size: Vector3, position: Vector3, ) -> void: var body := StaticBody3D.new() body.position = position body.collision_layer = 1 body.collision_mask = 0 parent.add_child(body) var shape_node := CollisionShape3D.new() var shape := BoxShape3D.new() shape.size = size shape_node.shape = shape body.add_child(shape_node)