straywild/world/test_world.gd
Voyager 3b84bfe3a0 feat: expand progression and multiplayer systems
Add named save slots, progression import/export, and a unified play flow. Add live friend requests, presence, invitations, and relationship controls without durable discovery-server social storage. Advance the network protocol with isolated channels, movement reconciliation, late-join recovery, fishing replication, and animation synchronization. Preserve per-species catch totals, refine generated-world startup and water recovery, and complete the related input and interface improvements.
2026-08-23 20:49:54 -04:00

291 lines
8.5 KiB
GDScript

class_name TestWorld
extends Node3D
const FishingShopInteractionType = preload(
"res://world/fishing_shop_interaction.gd"
)
const PlayerStorageInteractionType = preload(
"res://world/player_storage_interaction.gd"
)
const WorldLayoutType = preload("res://world/world_layout.gd")
const GENERATED_REGION_SCENE: PackedScene = preload(
"res://world/generation/generated_world_region.tscn"
)
const STARTER_ISLAND_REGION_SCENE: PackedScene = preload(
"res://world/regions/starter_island_region.tscn"
)
## Keep the player boundary beyond the longest fishing cast. The boundary is
## solid terrain to character movement and therefore also blocks cast-arc
## probes when it sits directly against the authored shoreline.
const WORLD_BOUNDARY_SHORELINE_CLEARANCE := 18.0
@onready var _regions_root: Node3D = $Regions
@onready var _active_region: WorldRegion = _find_active_region()
@onready var _below_world_failsafe: PlayerWaterTrigger = (
$Safety/BelowWorldFailsafe
)
@onready var _world_environment: WorldEnvironment = $Environment/WorldEnvironment
@onready var _sun: DirectionalLight3D = $Environment/Sun
var _world_layout: StringName = WorldLayoutType.GENERATED
var _world_seed: int = PlayerSaveManager.DEFAULT_WORLD_SEED
var _light_performance_profile: bool = false
var _dedicated_simulation: bool = false
func get_player_water_triggers() -> Array[PlayerWaterTrigger]:
var triggers: Array[PlayerWaterTrigger] = []
if _active_region != null:
triggers.append_array(_active_region.get_water_recovery_triggers())
triggers.append(_below_world_failsafe)
return triggers
func get_safe_respawn_points() -> Array[SafeRespawnPoint]:
return (
_active_region.get_safe_respawn_points()
if _active_region != null
else []
)
func get_fishing_shop() -> FishingShopInteractionType:
return _active_region.get_fishing_shop()
func get_player_storage() -> PlayerStorageInteractionType:
return _active_region.get_player_storage()
func get_player_spawn_transform() -> Transform3D:
return _active_region.get_player_spawn_transform()
func get_world_environment() -> WorldEnvironment:
return _world_environment
func get_sun() -> DirectionalLight3D:
return _sun
func set_light_performance_profile(enabled: bool) -> void:
_light_performance_profile = enabled
if _active_region != null:
_active_region.set_light_performance_profile(enabled)
func set_dedicated_simulation(enabled: bool) -> void:
_dedicated_simulation = enabled
if _active_region != null:
_set_region_presentation_enabled(_active_region, not enabled)
_world_environment.process_mode = (
Node.PROCESS_MODE_DISABLED if enabled else Node.PROCESS_MODE_INHERIT
)
_sun.visible = not enabled
_sun.process_mode = (
Node.PROCESS_MODE_DISABLED if enabled else Node.PROCESS_MODE_INHERIT
)
func _set_region_presentation_enabled(
region: WorldRegion,
enabled: bool,
) -> void:
for class_name_value: String in [
"WaterSurfaceMotion",
"LocalStormCloudLayer",
"WorldCharacterDisplay",
]:
for value: Node in region.find_children(
"*", class_name_value, true, false
):
value.process_mode = (
Node.PROCESS_MODE_INHERIT
if enabled
else Node.PROCESS_MODE_DISABLED
)
for value: Node in region.find_children("*", "GeometryInstance3D", true, false):
(value as GeometryInstance3D).visible = enabled
for value: Node in region.find_children("*", "GPUParticles3D", true, false):
var particles := value as GPUParticles3D
particles.emitting = enabled
particles.process_mode = (
Node.PROCESS_MODE_INHERIT if enabled else Node.PROCESS_MODE_DISABLED
)
for value: Node in region.find_children("*", "AnimationPlayer", true, false):
var animation_player := value as AnimationPlayer
animation_player.active = enabled
animation_player.process_mode = (
Node.PROCESS_MODE_INHERIT if enabled else Node.PROCESS_MODE_DISABLED
)
for class_name_value: String in ["AudioStreamPlayer", "AudioStreamPlayer3D"]:
for value: Node in region.find_children(
"*", class_name_value, true, false
):
value.call("stop")
value.process_mode = (
Node.PROCESS_MODE_INHERIT
if enabled
else Node.PROCESS_MODE_DISABLED
)
func get_fishable_water_regions() -> Array[FishableWaterRegion]:
return (
_active_region.get_fishable_water_regions()
if _active_region != null
else []
)
func get_saltwater_shoreline_mesh() -> MeshInstance3D:
return _active_region.get_saltwater_shoreline_mesh()
func get_spawn_surface_triangles(
material_names: Array[StringName],
minimum_global_y: float,
) -> Array[PackedVector3Array]:
return _active_region.get_spawn_surface_triangles(
material_names,
minimum_global_y,
)
func activate_world(layout: StringName, seed: int) -> bool:
if (
not WorldLayoutType.is_valid(layout)
or seed <= 0
or seed > PlayerSaveManager.MAX_WORLD_SEED
):
return false
if _active_region == null or _active_region.region_id != layout:
if not _replace_active_region(layout, seed):
return false
_world_layout = layout
_world_seed = seed
if layout == WorldLayoutType.GENERATED:
if not _active_region.has_method("generate_world"):
return false
if not bool(_active_region.call("generate_world", seed)):
return false
_configure_world_coverage()
return true
func generate_world(seed: int) -> bool:
return activate_world(WorldLayoutType.GENERATED, seed)
func get_world_layout() -> StringName:
return _world_layout
func get_generation_seed() -> int:
return _world_seed
func is_world_ready() -> bool:
if _active_region == null:
return false
if _world_layout != WorldLayoutType.GENERATED:
return true
return (
_active_region.has_method(&"is_world_generated")
and bool(_active_region.call(&"is_world_generated"))
)
func get_diggable_area_triangles(
area_id: StringName,
) -> Array[PackedVector3Array]:
if _active_region != null:
var area: DiggableArea3D = _active_region.get_diggable_area(area_id)
if area != null:
return area.get_surface_triangles()
return []
func get_gatherable_spawn_positions(
anchor_set_id: StringName,
) -> PackedVector3Array:
if _active_region != null:
var anchor_set: GatherableAnchorSet3D = (
_active_region.get_gatherable_anchor_set(anchor_set_id)
)
if anchor_set != null:
return anchor_set.get_spawn_positions()
return PackedVector3Array()
func _ready() -> void:
if _active_region != null:
_world_layout = _active_region.region_id
_configure_world_coverage()
func _find_active_region() -> WorldRegion:
for child: Node in _regions_root.get_children():
var region := child as WorldRegion
if region != null:
return region
return null
func _replace_active_region(layout: StringName, seed: int) -> bool:
var region_scene: PackedScene = (
GENERATED_REGION_SCENE
if layout == WorldLayoutType.GENERATED
else STARTER_ISLAND_REGION_SCENE
)
var replacement := region_scene.instantiate() as WorldRegion
if replacement == null:
return false
if layout == WorldLayoutType.GENERATED:
replacement.set("initial_seed", seed)
if _active_region != null:
_regions_root.remove_child(_active_region)
_active_region.free()
_active_region = replacement
_regions_root.add_child(_active_region)
_active_region.set_light_performance_profile(_light_performance_profile)
if _dedicated_simulation:
_set_region_presentation_enabled(_active_region, false)
return true
func _configure_world_coverage() -> void:
if _active_region == null:
return
var half: Vector2 = _active_region.get_playable_half_extents()
var wall_margin := WORLD_BOUNDARY_SHORELINE_CLEARANCE
var wall_height := 14.0
var bounds_root := $WorldBounds as Node3D
var north_south_size := Vector3(
half.x * 2.0 + wall_margin * 2.0,
wall_height,
2.0,
)
var east_west_size := Vector3(
2.0,
wall_height,
half.y * 2.0 + wall_margin * 2.0,
)
_set_bound(bounds_root.get_node("North"), Vector3(0.0, 5.0, half.y + wall_margin), north_south_size)
_set_bound(bounds_root.get_node("South"), Vector3(0.0, 5.0, -half.y - wall_margin), north_south_size)
_set_bound(bounds_root.get_node("West"), Vector3(-half.x - wall_margin, 5.0, 0.0), east_west_size)
_set_bound(bounds_root.get_node("East"), Vector3(half.x + wall_margin, 5.0, 0.0), east_west_size)
_below_world_failsafe.position = Vector3(0.0, -7.0, 0.0)
var coverage := _below_world_failsafe.get_node("Coverage") as CollisionShape3D
var coverage_shape := coverage.shape as BoxShape3D
if coverage_shape != null:
coverage_shape.size = Vector3(half.x * 2.0, 4.0, half.y * 2.0)
func _set_bound(body: Node3D, position: Vector3, size: Vector3) -> void:
body.position = position
var collision := body.get_node("Shape") as CollisionShape3D
var shape := collision.shape as BoxShape3D
if shape != null:
shape.size = size