feat: expand seasonal creature collection

This commit is contained in:
Alexander Sellite 2026-08-18 21:20:02 -04:00
parent 925bbb79e5
commit 95c6b8b053
415 changed files with 1688 additions and 389 deletions

View file

@ -1,7 +1,7 @@
class_name NetworkProtocol
extends RefCounted
const PROTOCOL_VERSION: int = 7
const PROTOCOL_VERSION: int = 8
const GAME_BUILD: String = "prealpha"
const MAX_GAME_VERSION_LENGTH: int = 64
const MAX_DISPLAY_NAME_LENGTH: int = 24
@ -22,7 +22,7 @@ const ENET_CHANNEL_COUNT: int = 10
const SURFACE_DRAWING_CAPABILITY: String = "surface_drawing_v2"
const ART_SHOP_CAPABILITY: String = "art_shop_v1"
const BACKPACK_SHOP_CAPABILITY: String = "backpack_shop_v1"
const WORLD_TIME_CAPABILITY: String = "world_time_v1"
const WORLD_TIME_CAPABILITY: String = "world_time_v2"
const WORLD_WEATHER_CAPABILITY: String = "world_weather_v1"
const FISH_QUALITY_CAPABILITY: String = "fish_quality_v1"
const JOBS_CAPABILITY: String = "jobs_v1"

View file

@ -11,6 +11,7 @@ const GatherableCatalogType = preload("res://gathering/gatherable_catalog.gd")
const GatherableDataType = preload("res://gathering/gatherable_data.gd")
const WorldGatherableType = preload("res://gathering/world_gatherable.gd")
const TestWorldType = preload("res://world/test_world.gd")
const CalendarSeasonType = preload("res://world/calendar_season.gd")
signal local_capture_received(fish_catch: FishCatch)
signal local_interaction_finished(accepted: bool, message: String)
@ -24,6 +25,7 @@ var _session: NetworkSession
var _spawn_service: PlayerSpawnService
var _world: TestWorldType
var _world_root: Node3D
var _world_time: WorldTimeService
var _catalog: GatherableCatalogType
var _fish_catalog: FishPoolType
var _local_inventory: FishInventory
@ -65,6 +67,7 @@ func setup(
local_experience: PlayerExperience,
save_manager: PlayerSaveManager,
item_use: NetworkItemUseService,
world_time: WorldTimeService = null,
) -> void:
_session = session
_spawn_service = spawn_service
@ -78,10 +81,18 @@ func setup(
_local_experience = local_experience
_save_manager = save_manager
_item_use = item_use
_world_time = world_time
_rng.randomize()
_session.peer_authenticated.connect(_on_peer_authenticated)
_session.peer_removed.connect(_on_peer_removed)
_session.state_changed.connect(_on_session_state_changed)
if (
_world_time != null
and not _world_time.calendar_date_changed.is_connected(
_on_calendar_date_changed
)
):
_world_time.calendar_date_changed.connect(_on_calendar_date_changed)
set_physics_process(true)
_begin_population_if_ready.call_deferred()
@ -193,7 +204,9 @@ func get_charge_duration_for_tool(tool_id: StringName) -> float:
var duration: float = 0.0
if _catalog == null or tool_id.is_empty():
return duration
for entry: GatherableDataType in _catalog.get_available_entries():
for entry: GatherableDataType in _catalog.get_available_entries(
_current_season()
):
if entry.required_tool_id == tool_id:
duration = maxf(duration, entry.charge_duration)
return duration
@ -240,7 +253,9 @@ func _begin_population_if_ready() -> void:
return
_clear_world()
_population_session_id = session_id
for entry: GatherableDataType in _catalog.get_available_entries():
for entry: GatherableDataType in _catalog.get_available_entries(
_current_season()
):
_cache_spawn_surface(entry)
for _spawn_index: int in entry.population:
_spawn_entity(entry)
@ -557,7 +572,7 @@ func _update_respawns() -> void:
var entry: GatherableDataType = _catalog.get_entry(
type_id
)
if entry != null and entry.is_available():
if entry != null and entry.is_available(_current_season()):
_spawn_entity(entry)
_next_respawn_by_type[type_id] = (
now + entry.minimum_respawn_spacing_seconds
@ -1193,6 +1208,61 @@ func _on_session_state_changed(_state: NetworkSession.State) -> void:
_population_session_id = ""
func _on_calendar_date_changed(_date_id: String) -> void:
if (
_session == null
or not _session.is_host()
or not _session.is_gameplay_session_active()
or _population_session_id.is_empty()
):
return
_reconcile_seasonal_population()
func _reconcile_seasonal_population() -> void:
if _catalog == null:
return
var season: int = _current_season()
for entity_id: String in _entities.keys():
var state: Dictionary = _entities.get(entity_id, {})
var entry := state.get("data") as GatherableDataType
if entry != null and not entry.is_available(season):
_despawn_entity(entity_id, &"season_changed", false, false)
var retained_respawns: Array[Dictionary] = []
for respawn: Dictionary in _respawns:
var entry: GatherableDataType = _catalog.get_entry(
StringName(str(respawn.get("type_id", "")))
)
if entry != null and entry.is_available(season):
retained_respawns.append(respawn)
_respawns = retained_respawns
for entry: GatherableDataType in _catalog.get_available_entries(season):
_cache_spawn_surface(entry)
var current_population: int = _population_for_type(entry.type_id)
while current_population < entry.population:
_spawn_entity(entry)
current_population += 1
func _population_for_type(type_id: StringName) -> int:
var count: int = 0
for state: Dictionary in _entities.values():
if StringName(str(state.get("type_id", ""))) == type_id:
count += 1
for respawn: Dictionary in _respawns:
if StringName(str(respawn.get("type_id", ""))) == type_id:
count += 1
return count
func _current_season() -> int:
return (
_world_time.get_season()
if _world_time != null
else CalendarSeasonType.UNKNOWN
)
func _clear_world() -> void:
for presentation: WorldGatherableType in _presentations.values():
if presentation != null and is_instance_valid(presentation):

View file

@ -23,6 +23,12 @@ func setup(session: NetworkSession, world_time: WorldTimeService) -> void:
_world_time.authoritative_time_set.connect(
_on_authoritative_time_set
)
if not _world_time.calendar_date_changed.is_connected(
_on_authoritative_calendar_date_changed
):
_world_time.calendar_date_changed.connect(
_on_authoritative_calendar_date_changed
)
set_process(true)
@ -90,6 +96,13 @@ func _on_authoritative_time_set(_time_hours: float) -> void:
_broadcast_snapshot()
func _on_authoritative_calendar_date_changed(_date_id: String) -> void:
if _session == null or not _session.is_host():
return
_sync_elapsed = 0.0
_broadcast_snapshot()
func _broadcast_snapshot() -> void:
if _session == null or not _session.is_host():
return
@ -107,6 +120,7 @@ func _send_snapshot(peer_id: int) -> void:
receive_world_time_snapshot.rpc_id(peer_id, {
"session_id": _session.get_session_id(),
"time_hours": _world_time.get_time_hours(),
"calendar_date_id": _world_time.get_calendar_date_id(),
"sequence": _sequence,
})
@ -128,7 +142,10 @@ func receive_world_time_snapshot(data: Dictionary) -> void:
if sequence <= _last_received_sequence:
return
_last_received_sequence = sequence
_world_time.synchronize_time(float(data["time_hours"]))
_world_time.synchronize_calendar_time(
float(data["time_hours"]),
str(data["calendar_date_id"]),
)
static func validate_snapshot(data: Variant) -> bool:
@ -143,6 +160,10 @@ static func validate_snapshot(data: Variant) -> bool:
and is_finite(float(value["time_hours"]))
and float(value["time_hours"]) >= 0.0
and float(value["time_hours"]) < WorldTimeService.HOURS_PER_DAY
and typeof(value.get("calendar_date_id")) == TYPE_STRING
and WorldTimeService.is_valid_calendar_date_id(
str(value["calendar_date_id"])
)
and typeof(value.get("sequence")) == TYPE_INT
and int(value["sequence"]) >= 0
)