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.
This commit is contained in:
Alexander Sellite 2026-08-23 20:48:38 -04:00
parent 1db1a5b754
commit 3b84bfe3a0
97 changed files with 7869 additions and 982 deletions

View file

@ -79,8 +79,6 @@ func _ready() -> void:
_validate_biome_catalog()
_configure_static_water()
_build_shoreline_reference()
if not generate_world(initial_seed):
push_error("The initial generated world could not be built.")
func generate_world(seed: int) -> bool:
@ -100,6 +98,10 @@ func get_generation_seed() -> int:
return _current_seed
func is_world_generated() -> bool:
return is_instance_valid(_generator.get_generated_chunks_root())
func get_playable_half_extents() -> Vector2:
var size := Vector2(
float(_generator.grid_size.x),

View file

@ -25,6 +25,7 @@ const MAX_PACKED_SOLVER_VARIANTS := 62
@export var generation_seed := 13001
@export var generate_on_ready := true
@export var build_collision := true
@export_range(1, 16, 1) var collision_batch_size := 4
@export var show_chunk_labels := false
@export var force_center_chunk_id: StringName = &"chunk_0000"
@export var required_chunk_ids := PackedStringArray()
@ -3928,9 +3929,102 @@ func _build_solution_root() -> Node3D:
if not _add_stacked_elevated_chunks(solution_root):
solution_root.free()
return null
if build_collision and collision_batch_size > 1:
_batch_generated_terrain_collision(solution_root)
return solution_root
func _batch_generated_terrain_collision(solution_root: Node3D) -> void:
var shapes_by_batch: Dictionary[Vector2i, Array] = {}
var host_by_batch: Dictionary[Vector2i, Node3D] = {}
var bodies_to_remove: Array[StaticBody3D] = []
for chunk_node: Node in solution_root.get_children():
var chunk_root := chunk_node as Node3D
if chunk_root == null:
continue
var coordinate: Vector2i = chunk_root.get_meta(
&"terrain_chunk_coordinate",
Vector2i.ZERO,
)
var batch_coordinate := Vector2i(
floori(float(coordinate.x) / float(collision_batch_size)),
floori(float(coordinate.y) / float(collision_batch_size)),
)
if not host_by_batch.has(batch_coordinate):
host_by_batch[batch_coordinate] = chunk_root
var batch_shapes: Array = shapes_by_batch.get(
batch_coordinate,
[],
)
for value: Node in chunk_root.find_children(
"TerrainShape",
"CollisionShape3D",
true,
false,
):
var collision_shape := value as CollisionShape3D
if collision_shape == null:
continue
var collision_body := collision_shape.get_parent() as StaticBody3D
if collision_shape.shape == null or collision_body == null:
continue
var shape_to_solution := _transform_to_ancestor(
collision_shape,
solution_root,
)
batch_shapes.append({
"shape": collision_shape.shape,
"transform": shape_to_solution,
})
if collision_body not in bodies_to_remove:
bodies_to_remove.append(collision_body)
shapes_by_batch[batch_coordinate] = batch_shapes
while not bodies_to_remove.is_empty():
var collision_body: StaticBody3D = bodies_to_remove.pop_back()
collision_body.free()
for batch_coordinate: Vector2i in shapes_by_batch:
var batch_shapes: Array = shapes_by_batch[batch_coordinate]
if batch_shapes.is_empty():
continue
var host_root: Node3D = host_by_batch.get(batch_coordinate)
if host_root == null:
continue
var body := StaticBody3D.new()
body.name = "TerrainCollisionBatch_%d_%d" % [
batch_coordinate.x,
batch_coordinate.y,
]
body.collision_layer = 1
body.collision_mask = 0
body.set_meta(&"terrain_collision_batch", batch_coordinate)
host_root.add_child(body)
var solution_to_body: Transform3D = host_root.transform.affine_inverse()
for shape_index: int in batch_shapes.size():
var record: Dictionary = batch_shapes[shape_index]
var collision := CollisionShape3D.new()
collision.name = "TerrainShape_%d" % shape_index
collision.shape = record.get("shape") as Shape3D
collision.transform = (
solution_to_body
* (record.get("transform", Transform3D.IDENTITY) as Transform3D)
)
body.add_child(collision)
static func _transform_to_ancestor(
node: Node3D,
ancestor: Node3D,
) -> Transform3D:
var result := Transform3D.IDENTITY
var current: Node3D = node
while current != ancestor:
result = current.transform * result
current = current.get_parent() as Node3D
if current == null:
return Transform3D.IDENTITY
return result
func _add_stacked_elevated_chunks(solution_root: Node3D) -> bool:
for index: int in _stacked_elevated_placements.size():
var record := _stacked_elevated_placements[index]