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/.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, 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))