M2.4: the server decides online (characters, inventory, stats, death, owned graves, per-player saves); death screen with Respawn after 3 s
This commit is contained in:
@@ -65,6 +65,14 @@ var bed_spawn := Vector3.ZERO ## position of the bed you respawn at
|
||||
func is_online() -> bool:
|
||||
return not menu_mode and Net.state == "connected"
|
||||
var _loading := false ## applying a save: defer support recompute
|
||||
var _applying := false ## applying a save or snapshot: not news for the server's players
|
||||
|
||||
## Server only: changes to the island, wherever they came from (a player's
|
||||
## request, a collapse, a death), for world_net.gd to send to everyone.
|
||||
signal piece_added(piece: Node3D)
|
||||
signal pieces_removed(gone: Array, fell: Array)
|
||||
signal dropped_added(item: Node3D)
|
||||
signal saved
|
||||
|
||||
func _ready() -> void:
|
||||
_autosave_left = autosave_seconds
|
||||
@@ -151,12 +159,15 @@ func air_warmth(pos: Vector3, sheltered: bool = false) -> float:
|
||||
return w
|
||||
|
||||
## 0..1 heat from fires and a held torch.
|
||||
func heat_at(pos: Vector3) -> float:
|
||||
## `holder` is whose torch counts (default: the local player; on a server,
|
||||
## each player's character).
|
||||
func heat_at(pos: Vector3, holder: Node = null) -> float:
|
||||
var h := 0.0
|
||||
for n in get_tree().get_nodes_in_group("heat"):
|
||||
h = maxf(h, n.heat_at(pos))
|
||||
if player.hands != null:
|
||||
h = maxf(h, player.hands.held_heat())
|
||||
var who: Node = holder if holder != null else player
|
||||
if who.get("hands") != null:
|
||||
h = maxf(h, who.hands.held_heat())
|
||||
return h
|
||||
|
||||
## Death: everything you carried goes into a grave where you fell, and you
|
||||
@@ -176,16 +187,34 @@ func _on_player_died() -> void:
|
||||
if st != null:
|
||||
has_items = true
|
||||
if has_items:
|
||||
var gxf := Transform3D(Basis(Vector3.UP, player.rotation.y), pos)
|
||||
if net_sync != null:
|
||||
WorldNet.place("grave", gxf, player.inventory.to_data()) # the server puts it in the shared world
|
||||
else:
|
||||
var grave := spawn_piece(ItemDB.get_item("grave"), gxf)
|
||||
grave.inventory.load_data(player.inventory.to_data())
|
||||
var grave := spawn_piece(ItemDB.get_item("grave"), Transform3D(Basis(Vector3.UP, player.rotation.y), pos))
|
||||
grave.inventory.load_data(player.inventory.to_data())
|
||||
player.inventory.load_data(Inventory.new(player.inventory.size()).to_data())
|
||||
# The death screen: you stay down for a few seconds, then Respawn.
|
||||
# (Headless, with no screen, you wake straight away.)
|
||||
dead_waiting = true
|
||||
player.velocity = Vector3.ZERO
|
||||
player.set_physics_process(false)
|
||||
if hud != null:
|
||||
hud.close_screens()
|
||||
var bed := spawn_bed() if net_sync == null else null # (online: beds aren't shared until M2.4)
|
||||
hud.show_death(player.survival.cause, _death_note(has_items, spawn_bed() != null), RESPAWN_DELAY, respawn_after_death)
|
||||
else:
|
||||
respawn_after_death()
|
||||
|
||||
const RESPAWN_DELAY := 3.0 ## seconds on the death screen before you can respawn
|
||||
var dead_waiting := false ## dead, on the death screen
|
||||
|
||||
static func _death_note(has_items: bool, to_bed: bool) -> String:
|
||||
return "%s\nYou'll wake %s." % ["Your belongings are in a grave where you fell." if has_items else "You had nothing on you.",
|
||||
"at your bed" if to_bed else "at the island start"]
|
||||
|
||||
## Respawn (the death screen's button): at your bed, or the island start.
|
||||
func respawn_after_death() -> void:
|
||||
if not dead_waiting:
|
||||
return
|
||||
dead_waiting = false
|
||||
player.set_physics_process(true)
|
||||
var bed := spawn_bed()
|
||||
if bed != null:
|
||||
_respawn_at_bed(bed)
|
||||
else:
|
||||
@@ -194,7 +223,17 @@ func _on_player_died() -> void:
|
||||
has_bed_spawn = false
|
||||
Actions.notify.emit("Your bed is gone, so you woke at the island start")
|
||||
player.survival.reset_after_death()
|
||||
Actions.notify.emit("You died. Your belongings are in your grave." if has_items else "You died.")
|
||||
|
||||
## Online: the server says we died. Same screen; Respawn asks the server.
|
||||
func died_online(text: String, has_items: bool, to_bed: bool) -> void:
|
||||
dead_waiting = true
|
||||
player.velocity = Vector3.ZERO
|
||||
player.set_physics_process(false)
|
||||
if hud != null:
|
||||
hud.close_screens()
|
||||
hud.show_death(text, _death_note(has_items, to_bed), RESPAWN_DELAY, func(): PlayerNet.ask("respawn"))
|
||||
else:
|
||||
get_tree().create_timer(RESPAWN_DELAY + 0.1).timeout.connect(func(): PlayerNet.ask("respawn"))
|
||||
|
||||
# --- beds and sleeping ------------------------------------------------------
|
||||
|
||||
@@ -217,9 +256,21 @@ func _respawn_at_bed(bed: Node3D) -> void:
|
||||
player.rotation = Vector3(0.0, bed.global_basis.get_euler().y + PI, 0.0)
|
||||
player.get_node("Camera3D").rotation.x = 0.0
|
||||
|
||||
## Online: the server says we died (or were moved) and puts us here.
|
||||
func respawn_from_server(at: Vector3, yaw: float) -> void:
|
||||
dead_waiting = false
|
||||
player.set_physics_process(true)
|
||||
if hud != null:
|
||||
hud.close_screens()
|
||||
hud.hide_death()
|
||||
player.global_position = at
|
||||
player.velocity = Vector3.ZERO
|
||||
player.rotation = Vector3(0.0, yaw, 0.0)
|
||||
player.get_node("Camera3D").rotation.x = 0.0
|
||||
|
||||
## Why you can't sleep in `bed` right now ("" = you can).
|
||||
func sleep_block_reason(bed: Node3D) -> String:
|
||||
if is_online():
|
||||
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:
|
||||
@@ -334,12 +385,17 @@ func save_now() -> bool:
|
||||
_autosave_left = autosave_seconds
|
||||
if not Saves.has_world():
|
||||
return false
|
||||
if dead_waiting and not server_mode:
|
||||
respawn_after_death() # never save someone lying dead
|
||||
if hud != null:
|
||||
hud.hide_death()
|
||||
var ok: bool = Saves.save_state(Saves.current_id, collect_save(), _session_time)
|
||||
if ok:
|
||||
_session_time = 0.0
|
||||
if server_mode:
|
||||
if not ok:
|
||||
printerr("[server] saving the world FAILED")
|
||||
saved.emit() # (the players' characters save with it)
|
||||
else:
|
||||
Actions.notify.emit("Game saved" if ok else "Save FAILED")
|
||||
return ok
|
||||
@@ -502,6 +558,11 @@ func remove_pieces_net(gone: Array, collapsed_nids: Array) -> void:
|
||||
_update_grass_cuts()
|
||||
|
||||
func apply_save(s: Dictionary, with_player: bool = true) -> void:
|
||||
_applying = true
|
||||
_apply_save(s, with_player)
|
||||
_applying = false
|
||||
|
||||
func _apply_save(s: Dictionary, with_player: bool) -> void:
|
||||
var p: Dictionary = s.get("player", {}) if with_player else {}
|
||||
if p.has("pos"):
|
||||
player.global_position = Vector3(p.pos[0], p.pos[1] + 0.05, p.pos[2])
|
||||
@@ -596,16 +657,21 @@ func placement_valid(def: Dictionary, xf: Transform3D) -> bool:
|
||||
q.exclude = [terrain.get_node("TerrainBody").get_rid(), player.get_rid()]
|
||||
return get_world_3d().direct_space_state.intersect_shape(q, 1).is_empty()
|
||||
|
||||
func spawn_piece(def: Dictionary, xf: Transform3D) -> Node3D:
|
||||
## `extra` is the piece's own state (a grave's owner, a door left open...).
|
||||
func spawn_piece(def: Dictionary, xf: Transform3D, extra: Dictionary = {}) -> Node3D:
|
||||
var script: Script = PIECES.get(def.place.piece, preload("res://scripts/building/piece.gd"))
|
||||
var piece: StaticBody3D = script.new()
|
||||
piece.setup(def)
|
||||
_pieces.add_child(piece)
|
||||
piece.global_transform = xf
|
||||
if not extra.is_empty() and piece.has_method("load_extra"):
|
||||
piece.load_extra(extra)
|
||||
if piece.is_in_group("build") and not _loading:
|
||||
build.recompute()
|
||||
if piece.kind == "floor":
|
||||
_update_grass_cuts()
|
||||
if server_mode and not _applying:
|
||||
piece_added.emit(piece)
|
||||
return piece
|
||||
|
||||
## Grass doesn't grow up through floors.
|
||||
@@ -636,6 +702,8 @@ func _go_online() -> void:
|
||||
_spawn = Net.world_info.get("island_spawn", _spawn)
|
||||
var start: Vector3 = Net.world_info.get("spawn", _spawn)
|
||||
player.global_position = start + Vector3(0.0, 0.05, 0.0)
|
||||
if Net.world_info.has("yaw"):
|
||||
player.rotation.y = float(Net.world_info.yaw) # facing the way you were when you left
|
||||
net_sync = preload("res://scripts/net/net_sync.gd").new()
|
||||
net_sync.name = "NetSync"
|
||||
add_child(net_sync)
|
||||
@@ -645,8 +713,9 @@ func _go_online() -> void:
|
||||
# The island's changes come from the server from now on.
|
||||
props.regrow_enabled = false
|
||||
WorldNet.join_world(self)
|
||||
PlayerNet.join_world(self) # and our inventory and stats
|
||||
if hud != null:
|
||||
var bar := UITheme.label("Online: %s · the island is shared; your inventory isn't saved yet, and there are no animals yet" \
|
||||
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)
|
||||
bar.position = Vector2(16, 40)
|
||||
$HUD.add_child(bar)
|
||||
@@ -654,6 +723,7 @@ func _go_online() -> void:
|
||||
|
||||
func _on_net_left(_reason: String) -> void:
|
||||
WorldNet.leave_world()
|
||||
PlayerNet.leave_world()
|
||||
if is_inside_tree():
|
||||
get_tree().paused = false
|
||||
get_tree().auto_accept_quit = true
|
||||
@@ -661,10 +731,10 @@ func _on_net_left(_reason: String) -> void:
|
||||
|
||||
## Leaves `count` of an item lying in the world at `xf` (an arrow stuck in a
|
||||
## tree, a thrown spear, meat that didn't fit). Saved with the world.
|
||||
## Online, the server puts it in the shared world (and returns null here).
|
||||
## Online only the server drops things (our game never does: returns null).
|
||||
func drop_item(id: String, count: int, xf: Transform3D) -> Node3D:
|
||||
if net_sync != null:
|
||||
WorldNet.drop(id, count, xf)
|
||||
push_warning("[world] drop_item(%s) on a client: only the server drops items" % id)
|
||||
return null
|
||||
return _make_dropped(id, count, xf)
|
||||
|
||||
@@ -675,6 +745,8 @@ func _make_dropped(id: String, count: int, xf: Transform3D) -> Node3D:
|
||||
pk.dropped = true
|
||||
_dropped.add_child(pk)
|
||||
pk.global_transform = xf
|
||||
if server_mode and not _applying:
|
||||
dropped_added.emit(pk)
|
||||
return pk
|
||||
|
||||
## Casts a fishing float (Actions validated it). One at a time.
|
||||
@@ -708,8 +780,12 @@ func remove_piece(piece: Node3D, refund_to: Node3D = null) -> int:
|
||||
_pieces.remove_child(piece)
|
||||
piece.queue_free()
|
||||
if not was_build:
|
||||
if server_mode:
|
||||
pieces_removed.emit([piece], [])
|
||||
return 0
|
||||
var weak: Array = build.recompute()
|
||||
if server_mode:
|
||||
pieces_removed.emit([piece] + weak, weak)
|
||||
for w in weak:
|
||||
if refund_to != null:
|
||||
Actions._give(refund_to, w.item_id, 1)
|
||||
|
||||
Reference in New Issue
Block a user