M2.5: wildlife, wolves, hunting, fishing and sleeping on the server
This commit is contained in:
@@ -19,7 +19,6 @@ signal cursor_changed(stack) ## client: the server says this is on your cur
|
||||
|
||||
const ServerPlayer = preload("res://scripts/net/server_player.gd")
|
||||
const STATS_INTERVAL := 0.25
|
||||
const NOT_YET := "Hunting and fishing aren't on servers yet (they come with the animals)"
|
||||
|
||||
# Server.
|
||||
var proxies: Dictionary = {} ## peer -> server_player.gd
|
||||
@@ -69,6 +68,10 @@ func server_join(peer: int, id: String, display: String) -> Vector3:
|
||||
p.inventory.changed.connect(func(): _dirty[peer] = true; WorldNet.mark_changed())
|
||||
p.worn_changed.connect(func(): _dirty[peer] = true)
|
||||
p.survival.died.connect(_on_died.bind(peer))
|
||||
# A wolf bite or a boar: their screen flashes red, from the right side.
|
||||
p.survival.hurt.connect(func(amount, from):
|
||||
if proxies.has(peer):
|
||||
_s2c_hurt.rpc_id(peer, amount, from))
|
||||
var at := Vector3.INF
|
||||
var d := _read(_file(id))
|
||||
if not d.is_empty():
|
||||
@@ -96,10 +99,14 @@ func server_leave(peer: int) -> void:
|
||||
if p.survival.dead:
|
||||
_respawn(peer, false) # left on the death screen: they come back alive
|
||||
_save(p)
|
||||
var w: Node3D = WorldNet.world
|
||||
if w != null and w.bobber_of(p) != null:
|
||||
w.end_fishing(p)
|
||||
proxies.erase(peer)
|
||||
_dirty.erase(peer)
|
||||
_acks.erase(peer)
|
||||
p.queue_free()
|
||||
_maybe_pass_night() # (the last one awake leaving lets the others sleep)
|
||||
|
||||
func save_all() -> void:
|
||||
for p in proxies.values():
|
||||
@@ -149,6 +156,9 @@ func server_moved(peer: int, from: Vector3, to: Vector3, yaw: float, anim: int,
|
||||
p.rotation.y = yaw
|
||||
if to.y < p.water_level - 0.6:
|
||||
p.survival.soak()
|
||||
if p.in_bed and to.distance_to(p.bed_at) > 1.5:
|
||||
p.in_bed = false
|
||||
tell(p.peer, "You got out of bed")
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not Net.is_server() or proxies.is_empty():
|
||||
@@ -356,8 +366,129 @@ func _run(p: Node3D, what: String, a: Array) -> void:
|
||||
"debug_drop":
|
||||
if cheats and not ItemDB.get_item(_s(a, 0)).is_empty():
|
||||
w.drop_item(_s(a, 0), maxi(_i(a, 1), 1), Transform3D(Basis(), _v(a, 2)))
|
||||
# --- hunting and fishing (M2.5) ---
|
||||
# Arrows, spears and casts start from where the server knows you are
|
||||
# (your eyes), whatever your game says: only the aim comes from you.
|
||||
"melee":
|
||||
var c: Node3D = CreatureNet.creature(_i(a, 0))
|
||||
if c != null:
|
||||
Actions.melee(p, c)
|
||||
"butcher":
|
||||
var c: Node3D = CreatureNet.creature(_i(a, 0))
|
||||
if c != null:
|
||||
Actions.butcher(p, c)
|
||||
"shoot":
|
||||
var dir := _dir(a, 0)
|
||||
var power := clampf(float(a[1]) if a.size() > 1 and (a[1] is float or a[1] is int) else 0.0, 0.0, 1.0)
|
||||
if dir != Vector3.ZERO and Actions.can_draw(p):
|
||||
Actions.shoot(p, _eye(p) + dir * 0.5, dir, power, w)
|
||||
"throw":
|
||||
var dir := _dir(a, 1)
|
||||
if dir != Vector3.ZERO:
|
||||
Actions.throw_spear(p, _i(a, 0), _eye(p) + dir * 0.5 + Vector3(0, -0.05, 0), dir + Vector3(0, 0.06, 0), w)
|
||||
"cast":
|
||||
var dir := _dir(a, 0)
|
||||
if dir != Vector3.ZERO:
|
||||
Actions.cast_line(p, _eye(p) + dir * 0.6, dir, w)
|
||||
"reel":
|
||||
Actions.reel(p, w)
|
||||
"debug_spawn":
|
||||
if cheats and Creature.KINDS.has(_s(a, 0)) and _s(a, 0) != "wolf" and _v(a, 1).is_finite():
|
||||
w.creatures.spawn(_s(a, 0), _v(a, 1))
|
||||
"debug_time":
|
||||
if cheats:
|
||||
w.day_night.day = maxi(_i(a, 0), 1)
|
||||
w.day_night.time = clampf(float(a[1]) / 24.0, 0.0, 0.999) if a.size() > 1 else w.day_night.time
|
||||
w.day_night.apply(true)
|
||||
w.weather.snap()
|
||||
Net.push_clock()
|
||||
"debug_tp":
|
||||
var to := _v(a, 0)
|
||||
if cheats and to.is_finite():
|
||||
p.global_position = to
|
||||
Net.teleport(p.peer, to)
|
||||
_s2c_respawn.rpc_id(p.peer, to, p.rotation.y, "")
|
||||
"debug_hut":
|
||||
if cheats and _v(a, 0).is_finite():
|
||||
_debug_hut(_v(a, 0))
|
||||
_:
|
||||
Actions.notify.emit(NOT_YET if what in ["hunt", "fish"] else "Unknown request")
|
||||
Actions.notify.emit("Unknown request")
|
||||
|
||||
const Creature = preload("res://scripts/creatures/creature.gd")
|
||||
|
||||
static func _eye(p: Node3D) -> Vector3:
|
||||
return p.global_position + Vector3(0, 1.6, 0)
|
||||
|
||||
## A unit aim direction from the request, or ZERO.
|
||||
static func _dir(a: Array, k: int) -> Vector3:
|
||||
var d := _v(a, k)
|
||||
return d.normalized() if d.is_finite() and d.length() > 0.01 else Vector3.ZERO
|
||||
|
||||
## Test hook: a closed hut (floors, walls, door, roof) with two beds in it.
|
||||
func _debug_hut(center: Vector3) -> void:
|
||||
var w: Node3D = WorldNet.world
|
||||
var out: Dictionary = await w.build.debug_build_hut(center, 0.0)
|
||||
if out.pieces.is_empty():
|
||||
return
|
||||
var top: float = out.pieces[0].global_position.y
|
||||
for x in [-0.6, 0.6]:
|
||||
w.spawn_piece(ItemDB.get_item("bed"), Transform3D(Basis(), Vector3(center.x + x, top, center.z - 0.3)))
|
||||
|
||||
# --- server: messages, getting hurt, sleeping ----------------------------------------
|
||||
|
||||
func tell(peer: int, text: String) -> void:
|
||||
if proxies.has(peer):
|
||||
_s2c_notify.rpc_id(peer, text)
|
||||
|
||||
func tell_all(text: String) -> void:
|
||||
for peer in proxies:
|
||||
_s2c_notify.rpc_id(peer, text)
|
||||
|
||||
## The server's sleep rule (server.json "sleep"): how many of the players
|
||||
## online must be in bed for the night to pass.
|
||||
func _beds_needed(n: int) -> int:
|
||||
match String(Net.config.v("sleep")):
|
||||
"anyone": return 1
|
||||
"half": return ceili(n / 2.0)
|
||||
return n # "all_online" (the default)
|
||||
|
||||
## Actions.use_bed decided `p` may sleep: they get into bed, and the night
|
||||
## passes once enough players are in bed.
|
||||
func go_to_bed(p: Node3D) -> void:
|
||||
p.in_bed = true
|
||||
p.bed_at = p.global_position
|
||||
if not _maybe_pass_night():
|
||||
var n: int = _alive().size()
|
||||
var asleep: int = _alive().filter(func(q): return q.in_bed).size()
|
||||
tell(p.peer, "You're in bed (%d of %d asleep). The night passes when %s." % [asleep, n,
|
||||
"everyone's in bed" if _beds_needed(n) >= n else "%d are" % _beds_needed(n)])
|
||||
for q in _alive():
|
||||
if q != p:
|
||||
tell(q.peer, "%s is in bed (%d of %d asleep)" % [p.display, asleep, n])
|
||||
|
||||
func _alive() -> Array:
|
||||
return proxies.values().filter(func(q): return not q.survival.dead)
|
||||
|
||||
## Everyone in bed (by the rule)? Then until 06:00 for all: the clock, fires,
|
||||
## regrowth and weather move on; sleepers wake rested.
|
||||
func _maybe_pass_night() -> bool:
|
||||
var w: Node3D = WorldNet.world
|
||||
var live := _alive()
|
||||
if w == null or live.is_empty():
|
||||
return false
|
||||
var asleep := live.filter(func(q): return q.in_bed)
|
||||
if asleep.size() < _beds_needed(live.size()):
|
||||
return false
|
||||
var secs: float = fposmod(6.0 - w.day_night.hours(), 24.0) / 24.0 * w.day_night.day_length
|
||||
w.skip_time(secs)
|
||||
for q in live:
|
||||
if q.in_bed:
|
||||
q.survival.after_sleep(secs)
|
||||
q.in_bed = false
|
||||
_s2c_slept.rpc_id(q.peer, asleep.has(q))
|
||||
Net.push_clock()
|
||||
WorldNet.save()
|
||||
return true
|
||||
|
||||
## The chest or grave `nid`, if this player may use it right now.
|
||||
func _container(p: Node3D, nid: int) -> Node3D:
|
||||
@@ -451,3 +582,22 @@ func _s2c_respawn(at: Vector3, yaw: float, text: String) -> void:
|
||||
@rpc("authority", "call_remote", "reliable")
|
||||
func _s2c_notify(text: String) -> void:
|
||||
Actions.notify.emit(text)
|
||||
|
||||
var hurts := 0 ## client: how often the server said we were hurt (tests read it)
|
||||
|
||||
@rpc("authority", "call_remote", "reliable")
|
||||
func _s2c_hurt(amount: float, from: Vector3) -> void:
|
||||
hurts += 1
|
||||
if world != null and is_instance_valid(world):
|
||||
world.player.survival.hurt.emit(amount, from)
|
||||
|
||||
## The night passed (we were asleep, or up while the others slept).
|
||||
@rpc("authority", "call_remote", "reliable")
|
||||
func _s2c_slept(was_asleep: bool) -> void:
|
||||
if world == null or not is_instance_valid(world):
|
||||
return
|
||||
var text := "You slept until morning" if was_asleep else "The night passed while the others slept"
|
||||
if world.hud != null and was_asleep:
|
||||
world.hud.fade(func(): Actions.notify.emit(text))
|
||||
else:
|
||||
Actions.notify.emit(text)
|
||||
|
||||
Reference in New Issue
Block a user