- 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)
70 lines
2.9 KiB
GDScript
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
|