Add starter RV progression and fishing feedback

This commit is contained in:
Alexander Sellite 2026-08-31 16:03:55 -04:00
parent eed361681a
commit d8bf59bca0
47 changed files with 2268 additions and 467 deletions

View file

@ -12,6 +12,9 @@ const PlayerStorageInteractionType = preload(
const DecorShopInteractionType = preload(
"res://world/decor_shop_interaction.gd"
)
const RVUpgradeInteractionType = preload(
"res://world/rv_upgrade_interaction.gd"
)
const PrecipitationOcclusionType = preload(
"res://world/environment/precipitation_occlusion.gd"
)
@ -52,6 +55,8 @@ 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 HOME_GROUND_HEIGHT_TOLERANCE := 0.5
const HOME_ELEVATION_HEIGHT_TOLERANCE := 0.6
const PROP_SURFACE_SAMPLE_DIRECTIONS: Array[Vector2] = [
Vector2(1.0, 0.0),
Vector2(0.70710678, 0.70710678),
@ -86,9 +91,13 @@ const PROCEDURAL_PROP_GROUPS: Array[StringName] = [
@onready var _decor_shop: DecorShopInteractionType = (
$Interactables/DecorShopWorld/InteractionArea
)
@onready var _rv_upgrade_shop: RVUpgradeInteractionType = (
$Interactables/RVUpgradeShopWorld/InteractionArea
)
@onready var _shop_root: Node3D = %FishingShopWorld
@onready var _storage_root: Node3D = %PlayerStorageBox
@onready var _decor_shop_root: Node3D = %DecorShopWorld
@onready var _rv_upgrade_shop_root: Node3D = %RVUpgradeShopWorld
@onready var _decorations: Node3D = %Decorations
@onready var _tree_anchors: GatherableAnchorSet3D = %ReachableTreeTrunks
@onready var _diggable_beach: DiggableArea3D = %DiggableBeach
@ -104,6 +113,7 @@ 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] = {}
var _home_accessible_elevations_by_coordinate: Dictionary[Vector2i, Array] = {}
func _ready() -> void:
@ -159,6 +169,10 @@ func get_decor_shop() -> DecorShopInteractionType:
return _decor_shop
func get_rv_upgrade_shop() -> RVUpgradeInteractionType:
return _rv_upgrade_shop
func get_saltwater_shoreline_mesh() -> MeshInstance3D:
return _shoreline_reference
@ -184,6 +198,7 @@ func get_spawn_surface_triangles(
material_names: Array[StringName],
minimum_global_y: float,
minimum_up_dot: float = 0.6,
maximum_global_y: float = 100.0,
) -> Array[PackedVector3Array]:
var triangles: Array[PackedVector3Array] = []
if material_names.is_empty():
@ -204,10 +219,27 @@ func get_spawn_surface_triangles(
mesh.surface_get_arrays(surface_index),
minimum_global_y,
minimum_up_dot,
maximum_global_y,
)
return triangles
func is_home_exterior_position_accessible(position: Vector3) -> bool:
var local_position := _generator.to_local(position)
if local_position.y <= HOME_GROUND_HEIGHT_TOLERANCE:
return true
var coordinate := _generator.coordinate_for_local_position(local_position)
for value: Variant in _home_accessible_elevations_by_coordinate.get(
coordinate,
[],
):
if absf(local_position.y - float(value)) <= (
HOME_ELEVATION_HEIGHT_TOLERANCE
):
return true
return false
func get_water_recovery_position(
entry_position: Vector3,
fallback_position: Vector3,
@ -242,6 +274,7 @@ func get_water_recovery_position(
func _on_generation_completed(summary: Dictionary) -> void:
_diggable_beach.invalidate_surface_cache()
var records: Array[Dictionary] = _generator.placement_records()
_cache_home_accessible_elevations(records)
_assign_biomes(records, summary)
var spawn_position := Vector3.ZERO
_clear_children(_decorations)
@ -388,6 +421,97 @@ func _on_generation_completed(summary: Dictionary) -> void:
world_generated.emit(_current_seed, summary)
func _cache_home_accessible_elevations(
records: Array[Dictionary],
) -> void:
_home_accessible_elevations_by_coordinate.clear()
var records_by_coordinate: Dictionary[Vector2i, Dictionary] = {}
var elevation_tiers: Dictionary[int, bool] = {}
for record: Dictionary in records:
var coordinate: Vector2i = record.get(
"coordinate",
Vector2i(-1, -1),
)
records_by_coordinate[coordinate] = record
for tier: int in _elevation_tiers_for_tags(
record.get("tags", PackedStringArray())
):
elevation_tiers[tier] = true
var visited: Dictionary[String, bool] = {}
for tier: int in elevation_tiers:
var elevation_tag := "elevation_%d" % tier
for coordinate: Vector2i in records_by_coordinate:
var visit_key := _home_elevation_visit_key(coordinate, tier)
if visited.has(visit_key):
continue
var record: Dictionary = records_by_coordinate[coordinate]
var tags: PackedStringArray = record.get(
"tags",
PackedStringArray(),
)
if elevation_tag not in tags:
continue
var component: Array[Vector2i] = []
var pending: Array[Vector2i] = [coordinate]
var has_ramp := false
while not pending.is_empty():
var current: Vector2i = pending.pop_back()
var current_key := _home_elevation_visit_key(current, tier)
if visited.has(current_key):
continue
var current_record: Dictionary = records_by_coordinate.get(
current,
{},
)
var current_tags: PackedStringArray = current_record.get(
"tags",
PackedStringArray(),
)
if elevation_tag not in current_tags:
continue
visited[current_key] = true
component.append(current)
has_ramp = has_ramp or "ramp" in current_tags
for offset: Vector2i in [
Vector2i.LEFT,
Vector2i.RIGHT,
Vector2i.UP,
Vector2i.DOWN,
]:
pending.append(current + offset)
if not has_ramp:
continue
var surface_height := (
float(tier - 1) * _generator.elevated_cliff_level_height
)
for component_coordinate: Vector2i in component:
var heights: Array = (
_home_accessible_elevations_by_coordinate.get(
component_coordinate,
[],
)
)
heights.append(surface_height)
_home_accessible_elevations_by_coordinate[
component_coordinate
] = heights
func _elevation_tiers_for_tags(tags: PackedStringArray) -> Array[int]:
var tiers: Array[int] = []
for tag_value: String in tags:
if not tag_value.begins_with("elevation_"):
continue
var suffix := tag_value.trim_prefix("elevation_")
if suffix.is_valid_int():
tiers.append(suffix.to_int())
return tiers
func _home_elevation_visit_key(coordinate: Vector2i, tier: int) -> String:
return "%d:%d:%d" % [tier, coordinate.x, coordinate.y]
func _mark_precipitation_occluders() -> void:
for prop: Node in _decorations.get_children():
var prop_group := prop.get_meta(&"terrain_prop_group", &"") as StringName
@ -441,6 +565,8 @@ func _place_spawn_amenities(center: Vector3) -> void:
_storage_root.rotation.y = PI * 0.5
_decor_shop_root.position = center + Vector3(-2.4, 0.0, 1.2)
_decor_shop_root.rotation.y = PI * 0.5
_rv_upgrade_shop_root.position = center + Vector3(-2.35, 0.0, -1.35)
_rv_upgrade_shop_root.rotation.y = PI
func _configure_static_water() -> void:
@ -1304,6 +1430,7 @@ func _terrain_surface_triangles_by_coordinate(
mesh_instance.mesh.surface_get_arrays(surface_index),
-INF,
0.35,
100.0,
)
if triangles.is_empty():
continue
@ -1651,6 +1778,7 @@ func _append_surface_triangles(
arrays: Array,
minimum_global_y: float,
minimum_up_dot: float,
maximum_global_y: float,
) -> void:
if arrays.size() <= Mesh.ARRAY_INDEX:
return
@ -1660,10 +1788,28 @@ func _append_surface_triangles(
return
if indices.is_empty():
for index: int in range(0, vertices.size() - 2, 3):
_append_triangle(result, mesh_instance, vertices[index], vertices[index + 1], vertices[index + 2], minimum_global_y, minimum_up_dot)
_append_triangle(
result,
mesh_instance,
vertices[index],
vertices[index + 1],
vertices[index + 2],
minimum_global_y,
minimum_up_dot,
maximum_global_y,
)
return
for index: int in range(0, indices.size() - 2, 3):
_append_triangle(result, mesh_instance, vertices[indices[index]], vertices[indices[index + 1]], vertices[indices[index + 2]], minimum_global_y, minimum_up_dot)
_append_triangle(
result,
mesh_instance,
vertices[indices[index]],
vertices[indices[index + 1]],
vertices[indices[index + 2]],
minimum_global_y,
minimum_up_dot,
maximum_global_y,
)
func _append_triangle(
@ -1674,11 +1820,15 @@ func _append_triangle(
local_c: Vector3,
minimum_global_y: float,
minimum_up_dot: float,
maximum_global_y: float,
) -> void:
var a := mesh_instance.to_global(local_a)
var b := mesh_instance.to_global(local_b)
var c := mesh_instance.to_global(local_c)
if minf(a.y, minf(b.y, c.y)) <= minimum_global_y:
if (
maxf(a.y, maxf(b.y, c.y)) <= minimum_global_y
or minf(a.y, minf(b.y, c.y)) >= maximum_global_y
):
return
var cross := (b - a).cross(c - a)
if cross.length_squared() <= 0.0000001:

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=15 format=3]
[gd_scene load_steps=16 format=3]
[ext_resource type="Script" path="res://world/generation/generated_world_region.gd" id="1_region"]
[ext_resource type="Script" path="res://world/generation/terrain_chunk_generator.gd" id="2_generator"]
@ -14,6 +14,7 @@
[ext_resource type="Resource" path="res://world/generation/props/terrain_prop_catalog.tres" id="12_prop_catalog"]
[ext_resource type="Resource" path="res://world/generation/biomes/terrain_biome_catalog.tres" id="13_biome_catalog"]
[ext_resource type="PackedScene" path="res://world/interactables/decor_shop_world.tscn" id="14_decor_shop"]
[ext_resource type="PackedScene" path="res://world/interactables/rv_upgrade_shop_world.tscn" id="15_rv_upgrade"]
[node name="GeneratedWorldRegion" type="Node3D"]
script = ExtResource("1_region")
@ -102,6 +103,9 @@ unique_name_in_owner = true
[node name="DecorShopWorld" parent="Interactables" instance=ExtResource("14_decor_shop")]
unique_name_in_owner = true
[node name="RVUpgradeShopWorld" parent="Interactables" instance=ExtResource("15_rv_upgrade")]
unique_name_in_owner = true
[node name="Decorations" type="Node3D" parent="."]
unique_name_in_owner = true

View file

@ -4414,6 +4414,19 @@ func chunk_position(coordinate: Vector2i) -> Vector3:
)
func coordinate_for_local_position(position: Vector3) -> Vector2i:
if catalog == null or catalog.chunk_size <= 0.0:
return Vector2i(-1, -1)
var half_grid := Vector2(
float(grid_size.x - 1) * 0.5,
float(grid_size.y - 1) * 0.5,
)
return Vector2i(
roundi(position.x / catalog.chunk_size + half_grid.x),
roundi(position.z / catalog.chunk_size + half_grid.y),
)
func get_generated_chunks_root() -> Node3D:
return _generated_chunks