Files
Cinderwild/scripts/net/server_player.gd

111 lines
3.9 KiB
GDScript

extends CharacterBody3D
## One player's character on a dedicated server (M2.4): what they carry, what
## they hold, what they wear, their bed, and their survival stats, which run
## here (warmth from the server's fires and buildings, hunger, diet...).
##
## It has the same fields and functions the Actions rules use on the local
## player in single-player (inventory, selected, worn, held_item_id,
## survival...), so the server runs exactly the same rules for everyone.
## It has no body: where it is comes from the player's moves (net.gd).
## Saved as players/<player id>.json in the server's world (player_net.gd).
signal worn_changed
signal selected_changed
const HOTBAR := 8
var peer := 0
var player_id := ""
var display := ""
var inventory := Inventory.new(40)
var selected := 0
var worn := ""
var cursor = null ## the stack held on the inventory screen's cursor
var survival: Node
var hands: Node ## (self: held_heat for a carried torch)
## (A body with no collision, so it has a physics id: wolves look for a clear
## line to it, and arrows skip whoever shot them. `velocity` comes from the
## player's moves.)
var water_level := -INF
var in_bed := false ## got into bed, waiting for the night to pass
var bed_at := Vector3.ZERO
var has_bed_spawn := false
var bed_spawn := Vector3.ZERO
var anim := 0 ## last movement state reported (avatar.gd Anim)
var died_at := 0 ## ticks (ms) when they last died
var melee_ready_at := 0 ## ticks (ms): PvP swings wait for the weapon's cooldown
## Physics layer 5 (value 16): only arrows and thrown spears look for it,
## and only when PvP is on (projectile.gd). Nothing else collides with it.
const PLAYER_LAYER := 16
func setup(world: Node3D) -> void:
hands = self
collision_layer = PLAYER_LAYER
collision_mask = 0
set_physics_process(false)
var cs := CollisionShape3D.new()
var cap := CapsuleShape3D.new()
cap.radius = 0.4
cap.height = 1.8
cs.shape = cap
cs.position = Vector3(0, 0.9, 0)
add_child(cs)
water_level = world.terrain.water_level
survival = preload("res://scripts/player/survival.gd").new()
survival.name = "Survival"
survival.player = self
survival.world = world
add_child(survival)
func held_item_id() -> String:
var s = inventory.get_slot(selected)
return s.id if s != null else ""
func select(i: int) -> void:
selected = wrapi(i, 0, inventory.size()) # (any bag slot: player.gd's rows)
selected_changed.emit()
func held_heat() -> float:
return float(ItemDB.get_item(held_item_id()).get("light", {}).get("heat", 0.0))
## Chests and graves call this on interact; on the server there's no screen.
func open_inventory(_container: Node) -> void:
pass
func claims_bed(bed: Node3D) -> bool:
return has_bed_spawn and bed_spawn.distance_to(bed.global_position) < 0.3
func claim_bed(bed: Node3D) -> void:
has_bed_spawn = true
bed_spawn = bed.global_position
## The bed this player respawns at, if it's still standing.
func bed_piece(world: Node3D) -> Node3D:
if not has_bed_spawn:
return null
for p in world.all_pieces():
if p.is_in_group("bed") and claims_bed(p):
return p
return null
func to_data() -> Dictionary:
return {"name": display, "pos": [global_position.x, global_position.y, global_position.z], "yaw": rotation.y,
"inventory": inventory.to_data(), "selected": selected, "stats": survival.to_data(), "worn": worn,
"bed": {"has": has_bed_spawn, "pos": [bed_spawn.x, bed_spawn.y, bed_spawn.z]}}
func load_data(d: Dictionary) -> void:
if d.has("inventory"):
inventory.load_data(d.inventory)
selected = int(d.get("selected", 0))
if d.has("stats"):
survival.load_data(d.stats)
worn = String(d.get("worn", ""))
if not worn.is_empty() and ItemDB.get_item(worn).is_empty():
worn = ""
var b: Dictionary = d.get("bed", {})
has_bed_spawn = bool(b.get("has", false))
if b.has("pos"):
bed_spawn = Vector3(b.pos[0], b.pos[1], b.pos[2])
rotation.y = float(d.get("yaw", 0.0))