import { IcyNetUser } from '../../common/types/user'; import { PonyEntity } from './model/pony'; import { FullStatePacket, PositionUpdatePacket, } from '../../common/types/packet'; import { CanvasUtils } from './canvas-utils'; import { ChatBubble } from './chat-bubble'; import { Vector3, Scene, Euler } from 'three'; import { clamp } from '../../common/helper'; const chatBuilder = new CanvasUtils({ rounded: true, roundedRadius: 8, }); export class PlayerEntity extends PonyEntity { private _updateQueue: PositionUpdatePacket[] = []; private _targetFrame: any = null; private _lastFrame: any = null; private _chats: ChatBubble[] = []; public remote = true; private _p1 = new Vector3(); private _p2 = new Vector3(); private _q1 = new Vector3(); private _q2 = new Vector3(); private _pf = new Vector3(); private _qf = new Vector3(); constructor(public user: IcyNetUser) { super(); } public static fromUser(user: IcyNetUser, scene: Scene): PlayerEntity { const entity = new PlayerEntity(user); entity.initialize(); entity.addNameTag(user.display_name); scene.add(entity.container); return entity; } public setVelocity(velocity: Vector3) { this.velocity.copy(velocity); } public setAngularVelocity(velocity: Vector3) { this.angularVelocity.copy(velocity); } public setPosition(pos: Vector3) { this.container.position.copy(pos); } public addChat(message: string): void { const lines: string[] = []; let truncated = message; while (truncated.length > 80) { lines.push(truncated.substring(0, 80)); truncated = truncated.substring(80); } if (truncated) { lines.push(truncated); } const newChat = new ChatBubble(chatBuilder, lines); this._chats.forEach((item) => { const scaled = item.tag.scale.y; item.tag.position.add(new Vector3(0, scaled, 0)); }); this._chats.unshift(newChat); newChat.tag.position.set(0, 1.8 + this.nameTag!.tag.scale.y + 0.15, 0.5); this.container.add(newChat.tag); if (this._chats.length > 3) { this._chats.splice(3, this._chats.length - 1).forEach((item) => { this.container.remove(item.tag); item.dispose(); }); } setTimeout(() => { const i = this._chats.indexOf(newChat); if (i !== -1) { this.container.remove(newChat.tag); this._chats.splice(i, 1); newChat.dispose(); } }, 5000); } public setRotation(rot: Vector3 | Euler) { this.container.rotation.copy( rot instanceof Euler ? rot : new Euler(rot.x, rot.y, rot.z, 'XYZ'), ); } public addUncommittedChanges(packet: FullStatePacket) { const appendix = { ...packet, time: 0.1 }; this._updateQueue.push(appendix); if (!this._targetFrame) { this.setFromPacket(packet); this._targetFrame = appendix; } if (packet.character) { this.setCharacter(packet.character); } } public update(dt: number) { this.commitServerUpdate(dt); super.update(dt); } private setFromPacket(packet: FullStatePacket | PositionUpdatePacket) { if ((packet as FullStatePacket).velocity) { this.setVelocity( new Vector3().fromArray((packet as FullStatePacket).velocity!), ); } if ((packet as FullStatePacket).angular) { this.setAngularVelocity( new Vector3().fromArray((packet as FullStatePacket).angular!), ); } if (packet.position) { this.setPosition(new Vector3().fromArray(packet.position)); } if (packet.rotation) { this.setRotation(new Euler().fromArray(packet.rotation)); } if (packet.animState !== undefined) { this.setWalkAnimationState(packet.animState); } } private commitServerUpdate(dt: number) { if (this._updateQueue.length == 0) { return; } for (let i = 0; i < this._updateQueue.length; ++i) { this._updateQueue[i].time! -= dt; } while (this._updateQueue.length > 0 && this._updateQueue[0].time! <= 0.0) { this._lastFrame = { animState: this._targetFrame.animState, position: this.container.position.toArray(), rotation: this.container.rotation.toArray(), }; this._targetFrame = this._updateQueue.shift(); this._targetFrame.time = 0.0; } // Lerp the position and rotation if (this._targetFrame && this._lastFrame) { this._targetFrame.time += dt; this._p1.fromArray(this._lastFrame.position); this._p2.fromArray(this._targetFrame.position); this._q1.fromArray(this._lastFrame.rotation); this._q2.fromArray(this._targetFrame.rotation); this._pf.copy(this._p1); this._qf.copy(this._q1); const t = clamp(this._targetFrame.time / 0.1, 0.0, 1.0); this._pf.lerp(this._p2, t); this._qf.lerp(this._q2, t); this.setPosition(this._pf); this.setRotation(this._qf); this.setWalkAnimationState(this._lastFrame.animState); } } }