Separate generated freshwater fish habitats

This commit is contained in:
Alexander Sellite 2026-08-21 12:29:19 -04:00
parent 7242e5d433
commit 8656f46ce4
27 changed files with 177 additions and 36 deletions

View file

@ -0,0 +1,13 @@
[gd_resource type="Resource" load_steps=8 format=3]
[ext_resource type="Script" path="res://fish/fish_pool.gd" id="1_pool"]
[ext_resource type="Resource" path="res://fish/species/sturgeon_lake/sturgeon_lake.tres" id="2_sturgeon"]
[ext_resource type="Resource" path="res://fish/species/trout_golden/trout_golden.tres" id="3_trout"]
[ext_resource type="Resource" path="res://fish/species/chub_lake/chub_lake.tres" id="4_chub"]
[ext_resource type="Resource" path="res://fish/species/saugeye/saugeye.tres" id="5_saugeye"]
[ext_resource type="Resource" path="res://fish/species/walleye/walleye.tres" id="6_walleye"]
[ext_resource type="Resource" path="res://fish/species/goby_round/goby_round.tres" id="7_goby"]
[resource]
script = ExtResource("1_pool")
candidates = [ExtResource("2_sturgeon"), ExtResource("3_trout"), ExtResource("4_chub"), ExtResource("5_saugeye"), ExtResource("6_walleye"), ExtResource("7_goby")]

View file

@ -0,0 +1,16 @@
[gd_resource type="Resource" load_steps=11 format=3]
[ext_resource type="Script" path="res://fish/fish_pool.gd" id="1_pool"]
[ext_resource type="Resource" path="res://fish/species/bowfin/bowfin.tres" id="2_bowfin"]
[ext_resource type="Resource" path="res://fish/species/gar_spotted/gar_spotted.tres" id="3_gar_spotted"]
[ext_resource type="Resource" path="res://fish/species/lungfish_west_african/lungfish_west_african.tres" id="4_lungfish"]
[ext_resource type="Resource" path="res://fish/species/catfish_channel/catfish_channel.tres" id="5_catfish_channel"]
[ext_resource type="Resource" path="res://fish/species/catfish_white/catfish_white.tres" id="6_catfish_white"]
[ext_resource type="Resource" path="res://fish/species/carp/carp.tres" id="7_carp"]
[ext_resource type="Resource" path="res://fish/species/goldfish_bubbleeye/goldfish_bubbleeye.tres" id="8_bubbleeye"]
[ext_resource type="Resource" path="res://fish/species/goldfish/goldfish.tres" id="9_goldfish"]
[ext_resource type="Resource" path="res://fish/species/bluegill/bluegill.tres" id="10_bluegill"]
[resource]
script = ExtResource("1_pool")
candidates = [ExtResource("2_bowfin"), ExtResource("3_gar_spotted"), ExtResource("4_lungfish"), ExtResource("5_catfish_channel"), ExtResource("6_catfish_white"), ExtResource("7_carp"), ExtResource("8_bubbleeye"), ExtResource("9_goldfish"), ExtResource("10_bluegill")]

View file

@ -0,0 +1,18 @@
[gd_resource type="Resource" load_steps=13 format=3]
[ext_resource type="Script" path="res://fish/fish_pool.gd" id="1_pool"]
[ext_resource type="Resource" path="res://fish/species/gar_longnose/gar_longnose.tres" id="2_gar"]
[ext_resource type="Resource" path="res://fish/species/paddlefish/paddlefish.tres" id="3_paddlefish"]
[ext_resource type="Resource" path="res://fish/species/sturgeon_shovelnose/sturgeon_shovelnose.tres" id="4_sturgeon"]
[ext_resource type="Resource" path="res://fish/species/trout_cutthroat/trout_cutthroat.tres" id="5_cutthroat"]
[ext_resource type="Resource" path="res://fish/species/trout_rainbow/trout_rainbow.tres" id="6_rainbow"]
[ext_resource type="Resource" path="res://fish/species/catfish_blue/catfish_blue.tres" id="7_blue_catfish"]
[ext_resource type="Resource" path="res://fish/species/catfish_flathead/catfish_flathead.tres" id="8_flathead"]
[ext_resource type="Resource" path="res://fish/species/chub_european/chub_european.tres" id="9_chub_european"]
[ext_resource type="Resource" path="res://fish/species/chub_flame/chub_flame.tres" id="10_chub_flame"]
[ext_resource type="Resource" path="res://fish/species/sauger/sauger.tres" id="11_sauger"]
[ext_resource type="Resource" path="res://fish/species/trout_steelhead/trout_steelhead.tres" id="12_steelhead"]
[resource]
script = ExtResource("1_pool")
candidates = [ExtResource("2_gar"), ExtResource("3_paddlefish"), ExtResource("4_sturgeon"), ExtResource("5_cutthroat"), ExtResource("6_rainbow"), ExtResource("7_blue_catfish"), ExtResource("8_flathead"), ExtResource("9_chub_european"), ExtResource("10_chub_flame"), ExtResource("11_sauger"), ExtResource("12_steelhead")]

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="BlueCatfishAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"river"])
allow_day = false
preferred_bait_tags = Array[StringName]([&"worm", &"minnow"])
preferred_bait_weight_multiplier = 1.35

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="ChannelCatfishAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"starter_pond", &"pond"])
allow_day = false
preferred_bait_tags = Array[StringName]([&"worm"])
preferred_bait_weight_multiplier = 1.35

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="FlatheadCatfishAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"river"])
allow_day = false
preferred_bait_tags = Array[StringName]([&"worm", &"minnow"])
preferred_bait_weight_multiplier = 1.35

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="WhiteCatfishAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"starter_pond", &"pond"])
allow_day = false
preferred_bait_tags = Array[StringName]([&"worm"])
preferred_bait_weight_multiplier = 1.35

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="ChubEuropeanAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"river"])
[resource]
script = ExtResource("1_fish_data")

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="ChubFlameAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"river"])
[resource]
script = ExtResource("1_fish_data")

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="ChubLakeAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"lake"])
[resource]
script = ExtResource("1_fish_data")

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="GoldfishAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"starter_pond", &"pond"])
[resource]
script = ExtResource("1_fish_data")

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="GoldfishBubbleeyeAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"starter_pond", &"pond"])
[resource]
script = ExtResource("1_fish_data")

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="SaugerAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"river"])
[resource]
script = ExtResource("1_fish_data")

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="SaugeyeAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"lake"])
[resource]
script = ExtResource("1_fish_data")

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="TroutCutthroatAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"river"])
[resource]
script = ExtResource("1_fish_data")

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="TroutGoldenAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"lake"])
[resource]
script = ExtResource("1_fish_data")

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="TroutRainbowAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"river"])
[resource]
script = ExtResource("1_fish_data")

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="TroutSteelheadAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"river"])
[resource]
script = ExtResource("1_fish_data")

View file

@ -7,7 +7,7 @@
[sub_resource type="Resource" id="WalleyeAvailability"]
script = ExtResource("3_availability_script")
allowed_location_tags = Array[StringName]([&"starter_pond"])
allowed_location_tags = Array[StringName]([&"lake"])
[resource]
script = ExtResource("1_fish_data")

View file

@ -24,6 +24,15 @@ const PondPool: FishPoolType = preload(
const OceanPool: FishPoolType = preload(
"res://fish/pools/starter_ocean_pool.tres"
)
const GeneratedPondPool: FishPoolType = preload(
"res://fish/pools/generated_pond_pool.tres"
)
const GeneratedLakePool: FishPoolType = preload(
"res://fish/pools/generated_lake_pool.tres"
)
const GeneratedRiverPool: FishPoolType = preload(
"res://fish/pools/generated_river_pool.tres"
)
const PelicanBuyer = preload("res://economy/buyers/pelicans.tres")
const StarterRegionScene = preload(
"res://world/regions/starter_island_region.tscn"
@ -73,6 +82,21 @@ const NEW_FRESH_WATER_IDS: Array[StringName] = [
&"bowfin", &"sturgeon_lake", &"gar_longnose", &"paddlefish",
&"sturgeon_shovelnose", &"gar_spotted", &"lungfish_west_african",
]
const GENERATED_POND_IDS: Array[StringName] = [
&"bowfin", &"gar_spotted", &"lungfish_west_african",
&"catfish_channel", &"catfish_white", &"carp",
&"goldfish_bubbleeye", &"goldfish", &"bluegill",
]
const GENERATED_LAKE_IDS: Array[StringName] = [
&"sturgeon_lake", &"trout_golden", &"chub_lake", &"saugeye",
&"walleye", &"goby_round",
]
const GENERATED_RIVER_IDS: Array[StringName] = [
&"gar_longnose", &"paddlefish", &"sturgeon_shovelnose",
&"trout_cutthroat", &"trout_rainbow", &"catfish_blue",
&"catfish_flathead", &"chub_european", &"chub_flame", &"sauger",
&"trout_steelhead",
]
func _initialize() -> void:
@ -81,6 +105,7 @@ func _initialize() -> void:
func _run() -> void:
_validate_catalog_and_pools()
_validate_generated_habitat_pools()
_validate_weight_based_display_scale()
_validate_starter_water_bodies()
_validate_authoritative_water_filter()
@ -89,6 +114,28 @@ func _run() -> void:
quit()
func _validate_generated_habitat_pools() -> void:
var expected_by_pool: Dictionary = {
GeneratedPondPool: GENERATED_POND_IDS,
GeneratedLakePool: GENERATED_LAKE_IDS,
GeneratedRiverPool: GENERATED_RIVER_IDS,
}
for pool: FishPoolType in expected_by_pool:
var expected_ids: Array[StringName] = expected_by_pool[pool]
assert(pool.candidates.size() == expected_ids.size())
for fish_id: StringName in expected_ids:
var fish: FishDataType = Catalog.get_fish_by_id(fish_id)
assert(fish != null and fish.is_fishable())
assert(pool.get_fish_by_id(fish_id) == fish)
assert(fish.is_allowed_in_water(WaterType.Type.FRESH_WATER))
assert(GeneratedPondPool.get_fish_by_id(&"pomfret_white") == null)
assert(GeneratedPondPool.get_fish_by_id(&"mackerel_atlantic") == null)
assert(GeneratedPondPool.get_fish_by_id(&"gar_longnose") == null)
assert(GeneratedRiverPool.get_fish_by_id(&"gar_longnose") != null)
assert(GeneratedRiverPool.get_fish_by_id(&"goby_round") == null)
assert(GeneratedLakePool.get_fish_by_id(&"goby_round") != null)
func _validate_weight_based_display_scale() -> void:
var previous_scale: float = 0.0
for fish: FishDataType in Catalog.candidates:
@ -235,7 +282,12 @@ func _validate_catalog_and_pools() -> void:
assert(fish.is_selectable())
assert(PondPool.get_fish_by_id(fish_id) == fish)
assert(OceanPool.get_fish_by_id(fish_id) == null)
assert(fish.availability.allowed_location_tags == [&"starter_pond"])
var expected_tags: Array[StringName] = []
if fish_id in [&"catfish_channel", &"catfish_white"]:
expected_tags.assign([&"starter_pond", &"pond"])
else:
expected_tags.append(&"river")
assert(fish.availability.allowed_location_tags == expected_tags)
assert(
LogbookCatalog.category_for(fish)
== LogbookCatalog.Category.FRESH_WATER
@ -306,17 +358,25 @@ func _validate_catalog_and_pools() -> void:
var pond_context := FishingContextType.new()
pond_context.location_tags = [&"starter_pond"]
pond_context.water_type = WaterType.Type.FRESH_WATER
var night_pond_context := FishingContextType.new()
night_pond_context.location_tags = [&"starter_pond"]
night_pond_context.water_type = WaterType.Type.FRESH_WATER
night_pond_context.is_night = true
var ocean_context := FishingContextType.new()
ocean_context.location_tags = [&"coast", &"ocean"]
ocean_context.water_type = WaterType.Type.SALT_WATER
for fish_id: StringName in CATFISH_IDS:
var fish: FishDataType = Catalog.get_fish_by_id(fish_id)
assert(not fish.availability.is_available(pond_context))
assert(fish.availability.is_available(night_pond_context))
var day_habitat_context := FishingContextType.new()
if fish_id in [&"catfish_channel", &"catfish_white"]:
day_habitat_context.location_tags.append(&"pond")
else:
day_habitat_context.location_tags.append(&"river")
day_habitat_context.water_type = WaterType.Type.FRESH_WATER
var night_habitat_context := FishingContextType.new()
night_habitat_context.location_tags = (
day_habitat_context.location_tags.duplicate()
)
night_habitat_context.water_type = WaterType.Type.FRESH_WATER
night_habitat_context.is_night = true
assert(not fish.availability.is_available(day_habitat_context))
assert(fish.availability.is_available(night_habitat_context))
assert(not fish.availability.is_available(ocean_context))
var single_species_pool := FishPoolType.new()
single_species_pool.candidates = [fish]
@ -326,7 +386,7 @@ func _validate_catalog_and_pools() -> void:
selector.begin_roll()
assert(
selector.select_fish(
single_species_pool, night_pond_context, collection
single_species_pool, night_habitat_context, collection
) == fish
)
collection.free()

View file

@ -3,6 +3,15 @@ extends SceneTree
const RegionScene: PackedScene = preload(
"res://world/generation/generated_world_region.tscn"
)
const GeneratedPondPool: FishPool = preload(
"res://fish/pools/generated_pond_pool.tres"
)
const GeneratedLakePool: FishPool = preload(
"res://fish/pools/generated_lake_pool.tres"
)
const GeneratedRiverPool: FishPool = preload(
"res://fish/pools/generated_river_pool.tres"
)
const FIRST_SEED := 13001
const SECOND_SEED := 13004
const EXPECTED_PROP_IDS: Array[StringName] = [
@ -153,6 +162,13 @@ func _validate_generated_region(
assert(fresh.surface_size.x < 10.0 or fresh.surface_size.y < 10.0)
assert(not fresh.visual_surface_enabled)
assert(not (fresh.get_node("VisualWater") as MeshInstance3D).visible)
if &"river" in fresh.location_tags:
assert(fresh.fish_pool == GeneratedRiverPool)
elif &"lake" in fresh.location_tags:
assert(fresh.fish_pool == GeneratedLakePool)
else:
assert(&"pond" in fresh.location_tags)
assert(fresh.fish_pool == GeneratedPondPool)
_validate_authored_chunk_surfaces(region, generator)

View file

@ -291,11 +291,15 @@ func _validate_catalog() -> void:
and stream_variant.water_inlet_edges == stream.water_inlet_edges
and stream_variant.water_outlet_edges == stream.water_outlet_edges
and stream_variant.water_surface_size == stream.water_surface_size
and stream_variant.water_surface_offset
== stream.water_surface_offset,
and stream.water_surface_offset.x < 0.0
and stream_variant.water_surface_offset.x > 0.0
and is_equal_approx(
stream_variant.water_surface_offset.y,
stream.water_surface_offset.y,
),
(
"Both stream visuals must share one combined selection weight "
+ "and identical terrain/water behavior."
+ "and mirrored water footprints."
),
)
for cliff_definition: TerrainChunkDefinition in [

View file

@ -15,5 +15,5 @@ allowed_neighbor_tags = PackedStringArray("grass")
preferred_neighbor_tags = PackedStringArray("grass")
water_inlet_edges = 1
water_outlet_edges = 4
water_surface_size = Vector2(2.1, 10)
water_surface_size = Vector2(2.3, 10)
water_surface_offset = Vector2(-0.315, 0)

View file

@ -14,5 +14,5 @@ tags = PackedStringArray("land", "walkable", "fresh_water", "pond")
allowed_neighbor_tags = PackedStringArray("grass")
preferred_neighbor_tags = PackedStringArray("grass")
water_inlet_edges = 1
water_surface_size = Vector2(6.7, 7.75)
water_surface_size = Vector2(7, 8)
water_surface_offset = Vector2(-0.32, -1.03)

View file

@ -15,5 +15,5 @@ allowed_neighbor_tags = PackedStringArray("grass")
preferred_neighbor_tags = PackedStringArray("grass")
water_inlet_edges = 1
water_outlet_edges = 4
water_surface_size = Vector2(2.1, 10)
water_surface_offset = Vector2(-0.315, 0)
water_surface_size = Vector2(2.3, 10)
water_surface_offset = Vector2(0.323, 0)

View file

@ -16,8 +16,14 @@ const SALT_WATER_MATERIAL: Material = preload(
const FRESH_WATER_MATERIAL: Material = preload(
"res://world/materials/stylized_water_fresh.tres"
)
const POND_POOL: FishPool = preload(
"res://fish/pools/starter_pond_pool.tres"
const GENERATED_POND_POOL: FishPool = preload(
"res://fish/pools/generated_pond_pool.tres"
)
const GENERATED_LAKE_POOL: FishPool = preload(
"res://fish/pools/generated_lake_pool.tres"
)
const GENERATED_RIVER_POOL: FishPool = preload(
"res://fish/pools/generated_river_pool.tres"
)
const OCEAN_POOL: FishPool = preload(
"res://fish/pools/starter_ocean_pool.tres"
@ -374,7 +380,7 @@ func _configure_fresh_water(records: Array[Dictionary]) -> void:
body.visual_surface_enabled = false
body.water_material = _fresh_water_material()
body.water_type = WaterType.Type.FRESH_WATER
body.fish_pool = POND_POOL
body.fish_pool = _fresh_water_pool(tags)
body.location_tags = _fresh_water_location_tags(tags)
body.selection_priority = 10
@ -388,6 +394,14 @@ func _fresh_water_location_tags(tags: PackedStringArray) -> Array[StringName]:
return result
func _fresh_water_pool(tags: PackedStringArray) -> FishPool:
if "river" in tags:
return GENERATED_RIVER_POOL
if "lake" in tags:
return GENERATED_LAKE_POOL
return GENERATED_POND_POOL
func _configure_diggable_area() -> void:
var half_extents := get_playable_half_extents()
_diggable_beach.generation_bounds = Rect2(

View file

@ -159,7 +159,7 @@ script = ExtResource("10_water_body")
surface_size = Vector2(16.1, 12.3675)
water_material = ExtResource("19_fresh_water")
fish_pool = ExtResource("12_pond_pool")
location_tags = Array[StringName]([&"starter_pond"])
location_tags = Array[StringName]([&"starter_pond", &"pond"])
selection_priority = 1
[node name="VisualWater" type="MeshInstance3D" parent="WaterBodies/Pond" unique_id=1034265960]
@ -171,7 +171,7 @@ collision_layer = 4
collision_mask = 0
monitoring = false
script = ExtResource("3_water")
location_tags = Array[StringName]([&"starter_pond"])
location_tags = Array[StringName]([&"starter_pond", &"pond"])
water_type = 0
fish_pool = ExtResource("12_pond_pool")
selection_priority = 1