99 lines
3.4 KiB
GDScript
99 lines
3.4 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
|
|
|
|
func setup(world: Node3D) -> void:
|
|
hands = self
|
|
collision_layer = 0
|
|
collision_mask = 0
|
|
set_physics_process(false)
|
|
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, HOTBAR)
|
|
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.item_id == "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))
|