Files
sunrise/src/multiplayer/remotePlayers.ts
T

53 lines
2.7 KiB
TypeScript

import { type Object3D, MathUtils, Vector3 } from 'three';
import type { Sheep } from '../scene/sheep.ts';
import type { PlayerState } from './protocol.ts';
import { MULTIPLAYER } from '../constants/multiplayer.ts';
export function createRemotePlayers(template: Sheep, scene: Object3D, heightAt: (x: number, z: number) => number) {
const players = new Map<string, { sheep: Sheep; state: PlayerState; from: Vector3; yaw: number; elapsed: number }>();
return {
synchronize(states: PlayerState[], selfId: string) {
const present = new Set(states.filter(p => p.id !== selfId).map(p => p.id));
for (const [id, entry] of players) if (!present.has(id)) { entry.sheep.dispose(); players.delete(id); }
for (const state of states) {
if (state.id === selfId) continue;
let entry = players.get(state.id);
if (!entry) {
const sheep = template.clone();
sheep.group.name = state.nickname;
sheep.group.position.set(state.x, heightAt(state.x, state.z), state.z);
sheep.group.rotation.y = state.yaw;
entry = { sheep, state, from: sheep.group.position.clone(), yaw: state.yaw, elapsed: 0 };
players.set(state.id, entry);
scene.add(sheep.group);
} else if (state.updatedAt !== entry.state.updatedAt) {
entry.from.copy(entry.sheep.group.position);
entry.yaw = entry.sheep.group.rotation.y;
entry.elapsed = 0;
}
entry.state = state;
entry.sheep.setEquipment(state.equipment.umbrella, state.equipment.raincoat, state.equipment.hood);
}
},
update(delta: number, serverTime: number) {
let changed = false;
for (const entry of players.values()) {
const { sheep, state, from } = entry;
const duration = MULTIPLAYER.interpolationMs / 1000;
const interpolating = entry.elapsed < duration;
entry.elapsed += Math.max(0, delta);
const fraction = Math.min(1, entry.elapsed / duration);
const x = MathUtils.lerp(from.x, state.x, fraction), z = MathUtils.lerp(from.z, state.z, fraction);
sheep.group.position.set(x, heightAt(x, z), z);
const angle = Math.atan2(Math.sin(state.yaw - entry.yaw), Math.cos(state.yaw - entry.yaw));
sheep.group.rotation.y = entry.yaw + angle * fraction;
const active = sheep.pose(state.emote, Math.max(0, serverTime - state.emoteStartedAt) / 1000,
state.moving && serverTime - state.updatedAt < MULTIPLAYER.staleMovementMs, serverTime / 1000);
changed = changed || interpolating || active;
}
return changed;
},
clear() { for (const entry of players.values()) entry.sheep.dispose(); players.clear(); },
};
}