53 lines
2.7 KiB
TypeScript
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(); },
|
|
};
|
|
}
|