M2.5: wildlife, wolves, hunting, fishing and sleeping on the server

This commit is contained in:
Paul Bailey
2026-11-04 21:25:10 +00:00
parent 26be17b05f
commit a211fe4c63
20 changed files with 1258 additions and 88 deletions

View File

@@ -270,8 +270,6 @@ func respawn_from_server(at: Vector3, yaw: float) -> void:
## Why you can't sleep in `bed` right now ("" = you can).
func sleep_block_reason(bed: Node3D) -> String:
if is_online() or server_mode:
return "Sleeping on a server isn't in yet (everyone would have to agree)"
var h: float = day_night.hours()
if h > 5.5 and h < 19.5:
return "You can only sleep at night (after 19:30)"
@@ -284,7 +282,12 @@ func sleep_block_reason(bed: Node3D) -> String:
return ""
## Sleeps until 06:00: the clock, fires, regrowth and weather all move on.
## On a server you get into bed, and the night passes once enough players
## are in bed (the server's sleep rule, player_net.gd).
func sleep(p: Node3D) -> void:
if server_mode:
PlayerNet.go_to_bed(p)
return
var secs: float = fposmod(6.0 - day_night.hours(), 24.0) / 24.0 * day_night.day_length
if hud != null:
hud.fade(func(): _finish_sleep(p, secs))
@@ -335,7 +338,18 @@ func _on_terrain_ready() -> void:
var saved: Dictionary = Saves.load_state(Saves.current_id)
if not saved.is_empty():
apply_save(saved)
return # no player, no animals (the server runs those in M2.5)
# The server runs the wildlife and the wolves for everyone (M2.5):
# animals live around the players, and players see them via
# creature_net.gd.
set_physics_process(false) # (no player of our own to keep on the map)
creatures.setup(self)
creatures.enabled = not "--no-creatures" in OS.get_cmdline_user_args()
if den.found:
wolves = preload("res://scripts/creatures/wolf_pack.gd").new()
wolves.name = "Wolves"
add_child(wolves)
wolves.setup(self, den)
return
_respawn()
if Saves.has_world():
var state: Dictionary = Saves.load_state(Saves.current_id)
@@ -714,9 +728,9 @@ func _go_online() -> void:
props.regrow_enabled = false
WorldNet.join_world(self)
PlayerNet.join_world(self) # and our inventory and stats
CreatureNet.join_world(self) # the server's animals, wolves, arrows and floats
if hud != null:
var bar := UITheme.label("Online: %s · no animals, hunting or fishing on servers yet" \
% Net.server_info.get("server_name", "server"), 14, UITheme.TEXT_DIM)
var bar := UITheme.label("Online: %s" % Net.server_info.get("server_name", "server"), 14, UITheme.TEXT_DIM)
bar.position = Vector2(16, 40)
$HUD.add_child(bar)
Actions.notify.emit("Connected to %s" % Net.server_info.get("server_name", "the server"))
@@ -724,6 +738,7 @@ func _go_online() -> void:
func _on_net_left(_reason: String) -> void:
WorldNet.leave_world()
PlayerNet.leave_world()
CreatureNet.leave_world()
if is_inside_tree():
get_tree().paused = false
get_tree().auto_accept_quit = true
@@ -750,25 +765,69 @@ func _make_dropped(id: String, count: int, xf: Transform3D) -> Node3D:
return pk
## Casts a fishing float (Actions validated it). One at a time.
## One float per player (on a server, everyone can fish at once). `bobber`
## is the local player's.
func start_fishing(p: Node3D, origin: Vector3, vel: Vector3) -> Node3D:
end_fishing()
bobber = preload("res://scripts/fishing/bobber.gd").new()
bobber.setup(self, p, origin, vel, randi())
add_child(bobber)
return bobber
end_fishing(p)
var b: Node3D = preload("res://scripts/fishing/bobber.gd").new()
b.setup(self, p, origin, vel, randi())
add_child(b)
bobbers[p] = b
if p == player:
bobber = b
return b
func end_fishing() -> void:
if bobber != null and is_instance_valid(bobber):
bobber.queue_free()
bobber = null
func end_fishing(p: Node3D = null) -> void:
if p == null:
p = player
if is_online() and p == player and bobber != null:
PlayerNet.ask("reel") # (switched away from the rod: the server reels in)
var b: Node3D = bobbers.get(p)
bobbers.erase(p)
if b != null and is_instance_valid(b):
b.queue_free()
if p == player:
if bobber != null and is_instance_valid(bobber) and bobber != b:
bobber.queue_free() # (online: the server's float, shown here)
bobber = null
func bobber_of(p: Node3D) -> Node3D:
var b: Node3D = bobbers.get(p)
return b if b != null and is_instance_valid(b) else null
var bobbers: Dictionary = {} ## player -> their float
# --- who's here ------------------------------------------------------------
## Everyone animals react to: the local player in single-player; on a
## server, every player's character that's alive (player_net.gd).
func players() -> Array:
if server_mode:
return PlayerNet.proxies.values().filter(func(p): return is_instance_valid(p) and not p.survival.dead)
return [player]
## The nearest of them to `pos`, or null if nobody's around (a server with
## everyone dead or gone).
func nearest_player(pos: Vector3) -> Node3D:
if not server_mode:
return player
var best: Node3D = null
for p in players():
if best == null or p.global_position.distance_to(pos) < best.global_position.distance_to(pos):
best = p
return best
## Fires an arrow or thrown spear (Actions validated it).
func launch(id: String, origin: Vector3, vel: Vector3, damage: float, shooter: Node3D) -> Node3D:
var pr: Node3D = preload("res://scripts/creatures/projectile.gd").new()
pr.setup(id, self, origin, vel, damage, shooter)
add_child(pr)
if server_mode:
launched.emit(id, origin, vel, shooter) # everyone sees it fly (creature_net.gd)
return pr
signal launched(id: String, origin: Vector3, vel: Vector3, shooter: Node3D)
## Takes a piece out of the world (dismantled). Anything that loses its
## support collapses, and its item goes back to `refund_to` if there's room.
## Returns how many pieces collapsed.