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

@@ -65,11 +65,25 @@ func _ask(what: String, args: Array = []) -> bool:
PlayerNet.ask(what, args)
return true
const NOT_ONLINE := "Hunting and fishing aren't on servers yet (they come with the animals)"
## A message for one player: on screen in single-player; on a server, to
## that player's game (player_net.gd). For things that happen later than the
## request that caused them (a fish biting, an arrow landing a kill).
func tell(player: Node, text: String) -> void:
if Net.is_server() and player != null and player.get("peer") != null:
PlayerNet.tell(int(player.peer), text)
else:
notify.emit(text)
func _not_online() -> bool:
notify.emit(NOT_ONLINE)
return false
## A message for everyone (the wolves howling).
func tell_all(text: String) -> void:
if Net.is_server():
PlayerNet.tell_all(text)
else:
notify.emit(text)
## Online, a creature is known by its net id (creature_net.gd).
static func _cid(c: Node) -> int:
return int(c.get_meta("cid", 0))
func _in_reach(player: Node3D, target: Node3D, extra: float = 0.0) -> bool:
return player.global_position.distance_to(target.global_position) <= REACH + 2.5 + extra
@@ -251,7 +265,12 @@ func melee_stats(player: Node) -> Dictionary:
## One melee blow at an animal.
func melee(player: Node3D, target: Node3D) -> bool:
if _online():
return _not_online()
if not is_instance_valid(target) or not target.is_in_group("creature"):
return false
if not melee_stats(player).armed:
notify.emit(NEED_WEAPON)
return false
return _ask("melee", [_cid(target)])
if not is_instance_valid(target) or not target.is_in_group("creature"):
return false
var m := melee_stats(player)
@@ -275,12 +294,12 @@ func projectile_hit(shooter: Node3D, target: Node3D, damage: float, at: Vector3,
var was_alive: bool = not target.is_dead()
target.take_hit(damage, shooter.global_position if shooter != null else at, embed_id)
if was_alive and target.is_dead():
notify.emit("Killed a %s" % target.display_name().to_lower())
tell(shooter, "Killed a %s" % target.display_name().to_lower())
## Throws the spear in `slot` from `origin` along `dir`.
func throw_spear(player: Node3D, slot: int, origin: Vector3, dir: Vector3, world: Node3D) -> bool:
if _online():
return _not_online()
return _ask("throw", [slot, dir])
var s = player.inventory.get_slot(slot)
if s == null:
return false
@@ -298,9 +317,15 @@ func throw_spear(player: Node3D, slot: int, origin: Vector3, dir: Vector3, world
## Starts drawing the held bow: needs arrows and a little stamina.
func can_draw(player: Node3D) -> bool:
if _online():
return _not_online()
var bow: Dictionary = ItemDB.get_item(player.held_item_id()).get("bow", {})
if _online():
# (The server checks arrows and spends the stamina when you shoot.)
if bow.is_empty():
return false
if player.inventory.count(bow.ammo) <= 0:
notify.emit("No arrows (craft them at a workbench: sticks, flint, feathers)")
return false
return true
if bow.is_empty():
return false
if player.inventory.count(bow.ammo) <= 0:
@@ -314,7 +339,7 @@ func can_draw(player: Node3D) -> bool:
## Looses an arrow. `power` 0..1 is how far the bow was drawn.
func shoot(player: Node3D, origin: Vector3, dir: Vector3, power: float, world: Node3D) -> bool:
if _online():
return _not_online()
return _ask("shoot", [dir, power])
var bow: Dictionary = ItemDB.get_item(player.held_item_id()).get("bow", {})
if bow.is_empty() or player.global_position.distance_to(origin) > 2.5:
return false
@@ -328,7 +353,9 @@ func shoot(player: Node3D, origin: Vector3, dir: Vector3, power: float, world: N
func butcher(player: Node3D, carcass: Node3D) -> bool:
if _online():
return _not_online()
if not is_instance_valid(carcass) or not carcass.is_in_group("carcass"):
return false
return _ask("butcher", [_cid(carcass)])
if not is_instance_valid(carcass) or not carcass.is_in_group("carcass") or not _in_reach(player, carcass, 1.0):
return false
var drops: Dictionary = carcass.butcher_drops()
@@ -346,7 +373,9 @@ func butcher(player: Node3D, carcass: Node3D) -> bool:
## Casts the held rod from `origin` along `dir`.
func cast_line(player: Node3D, origin: Vector3, dir: Vector3, world: Node3D) -> bool:
if _online():
return _not_online()
if ItemDB.get_item(player.held_item_id()).get("fishing", {}).is_empty():
return false
return _ask("cast", [dir])
var rod: Dictionary = ItemDB.get_item(player.held_item_id()).get("fishing", {})
if rod.is_empty() or player.global_position.distance_to(origin) > 2.5:
return false
@@ -361,14 +390,14 @@ func cast_line(player: Node3D, origin: Vector3, dir: Vector3, world: Node3D) ->
## Reels in. With a fish biting you land it; otherwise the line just comes in.
func reel(player: Node3D, world: Node3D) -> bool:
if _online():
return _not_online()
var b: Node3D = world.bobber
return _ask("reel")
var b: Node3D = world.bobber_of(player)
if b == null or not is_instance_valid(b):
return false
var caught := ""
if b.state == "biting":
caught = b.fish
world.end_fishing()
world.end_fishing(player)
if caught.is_empty():
return false
if _give(player, caught, 1) == 0: