Files
Emberwild/scripts/net/character_mesh.gd
Paul Bailey d0f473e57b M2.2: player bodies, name tags, shared island and checked movement
- Joining loads the server's island (seed, spawn, clock); the server
  generates the same terrain from its seed and keeps a world clock that
  pauses when empty
- Other players are procedural low-poly characters in their chosen colours
  with name tags (hidden by walls, fading with distance), walk/run/swim
  animation and held items
- Movement: local and instant, sent at 20 Hz stamped with the sender's
  clock; the server refuses impossible moves (speed, rising, leaving the
  island, underground) and sends the player back; flagged respawns allowed
- Avatars drawn 100 ms behind and interpolated, with brief extrapolation
- Online: no pausing, player list in the Esc menu, Disconnect back to the
  title (with the reason); animals off and world changes local-only until
  M2.3-M2.5, shown on the HUD
- test_net_world: real server + walking and cheating bots at 100 ms +/-
  30 ms simulated latency (27 checks)
2026-11-04 15:08:14 +00:00

70 lines
2.9 KiB
GDScript

extends RefCounted
## Procedural low-poly player character, coloured by the player's appearance
## (identity.gd: tunic, hair, beard). Feet at the origin, facing -Z, 1.8 m
## tall to match the player capsule.
##
## build() returns:
## body: torso, belt, head, hair and beard (static)
## arm / leg: one limb each, hanging from its pivot down -Y
## shoulders / hips: the pivot points (left, right)
## hand: where a held item goes, in arm space
const P = preload("res://scripts/world/proc_mesh.gd")
const IM = preload("res://scripts/items/item_meshes.gd")
const Identity = preload("res://scripts/net/identity.gd")
const SKIN := Color(0.86, 0.68, 0.55)
const TROUSERS := Color(0.30, 0.25, 0.20)
const BOOTS := Color(0.20, 0.14, 0.10)
const BELT := Color(0.35, 0.22, 0.12)
const EYES := Color(0.08, 0.07, 0.06)
static var _cache := {}
static func build(appearance: Dictionary) -> Dictionary:
var a := Identity.sanitize_appearance(appearance)
var key := "%d_%d_%s" % [a.tunic, a.hair, a.beard]
if _cache.has(key):
return _cache[key]
var tunic: Color = Identity.TUNICS[a.tunic]
var hair: Color = Identity.HAIR[a.hair]
var r := RandomNumberGenerator.new()
r.seed = 7
var n := FastNoiseLite.new()
n.seed = 7
n.frequency = 1.5
var st := P._begin()
IM._box(st, Vector3(0, 1.2, 0), Vector3(0.42, 0.56, 0.24), tunic) # tunic
IM._box(st, Vector3(0, 0.93, 0), Vector3(0.44, 0.1, 0.26), P._mul(tunic, 0.85)) # skirt hem
IM._box(st, Vector3(0, 1.0, 0), Vector3(0.45, 0.06, 0.27), BELT)
IM._box(st, Vector3(0, 1.0, -0.135), Vector3(0.07, 0.05, 0.02), Color(0.6, 0.55, 0.4)) # buckle
IM._box(st, Vector3(0, 1.5, 0), Vector3(0.12, 0.06, 0.12), SKIN) # neck
P._blob(st, Vector3(0, 1.65, 0), Vector3(0.13, 0.15, 0.14), n, 0.05, SKIN, r, 1) # head
for s in [-1.0, 1.0]:
IM._box(st, Vector3(s * 0.05, 1.67, -0.13), Vector3(0.03, 0.025, 0.02), EYES)
IM._box(st, Vector3(0, 1.62, -0.145), Vector3(0.035, 0.05, 0.03), P._mul(SKIN, 0.92)) # nose
P._blob(st, Vector3(0, 1.72, 0.02), Vector3(0.145, 0.1, 0.15), n, 0.15, hair, r, 1) # hair
IM._box(st, Vector3(0, 1.66, 0.11), Vector3(0.24, 0.14, 0.06), hair) # back of the head
if a.beard:
IM._box(st, Vector3(0, 1.54, -0.115), Vector3(0.2, 0.13, 0.07), hair)
IM._box(st, Vector3(0, 1.47, -0.1), Vector3(0.12, 0.06, 0.05), hair)
var body := P._commit(st)
st = P._begin()
IM._box(st, Vector3(0, -0.2, 0), Vector3(0.12, 0.42, 0.13), tunic)
IM._box(st, Vector3(0, -0.47, 0), Vector3(0.09, 0.12, 0.1), SKIN)
var arm := P._commit(st)
st = P._begin()
IM._box(st, Vector3(0, -0.4, 0), Vector3(0.14, 0.78, 0.15), TROUSERS)
IM._box(st, Vector3(0, -0.85, -0.03), Vector3(0.15, 0.14, 0.22), BOOTS)
var leg := P._commit(st)
var out := {"body": body, "arm": arm, "leg": leg,
"shoulders": [Vector3(-0.27, 1.43, 0), Vector3(0.27, 1.43, 0)],
"hips": [Vector3(-0.1, 0.92, 0), Vector3(0.1, 0.92, 0)],
"hand": Vector3(0, -0.5, -0.02)}
_cache[key] = out
return out