Scale coastal crab spawning by habitat
This commit is contained in:
parent
d8bf59bca0
commit
0158b0215f
9 changed files with 261 additions and 59 deletions
|
|
@ -10,5 +10,8 @@ catch_data = ExtResource("2_catch")
|
||||||
required_tool_id = &"crab_net"
|
required_tool_id = &"crab_net"
|
||||||
surface_materials = Array[StringName]([&"sand"])
|
surface_materials = Array[StringName]([&"sand"])
|
||||||
minimum_surface_y = -0.8
|
minimum_surface_y = -0.8
|
||||||
|
surface_water_relation = 2
|
||||||
population = 4
|
population = 4
|
||||||
|
surface_population_per_100_square_meters = 0.4
|
||||||
|
maximum_surface_population = 16
|
||||||
sprite_pixel_size = 0.0005
|
sprite_pixel_size = 0.0005
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,10 @@ catch_data = ExtResource("2_catch")
|
||||||
required_tool_id = &"crab_net"
|
required_tool_id = &"crab_net"
|
||||||
surface_materials = Array[StringName]([&"sand"])
|
surface_materials = Array[StringName]([&"sand"])
|
||||||
minimum_surface_y = -0.44
|
minimum_surface_y = -0.44
|
||||||
|
surface_water_relation = 1
|
||||||
population = 2
|
population = 2
|
||||||
|
surface_population_per_100_square_meters = 0.15
|
||||||
|
maximum_surface_population = 24
|
||||||
movement_speed = 0.35
|
movement_speed = 0.35
|
||||||
roam_radius = 3.5
|
roam_radius = 3.5
|
||||||
scare_radius = 2.8
|
scare_radius = 2.8
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@ enum PresentationMode {
|
||||||
WATER_SPURT,
|
WATER_SPURT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SurfaceWaterRelation {
|
||||||
|
ANY,
|
||||||
|
ABOVE_SALT_WATER,
|
||||||
|
BELOW_SALT_WATER,
|
||||||
|
}
|
||||||
|
|
||||||
@export var type_id: StringName
|
@export var type_id: StringName
|
||||||
@export var catch_data: FishDataType
|
@export var catch_data: FishDataType
|
||||||
@export var required_tool_id: StringName
|
@export var required_tool_id: StringName
|
||||||
|
|
@ -17,12 +23,20 @@ enum PresentationMode {
|
||||||
@export var diggable_area_id: StringName
|
@export var diggable_area_id: StringName
|
||||||
@export var spawn_anchor_set_id: StringName
|
@export var spawn_anchor_set_id: StringName
|
||||||
@export_range(-100.0, 100.0, 0.01) var minimum_surface_y: float = 0.08
|
@export_range(-100.0, 100.0, 0.01) var minimum_surface_y: float = 0.08
|
||||||
|
@export_range(-100.0, 100.0, 0.01) var maximum_surface_y: float = 100.0
|
||||||
|
@export var surface_water_relation := SurfaceWaterRelation.ANY
|
||||||
|
@export_range(0.0, 2.0, 0.01) var waterline_margin: float = 0.01
|
||||||
@export_range(0, 64, 1) var population: int = 0
|
@export_range(0, 64, 1) var population: int = 0
|
||||||
## Anchored gatherables can scale with authored/generated attachment geometry.
|
## Anchored gatherables can scale with authored/generated attachment geometry.
|
||||||
## Zero preserves the fixed population above.
|
## Zero preserves the fixed population above.
|
||||||
@export_range(0.0, 1.0, 0.01) var spawn_anchor_occupancy_ratio := 0.0
|
@export_range(0.0, 1.0, 0.01) var spawn_anchor_occupancy_ratio := 0.0
|
||||||
## Zero leaves the anchor count as the only ceiling.
|
## Zero leaves the anchor count as the only ceiling.
|
||||||
@export_range(0, 256, 1) var maximum_anchor_population := 0
|
@export_range(0, 256, 1) var maximum_anchor_population := 0
|
||||||
|
## Optional density scaling for large generated surfaces. The authored
|
||||||
|
## population remains the minimum while the maximum keeps large maps bounded.
|
||||||
|
@export_range(0.0, 8.0, 0.01)
|
||||||
|
var surface_population_per_100_square_meters := 0.0
|
||||||
|
@export_range(0, 256, 1) var maximum_surface_population := 0
|
||||||
@export var presentation_mode: PresentationMode = PresentationMode.VISIBLE_CREATURE
|
@export var presentation_mode: PresentationMode = PresentationMode.VISIBLE_CREATURE
|
||||||
@export var requires_sneaking: bool = true
|
@export var requires_sneaking: bool = true
|
||||||
@export_range(0.0, 120.0, 0.1) var active_lifetime_seconds: float = 0.0
|
@export_range(0.0, 120.0, 0.1) var active_lifetime_seconds: float = 0.0
|
||||||
|
|
@ -77,6 +91,7 @@ func is_valid() -> bool:
|
||||||
or uses_spawn_anchors
|
or uses_spawn_anchors
|
||||||
or not surface_materials.is_empty()
|
or not surface_materials.is_empty()
|
||||||
)
|
)
|
||||||
|
and maximum_surface_y >= minimum_surface_y
|
||||||
and (
|
and (
|
||||||
not uses_diggable_area
|
not uses_diggable_area
|
||||||
or catch_data.collection_method
|
or catch_data.collection_method
|
||||||
|
|
@ -87,6 +102,10 @@ func is_valid() -> bool:
|
||||||
maximum_anchor_population == 0
|
maximum_anchor_population == 0
|
||||||
or maximum_anchor_population >= population
|
or maximum_anchor_population >= population
|
||||||
)
|
)
|
||||||
|
and (
|
||||||
|
maximum_surface_population == 0
|
||||||
|
or maximum_surface_population >= population
|
||||||
|
)
|
||||||
and movement_parameters_valid
|
and movement_parameters_valid
|
||||||
and _quality_multipliers_are_valid(
|
and _quality_multipliers_are_valid(
|
||||||
quality_movement_speed_multipliers
|
quality_movement_speed_multipliers
|
||||||
|
|
@ -126,6 +145,24 @@ func target_population_for_anchor_count(anchor_count: int) -> int:
|
||||||
return target
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
func target_population_for_surface_area(surface_area: float) -> int:
|
||||||
|
if surface_population_per_100_square_meters <= 0.0:
|
||||||
|
return population
|
||||||
|
if surface_area <= 0.0:
|
||||||
|
return 0
|
||||||
|
var target := maxi(
|
||||||
|
population,
|
||||||
|
ceili(
|
||||||
|
surface_area
|
||||||
|
* surface_population_per_100_square_meters
|
||||||
|
/ 100.0
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if maximum_surface_population > 0:
|
||||||
|
target = mini(target, maximum_surface_population)
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
func can_be_scared() -> bool:
|
func can_be_scared() -> bool:
|
||||||
return not is_stationary_spawn() and scare_radius > 0.0
|
return not is_stationary_spawn() and scare_radius > 0.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ var _entity_revisions: Dictionary = {}
|
||||||
var _surface_triangles: Dictionary = {}
|
var _surface_triangles: Dictionary = {}
|
||||||
var _surface_areas: Dictionary = {}
|
var _surface_areas: Dictionary = {}
|
||||||
var _surface_total_areas: Dictionary = {}
|
var _surface_total_areas: Dictionary = {}
|
||||||
|
var _surface_height_ranges: Dictionary = {}
|
||||||
var _spawn_anchor_positions: Dictionary = {}
|
var _spawn_anchor_positions: Dictionary = {}
|
||||||
var _respawns: Array[Dictionary] = []
|
var _respawns: Array[Dictionary] = []
|
||||||
var _next_respawn_by_type: Dictionary[StringName, float] = {}
|
var _next_respawn_by_type: Dictionary[StringName, float] = {}
|
||||||
|
|
@ -322,10 +323,14 @@ func _cache_spawn_surface(entry: GatherableDataType) -> void:
|
||||||
if not entry.diggable_area_id.is_empty():
|
if not entry.diggable_area_id.is_empty():
|
||||||
triangles = _world.get_diggable_area_triangles(entry.diggable_area_id)
|
triangles = _world.get_diggable_area_triangles(entry.diggable_area_id)
|
||||||
else:
|
else:
|
||||||
|
var height_range := _resolved_surface_height_range(entry)
|
||||||
triangles = _world.get_spawn_surface_triangles(
|
triangles = _world.get_spawn_surface_triangles(
|
||||||
entry.surface_materials,
|
entry.surface_materials,
|
||||||
entry.minimum_surface_y,
|
height_range.x,
|
||||||
|
0.6,
|
||||||
|
height_range.y,
|
||||||
)
|
)
|
||||||
|
_surface_height_ranges[entry.type_id] = height_range
|
||||||
var areas := PackedFloat32Array()
|
var areas := PackedFloat32Array()
|
||||||
var total_area: float = 0.0
|
var total_area: float = 0.0
|
||||||
for triangle: PackedVector3Array in triangles:
|
for triangle: PackedVector3Array in triangles:
|
||||||
|
|
@ -519,9 +524,18 @@ func _sample_surface_position(
|
||||||
if triangles.is_empty() or total_area <= 0.0:
|
if triangles.is_empty() or total_area <= 0.0:
|
||||||
return Vector3(INF, INF, INF)
|
return Vector3(INF, INF, INF)
|
||||||
var validates_diggable_surface := not entry.diggable_area_id.is_empty()
|
var validates_diggable_surface := not entry.diggable_area_id.is_empty()
|
||||||
|
var height_range: Vector2 = _surface_height_ranges.get(
|
||||||
|
entry.type_id,
|
||||||
|
Vector2(entry.minimum_surface_y, entry.maximum_surface_y),
|
||||||
|
)
|
||||||
|
var validates_height_range := _surface_height_ranges.has(entry.type_id)
|
||||||
var attempts: int = (
|
var attempts: int = (
|
||||||
48
|
48
|
||||||
if validates_diggable_surface or is_finite(maximum_distance)
|
if (
|
||||||
|
validates_diggable_surface
|
||||||
|
or validates_height_range
|
||||||
|
or is_finite(maximum_distance)
|
||||||
|
)
|
||||||
else 1
|
else 1
|
||||||
)
|
)
|
||||||
var fallback: Vector3 = Vector3(INF, INF, INF)
|
var fallback: Vector3 = Vector3(INF, INF, INF)
|
||||||
|
|
@ -551,9 +565,20 @@ func _sample_surface_position(
|
||||||
point,
|
point,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if within_requested_distance and valid_diggable_surface:
|
var valid_height := (
|
||||||
|
not validates_height_range
|
||||||
|
or (
|
||||||
|
point.y > height_range.x
|
||||||
|
and point.y < height_range.y
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
within_requested_distance
|
||||||
|
and valid_diggable_surface
|
||||||
|
and valid_height
|
||||||
|
):
|
||||||
return point
|
return point
|
||||||
if validates_diggable_surface:
|
if validates_diggable_surface or validates_height_range:
|
||||||
return Vector3(INF, INF, INF)
|
return Vector3(INF, INF, INF)
|
||||||
if origin.is_finite() and is_finite(maximum_distance):
|
if origin.is_finite() and is_finite(maximum_distance):
|
||||||
return origin
|
return origin
|
||||||
|
|
@ -1405,8 +1430,12 @@ func _reconcile_seasonal_population() -> void:
|
||||||
|
|
||||||
|
|
||||||
func _target_population(entry: GatherableDataType) -> int:
|
func _target_population(entry: GatherableDataType) -> int:
|
||||||
if entry == null or entry.spawn_anchor_set_id.is_empty():
|
if entry == null:
|
||||||
return entry.population if entry != null else 0
|
return 0
|
||||||
|
if entry.spawn_anchor_set_id.is_empty():
|
||||||
|
return entry.target_population_for_surface_area(
|
||||||
|
float(_surface_total_areas.get(entry.type_id, 0.0))
|
||||||
|
)
|
||||||
var anchors: PackedVector3Array = _spawn_anchor_positions.get(
|
var anchors: PackedVector3Array = _spawn_anchor_positions.get(
|
||||||
entry.type_id,
|
entry.type_id,
|
||||||
PackedVector3Array(),
|
PackedVector3Array(),
|
||||||
|
|
@ -1414,6 +1443,25 @@ func _target_population(entry: GatherableDataType) -> int:
|
||||||
return entry.target_population_for_anchor_count(anchors.size())
|
return entry.target_population_for_anchor_count(anchors.size())
|
||||||
|
|
||||||
|
|
||||||
|
func _resolved_surface_height_range(entry: GatherableDataType) -> Vector2:
|
||||||
|
var minimum_y := entry.minimum_surface_y
|
||||||
|
var maximum_y := entry.maximum_surface_y
|
||||||
|
var saltwater_height := _world.get_saltwater_surface_height()
|
||||||
|
if is_finite(saltwater_height):
|
||||||
|
match entry.surface_water_relation:
|
||||||
|
GatherableDataType.SurfaceWaterRelation.ABOVE_SALT_WATER:
|
||||||
|
minimum_y = maxf(
|
||||||
|
minimum_y,
|
||||||
|
saltwater_height + entry.waterline_margin,
|
||||||
|
)
|
||||||
|
GatherableDataType.SurfaceWaterRelation.BELOW_SALT_WATER:
|
||||||
|
maximum_y = minf(
|
||||||
|
maximum_y,
|
||||||
|
saltwater_height - entry.waterline_margin,
|
||||||
|
)
|
||||||
|
return Vector2(minimum_y, maximum_y)
|
||||||
|
|
||||||
|
|
||||||
func _population_for_type(type_id: StringName) -> int:
|
func _population_for_type(type_id: StringName) -> int:
|
||||||
var count: int = 0
|
var count: int = 0
|
||||||
for state: Dictionary in _entities.values():
|
for state: Dictionary in _entities.values():
|
||||||
|
|
@ -1445,6 +1493,7 @@ func _clear_world() -> void:
|
||||||
_surface_triangles.clear()
|
_surface_triangles.clear()
|
||||||
_surface_areas.clear()
|
_surface_areas.clear()
|
||||||
_surface_total_areas.clear()
|
_surface_total_areas.clear()
|
||||||
|
_surface_height_ranges.clear()
|
||||||
_spawn_anchor_positions.clear()
|
_spawn_anchor_positions.clear()
|
||||||
_respawns.clear()
|
_respawns.clear()
|
||||||
_next_respawn_by_type.clear()
|
_next_respawn_by_type.clear()
|
||||||
|
|
|
||||||
|
|
@ -40,39 +40,17 @@ const StarterRegionScene = preload(
|
||||||
const SharedFishPlaceholder: Texture2D = preload(
|
const SharedFishPlaceholder: Texture2D = preload(
|
||||||
"res://art/exported/creatures/fish_placeholder.png"
|
"res://art/exported/creatures/fish_placeholder.png"
|
||||||
)
|
)
|
||||||
const FINAL_CREATURE_TEXTURE_PATHS: Dictionary[StringName, String] = {
|
const FINAL_CREATURE_CATALOG_NUMBERS: Array[int] = [
|
||||||
&"bowfin": "res://art/exported/creatures/101.png",
|
101, 102, 103, 104, 105, 106, 107, 201, 403, 701, 704, 801,
|
||||||
&"sturgeon_lake": "res://art/exported/creatures/102.png",
|
802, 805, 806, 808, 2502, 2504, 2505, 2508, 2601, 2801, 2802,
|
||||||
&"gar_longnose": "res://art/exported/creatures/103.png",
|
2803, 2804, 2806, 2807, 3002, 3102, 3103, 3104, 3107, 3201,
|
||||||
&"paddlefish": "res://art/exported/creatures/104.png",
|
3401, 3902, 3906, 4101, 4401, 4701, 5001, 5702, 5901, 5902,
|
||||||
&"sturgeon_shovelnose": "res://art/exported/creatures/105.png",
|
5903, 5904, 6001, 6103, 6406, 6601, 6801, 6804, 6807, 6901,
|
||||||
&"gar_spotted": "res://art/exported/creatures/106.png",
|
6902, 6904, 6905, 7101, 7403, 7506, 7508,
|
||||||
&"lungfish_west_african": "res://art/exported/creatures/107.png",
|
]
|
||||||
&"mudskipper_atlantic": "res://art/exported/creatures/201.png",
|
|
||||||
&"flounder_winter": "res://art/exported/creatures/403.png",
|
|
||||||
&"mackerel_atlantic": "res://art/exported/creatures/701.png",
|
|
||||||
&"mackerel_chub": "res://art/exported/creatures/704.png",
|
|
||||||
&"sand_lance_american": "res://art/exported/creatures/801.png",
|
|
||||||
&"menhaden_atlantic": "res://art/exported/creatures/802.png",
|
|
||||||
&"anchovy_european": "res://art/exported/creatures/805.png",
|
|
||||||
&"sardine_european": "res://art/exported/creatures/806.png",
|
|
||||||
&"anchovy_northern": "res://art/exported/creatures/808.png",
|
|
||||||
&"crab_brown": "res://art/exported/creatures/3906.png",
|
|
||||||
&"salmon_atlantic": "res://art/exported/creatures/4401.png",
|
|
||||||
&"mahi_mahi": "res://art/exported/creatures/4701.png",
|
|
||||||
&"clownfish_ocellaris": "res://art/exported/creatures/5702.png",
|
|
||||||
&"chromis_blue_green": "res://art/exported/creatures/5901.png",
|
|
||||||
&"hogfish": "res://art/exported/creatures/6103.png",
|
|
||||||
&"mullet_striped": "res://art/exported/creatures/6804.png",
|
|
||||||
&"sheepshead": "res://art/exported/creatures/6905.png",
|
|
||||||
&"milkfish": "res://art/exported/creatures/7101.png",
|
|
||||||
&"ladyfish": "res://art/exported/creatures/7403.png",
|
|
||||||
&"tilapia_nile": "res://art/exported/creatures/7506.png",
|
|
||||||
&"betta_siamese": "res://art/exported/creatures/7508.png",
|
|
||||||
}
|
|
||||||
|
|
||||||
const ACTIVE_CATALOG_COUNT: int = 313
|
const ACTIVE_CATALOG_COUNT: int = 314
|
||||||
const INACTIVE_CATALOG_COUNT: int = 3
|
const INACTIVE_CATALOG_COUNT: int = 2
|
||||||
const FISHING_SPECIES_COUNT: int = 310
|
const FISHING_SPECIES_COUNT: int = 310
|
||||||
const GENERATED_POND_COUNT: int = 33
|
const GENERATED_POND_COUNT: int = 33
|
||||||
const GENERATED_LAKE_COUNT: int = 20
|
const GENERATED_LAKE_COUNT: int = 20
|
||||||
|
|
@ -235,10 +213,15 @@ func _validate_catalog_and_pools() -> void:
|
||||||
else:
|
else:
|
||||||
inactive_count += 1
|
inactive_count += 1
|
||||||
assert(not fish.is_selectable())
|
assert(not fish.is_selectable())
|
||||||
assert(fish.display_texture == null)
|
if fish.catalog_number not in FINAL_CREATURE_CATALOG_NUMBERS:
|
||||||
if FINAL_CREATURE_TEXTURE_PATHS.has(fish.id):
|
assert(fish.display_texture == null)
|
||||||
|
if fish.catalog_number in FINAL_CREATURE_CATALOG_NUMBERS:
|
||||||
|
var expected_path := (
|
||||||
|
"res://art/exported/creatures/%d.png"
|
||||||
|
% fish.catalog_number
|
||||||
|
)
|
||||||
var expected_texture: Texture2D = load(
|
var expected_texture: Texture2D = load(
|
||||||
FINAL_CREATURE_TEXTURE_PATHS[fish.id]
|
expected_path
|
||||||
) as Texture2D
|
) as Texture2D
|
||||||
assert(expected_texture != null)
|
assert(expected_texture != null)
|
||||||
assert(fish.display_texture == expected_texture)
|
assert(fish.display_texture == expected_texture)
|
||||||
|
|
@ -246,14 +229,14 @@ func _validate_catalog_and_pools() -> void:
|
||||||
if fish.collection_method == FishDataType.CollectionMethod.FISHING:
|
if fish.collection_method == FishDataType.CollectionMethod.FISHING:
|
||||||
fishing_count += 1
|
fishing_count += 1
|
||||||
assert(fish.active)
|
assert(fish.active)
|
||||||
if not FINAL_CREATURE_TEXTURE_PATHS.has(fish.id):
|
if fish.catalog_number not in FINAL_CREATURE_CATALOG_NUMBERS:
|
||||||
assert(fish.display_texture == SharedFishPlaceholder)
|
assert(fish.display_texture == SharedFishPlaceholder)
|
||||||
placeholder_fishing_count += 1
|
placeholder_fishing_count += 1
|
||||||
assert(active_count == ACTIVE_CATALOG_COUNT)
|
assert(active_count == ACTIVE_CATALOG_COUNT)
|
||||||
assert(inactive_count == INACTIVE_CATALOG_COUNT)
|
assert(inactive_count == INACTIVE_CATALOG_COUNT)
|
||||||
assert(fishing_count == FISHING_SPECIES_COUNT)
|
assert(fishing_count == FISHING_SPECIES_COUNT)
|
||||||
assert(final_art_count == FINAL_CREATURE_TEXTURE_PATHS.size())
|
assert(final_art_count == FINAL_CREATURE_CATALOG_NUMBERS.size())
|
||||||
assert(placeholder_fishing_count == 283)
|
assert(placeholder_fishing_count == 253)
|
||||||
var chum: FishDataType = Catalog.get_fish_by_id(&"salmon_chum")
|
var chum: FishDataType = Catalog.get_fish_by_id(&"salmon_chum")
|
||||||
assert(chum != null)
|
assert(chum != null)
|
||||||
assert(chum.get_season_text() == "fall")
|
assert(chum.get_season_text() == "fall")
|
||||||
|
|
@ -291,7 +274,7 @@ func _validate_catalog_and_pools() -> void:
|
||||||
) == chum
|
) == chum
|
||||||
)
|
)
|
||||||
seasonal_collection.free()
|
seasonal_collection.free()
|
||||||
var inactive_fish: FishDataType = Catalog.get_fish_by_id(&"crab_blue")
|
var inactive_fish: FishDataType = Catalog.get_fish_by_id(&"crab_dungeness")
|
||||||
assert(inactive_fish != null and not inactive_fish.active)
|
assert(inactive_fish != null and not inactive_fish.active)
|
||||||
var inactive_pool := FishPoolType.new()
|
var inactive_pool := FishPoolType.new()
|
||||||
inactive_pool.candidates = [inactive_fish]
|
inactive_pool.candidates = [inactive_fish]
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@ const GeneratedLakePool: FishPool = preload(
|
||||||
const GeneratedRiverPool: FishPool = preload(
|
const GeneratedRiverPool: FishPool = preload(
|
||||||
"res://fish/pools/generated_river_pool.tres"
|
"res://fish/pools/generated_river_pool.tres"
|
||||||
)
|
)
|
||||||
|
const Gatherables: GatherableCatalog = preload(
|
||||||
|
"res://gathering/catalog/gatherable_catalog.tres"
|
||||||
|
)
|
||||||
const PrecipitationOcclusionType = preload(
|
const PrecipitationOcclusionType = preload(
|
||||||
"res://world/environment/precipitation_occlusion.gd"
|
"res://world/environment/precipitation_occlusion.gd"
|
||||||
)
|
)
|
||||||
|
|
@ -402,7 +405,7 @@ func _validate_generated_region(
|
||||||
+ generator.stacked_elevated_placement_keys().size()
|
+ generator.stacked_elevated_placement_keys().size()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_validate_elevated_cliff_feature(generator)
|
_validate_elevated_cliff_feature(region, generator)
|
||||||
|
|
||||||
var shop := region.get_node("Interactables/FishingShopWorld") as Node3D
|
var shop := region.get_node("Interactables/FishingShopWorld") as Node3D
|
||||||
var storage := region.get_node("Interactables/PlayerStorageBox") as Node3D
|
var storage := region.get_node("Interactables/PlayerStorageBox") as Node3D
|
||||||
|
|
@ -536,6 +539,7 @@ func _validate_generated_region(
|
||||||
-INF,
|
-INF,
|
||||||
0.35,
|
0.35,
|
||||||
)
|
)
|
||||||
|
_validate_crab_spawn_surfaces(region)
|
||||||
_validate_water_recovery_positions(
|
_validate_water_recovery_positions(
|
||||||
region,
|
region,
|
||||||
grass_prop_surface_triangles,
|
grass_prop_surface_triangles,
|
||||||
|
|
@ -691,6 +695,7 @@ func _validate_generated_region(
|
||||||
|
|
||||||
|
|
||||||
func _validate_elevated_cliff_feature(
|
func _validate_elevated_cliff_feature(
|
||||||
|
region: GeneratedWorldRegion,
|
||||||
generator: TerrainChunkGenerator,
|
generator: TerrainChunkGenerator,
|
||||||
) -> void:
|
) -> void:
|
||||||
var elevated_coordinates: Dictionary[Vector2i, StringName] = {}
|
var elevated_coordinates: Dictionary[Vector2i, StringName] = {}
|
||||||
|
|
@ -824,6 +829,27 @@ func _validate_elevated_cliff_feature(
|
||||||
or generator.placement_keys()[low_index].begins_with("chunk_spawn@")
|
or generator.placement_keys()[low_index].begins_with("chunk_spawn@")
|
||||||
)
|
)
|
||||||
assert(found_primary_ramp == not primary_is_third_tier)
|
assert(found_primary_ramp == not primary_is_third_tier)
|
||||||
|
var primary_surface_height := (
|
||||||
|
generator.elevated_cliff_level_height
|
||||||
|
* (2.0 if primary_is_third_tier else 1.0)
|
||||||
|
)
|
||||||
|
var primary_surface_position := generator.to_global(
|
||||||
|
generator.chunk_position(feature_center)
|
||||||
|
+ Vector3.UP * primary_surface_height
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
region.is_home_exterior_position_accessible(primary_surface_position)
|
||||||
|
== not primary_is_third_tier
|
||||||
|
)
|
||||||
|
assert(region.is_home_exterior_position_accessible(generator.to_global(
|
||||||
|
generator.chunk_position(feature_center)
|
||||||
|
)))
|
||||||
|
# A higher surface sharing a ramp-accessible base cell is still invalid
|
||||||
|
# unless that elevation has its own authored ramp connection.
|
||||||
|
assert(not region.is_home_exterior_position_accessible(generator.to_global(
|
||||||
|
generator.chunk_position(feature_center)
|
||||||
|
+ Vector3.UP * (generator.elevated_cliff_level_height * 2.0)
|
||||||
|
)))
|
||||||
|
|
||||||
var generated := generator.get_generated_chunks_root()
|
var generated := generator.get_generated_chunks_root()
|
||||||
assert(generated != null)
|
assert(generated != null)
|
||||||
|
|
@ -1251,6 +1277,58 @@ func _validate_decoration_transform(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_crab_spawn_surfaces(region: GeneratedWorldRegion) -> void:
|
||||||
|
var brown := Gatherables.get_entry(&"crab_brown")
|
||||||
|
var blue := Gatherables.get_entry(&"crab_blue")
|
||||||
|
assert(brown != null and blue != null)
|
||||||
|
assert(is_equal_approx(
|
||||||
|
region.get_saltwater_surface_height(),
|
||||||
|
GeneratedWorldRegion.WATER_HEIGHT,
|
||||||
|
))
|
||||||
|
var brown_minimum := maxf(
|
||||||
|
brown.minimum_surface_y,
|
||||||
|
GeneratedWorldRegion.WATER_HEIGHT + brown.waterline_margin,
|
||||||
|
)
|
||||||
|
var blue_maximum := minf(
|
||||||
|
blue.maximum_surface_y,
|
||||||
|
GeneratedWorldRegion.WATER_HEIGHT - blue.waterline_margin,
|
||||||
|
)
|
||||||
|
assert(brown_minimum > blue_maximum)
|
||||||
|
var brown_triangles := region.get_spawn_surface_triangles(
|
||||||
|
brown.surface_materials,
|
||||||
|
brown_minimum,
|
||||||
|
0.6,
|
||||||
|
brown.maximum_surface_y,
|
||||||
|
)
|
||||||
|
var blue_triangles := region.get_spawn_surface_triangles(
|
||||||
|
blue.surface_materials,
|
||||||
|
blue.minimum_surface_y,
|
||||||
|
0.6,
|
||||||
|
blue_maximum,
|
||||||
|
)
|
||||||
|
assert(not brown_triangles.is_empty())
|
||||||
|
assert(not blue_triangles.is_empty())
|
||||||
|
assert(
|
||||||
|
brown.target_population_for_surface_area(
|
||||||
|
_surface_triangle_area(brown_triangles)
|
||||||
|
) > brown.population
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
blue.target_population_for_surface_area(
|
||||||
|
_surface_triangle_area(blue_triangles)
|
||||||
|
) > blue.population
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _surface_triangle_area(triangles: Array[PackedVector3Array]) -> float:
|
||||||
|
var area := 0.0
|
||||||
|
for triangle: PackedVector3Array in triangles:
|
||||||
|
area += (triangle[1] - triangle[0]).cross(
|
||||||
|
triangle[2] - triangle[0]
|
||||||
|
).length() * 0.5
|
||||||
|
return area
|
||||||
|
|
||||||
|
|
||||||
func _triangle_surface_height_at(
|
func _triangle_surface_height_at(
|
||||||
position: Vector3,
|
position: Vector3,
|
||||||
triangles: Array[PackedVector3Array],
|
triangles: Array[PackedVector3Array],
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ func _run() -> void:
|
||||||
func _validate_catalog() -> void:
|
func _validate_catalog() -> void:
|
||||||
assert(CatalogResource.candidates.size() == 316)
|
assert(CatalogResource.candidates.size() == 316)
|
||||||
var ordered := LogbookCatalog.ordered_species(CatalogResource.candidates)
|
var ordered := LogbookCatalog.ordered_species(CatalogResource.candidates)
|
||||||
assert(ordered.size() == 313)
|
assert(ordered.size() == 314)
|
||||||
var previous_number: int = 0
|
var previous_number: int = 0
|
||||||
var catalog_numbers: Dictionary[int, bool] = {}
|
var catalog_numbers: Dictionary[int, bool] = {}
|
||||||
for fish: FishDataType in CatalogResource.candidates:
|
for fish: FishDataType in CatalogResource.candidates:
|
||||||
|
|
@ -640,7 +640,10 @@ func _validate_page() -> void:
|
||||||
|
|
||||||
page.call("_select_category", LogbookCatalog.Category.SHELLFISH)
|
page.call("_select_category", LogbookCatalog.Category.SHELLFISH)
|
||||||
await create_timer(0.25).timeout
|
await create_timer(0.25).timeout
|
||||||
assert((page.get("_entry_buttons") as Dictionary).size() == 2)
|
assert((page.get("_entry_buttons") as Dictionary).size() == 3)
|
||||||
|
assert(
|
||||||
|
(page.get("_entry_buttons") as Dictionary).has(&"unknown_3902")
|
||||||
|
)
|
||||||
assert(
|
assert(
|
||||||
(page.get("_entry_buttons") as Dictionary).has(&"unknown_3906")
|
(page.get("_entry_buttons") as Dictionary).has(&"unknown_3906")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ extends SceneTree
|
||||||
|
|
||||||
const MainScene: PackedScene = preload("res://main/main.tscn")
|
const MainScene: PackedScene = preload("res://main/main.tscn")
|
||||||
const TEST_PORT: int = 18170
|
const TEST_PORT: int = 18170
|
||||||
const EXPECTED_POPULATION: int = 8
|
const EXPECTED_POPULATION: int = 12
|
||||||
const EXPECTED_BY_TYPE: Dictionary = {
|
const EXPECTED_BY_TYPE: Dictionary = {
|
||||||
&"crab_brown": 2,
|
&"crab_brown": 2,
|
||||||
|
&"crab_blue": 4,
|
||||||
&"clam_manila": 3,
|
&"clam_manila": 3,
|
||||||
&"beetle_stag_common": 3,
|
&"beetle_stag_common": 3,
|
||||||
}
|
}
|
||||||
|
|
@ -161,6 +162,10 @@ func _validate_population(
|
||||||
(position as Vector3).y
|
(position as Vector3).y
|
||||||
>= entry.minimum_surface_y - 0.001
|
>= entry.minimum_surface_y - 0.001
|
||||||
)
|
)
|
||||||
|
if entry.type_id == &"crab_brown":
|
||||||
|
assert((position as Vector3).y > -0.45)
|
||||||
|
elif entry.type_id == &"crab_blue":
|
||||||
|
assert((position as Vector3).y < -0.45)
|
||||||
if expect_authoritative_state:
|
if expect_authoritative_state:
|
||||||
var quality: int = int(state.get("quality", -1))
|
var quality: int = int(state.get("quality", -1))
|
||||||
assert(FishQuality.is_valid(quality))
|
assert(FishQuality.is_valid(quality))
|
||||||
|
|
@ -196,7 +201,10 @@ func _validate_respawn_budget(service: Node) -> void:
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
assert((service.get("_entities") as Dictionary).size() == 6)
|
assert(
|
||||||
|
(service.get("_entities") as Dictionary).size()
|
||||||
|
== EXPECTED_POPULATION - crab_ids.size()
|
||||||
|
)
|
||||||
|
|
||||||
var now: float = Time.get_ticks_msec() / 1000.0
|
var now: float = Time.get_ticks_msec() / 1000.0
|
||||||
var respawns: Array = service.get("_respawns")
|
var respawns: Array = service.get("_respawns")
|
||||||
|
|
@ -209,18 +217,27 @@ func _validate_respawn_budget(service: Node) -> void:
|
||||||
respawns[index] = respawn
|
respawns[index] = respawn
|
||||||
|
|
||||||
service.call("_update_respawns")
|
service.call("_update_respawns")
|
||||||
assert((service.get("_entities") as Dictionary).size() == 7)
|
assert(
|
||||||
|
(service.get("_entities") as Dictionary).size()
|
||||||
|
== EXPECTED_POPULATION - 1
|
||||||
|
)
|
||||||
assert((service.get("_respawns") as Array).size() == 1)
|
assert((service.get("_respawns") as Array).size() == 1)
|
||||||
var next_by_type: Dictionary = service.get("_next_respawn_by_type")
|
var next_by_type: Dictionary = service.get("_next_respawn_by_type")
|
||||||
var next_allowed: float = float(next_by_type.get(&"crab_brown", 0.0))
|
var next_allowed: float = float(next_by_type.get(&"crab_brown", 0.0))
|
||||||
assert(next_allowed - now >= 179.0)
|
assert(next_allowed - now >= 179.0)
|
||||||
|
|
||||||
service.call("_update_respawns")
|
service.call("_update_respawns")
|
||||||
assert((service.get("_entities") as Dictionary).size() == 7)
|
assert(
|
||||||
|
(service.get("_entities") as Dictionary).size()
|
||||||
|
== EXPECTED_POPULATION - 1
|
||||||
|
)
|
||||||
assert((service.get("_respawns") as Array).size() == 1)
|
assert((service.get("_respawns") as Array).size() == 1)
|
||||||
next_by_type[&"crab_brown"] = now - 1.0
|
next_by_type[&"crab_brown"] = now - 1.0
|
||||||
service.call("_update_respawns")
|
service.call("_update_respawns")
|
||||||
assert((service.get("_entities") as Dictionary).size() == 8)
|
assert(
|
||||||
|
(service.get("_entities") as Dictionary).size()
|
||||||
|
== EXPECTED_POPULATION
|
||||||
|
)
|
||||||
assert((service.get("_respawns") as Array).is_empty())
|
assert((service.get("_respawns") as Array).is_empty())
|
||||||
_validate_population(service, true)
|
_validate_population(service, true)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,17 @@ func _validate_catalog_statuses() -> void:
|
||||||
assert(is_equal_approx(brown.minimum_respawn_spacing_seconds, 180.0))
|
assert(is_equal_approx(brown.minimum_respawn_spacing_seconds, 180.0))
|
||||||
assert(brown.required_tool_id == &"crab_net")
|
assert(brown.required_tool_id == &"crab_net")
|
||||||
assert(is_equal_approx(brown.minimum_surface_y, -0.44))
|
assert(is_equal_approx(brown.minimum_surface_y, -0.44))
|
||||||
|
assert(
|
||||||
|
brown.surface_water_relation
|
||||||
|
== GatherableData.SurfaceWaterRelation.ABOVE_SALT_WATER
|
||||||
|
)
|
||||||
|
assert(is_equal_approx(
|
||||||
|
brown.surface_population_per_100_square_meters,
|
||||||
|
0.15,
|
||||||
|
))
|
||||||
|
assert(brown.maximum_surface_population == 24)
|
||||||
|
assert(brown.target_population_for_surface_area(100.0) == 2)
|
||||||
|
assert(brown.target_population_for_surface_area(7500.0) == 12)
|
||||||
assert(FishCatalog.get_fish_by_id(&"crab_brown") == brown.catch_data)
|
assert(FishCatalog.get_fish_by_id(&"crab_brown") == brown.catch_data)
|
||||||
assert(not brown.catch_data.is_fishable())
|
assert(not brown.catch_data.is_fishable())
|
||||||
var clam: GatherableData = Gatherables.get_entry(&"clam_manila")
|
var clam: GatherableData = Gatherables.get_entry(&"clam_manila")
|
||||||
|
|
@ -86,11 +97,27 @@ func _validate_catalog_statuses() -> void:
|
||||||
assert(FishCatalog.get_fish_by_id(&"beetle_stag_common") == beetle.catch_data)
|
assert(FishCatalog.get_fish_by_id(&"beetle_stag_common") == beetle.catch_data)
|
||||||
assert(not beetle.catch_data.is_fishable())
|
assert(not beetle.catch_data.is_fishable())
|
||||||
|
|
||||||
for type_id: StringName in [
|
var blue: GatherableData = Gatherables.get_entry(&"crab_blue")
|
||||||
&"crab_ghost",
|
assert(blue != null and blue.is_available())
|
||||||
&"crab_blue",
|
assert(blue.catch_data.active)
|
||||||
&"crab_dungeness",
|
assert(blue.is_available(CalendarSeasonType.Season.SUMMER))
|
||||||
]:
|
assert(blue.is_available(CalendarSeasonType.Season.FALL))
|
||||||
|
assert(not blue.is_available(CalendarSeasonType.Season.WINTER))
|
||||||
|
assert(
|
||||||
|
blue.surface_water_relation
|
||||||
|
== GatherableData.SurfaceWaterRelation.BELOW_SALT_WATER
|
||||||
|
)
|
||||||
|
assert(is_equal_approx(
|
||||||
|
blue.surface_population_per_100_square_meters,
|
||||||
|
0.4,
|
||||||
|
))
|
||||||
|
assert(blue.maximum_surface_population == 16)
|
||||||
|
assert(blue.target_population_for_surface_area(100.0) == 4)
|
||||||
|
assert(blue.target_population_for_surface_area(2250.0) == 9)
|
||||||
|
assert(FishCatalog.get_fish_by_id(&"crab_blue") == blue.catch_data)
|
||||||
|
assert(not blue.catch_data.is_fishable())
|
||||||
|
|
||||||
|
for type_id: StringName in [&"crab_ghost", &"crab_dungeness"]:
|
||||||
var entry: GatherableData = Gatherables.get_entry(type_id)
|
var entry: GatherableData = Gatherables.get_entry(type_id)
|
||||||
assert(entry != null)
|
assert(entry != null)
|
||||||
assert(entry.is_valid())
|
assert(entry.is_valid())
|
||||||
|
|
@ -109,8 +136,9 @@ func _validate_catalog_statuses() -> void:
|
||||||
)
|
)
|
||||||
|
|
||||||
var available: Array[GatherableData] = Gatherables.get_available_entries()
|
var available: Array[GatherableData] = Gatherables.get_available_entries()
|
||||||
assert(available.size() == 3)
|
assert(available.size() == 4)
|
||||||
assert(available.has(brown))
|
assert(available.has(brown))
|
||||||
|
assert(available.has(blue))
|
||||||
assert(available.has(clam))
|
assert(available.has(clam))
|
||||||
assert(available.has(beetle))
|
assert(available.has(beetle))
|
||||||
var winter_available: Array[GatherableData] = (
|
var winter_available: Array[GatherableData] = (
|
||||||
|
|
@ -118,6 +146,7 @@ func _validate_catalog_statuses() -> void:
|
||||||
)
|
)
|
||||||
assert(winter_available.has(brown))
|
assert(winter_available.has(brown))
|
||||||
assert(winter_available.has(clam))
|
assert(winter_available.has(clam))
|
||||||
|
assert(not winter_available.has(blue))
|
||||||
assert(not winter_available.has(beetle))
|
assert(not winter_available.has(beetle))
|
||||||
var rng := RandomNumberGenerator.new()
|
var rng := RandomNumberGenerator.new()
|
||||||
rng.seed = 24680
|
rng.seed = 24680
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue