import {compose, EventEmitter} from '@avocado/core'; import {Vector} from '@avocado/math'; import {Trait} from '../trait'; import {TraitUpdatePositionedPositionPacket} from '../packets/trait-update-positioned-position.packet'; const decorate = compose( EventEmitter, Vector.Mixin('_position', 'x', 'y', { track: true, }), Vector.Mixin('serverPosition', 'serverX', 'serverY', { track: true, }), ); // < 16768 will pack into 1 short per axe and give +/- 0.25 precision. export class Positioned extends decorate(Trait) { static defaultState() { return { x: 0, y: 0, }; } static type() { return 'positioned'; } constructor(entity, params, state) { super(entity, params, state); this.on('_positionChanged', this.on_positionChanged, this); const x = this.state.x; const y = this.state.y; this._position = [x, y]; this.entity.position[0] = x; this.entity.position[1] = y; if (AVOCADO_CLIENT) { this._relaxServerPositionConstraintIfNearerThan = 0; this.serverPosition = this._position; this.serverPositionDirty = false; this.on('serverPositionChanged', this.onServerPositionChanged, this); } } destroy() { this.off('_positionChanged', this.on_positionChanged); if (AVOCADO_CLIENT) { this.off('serverPositionChanged', this.onServerPositionChanged); } } acceptPacket(packet) { if (packet instanceof TraitUpdatePositionedPositionPacket) { [this.serverX, this.serverY] = packet.data.position; } } on_positionChanged(oldPosition, newPosition) { this.entity.position[0] = newPosition[0]; this.entity.position[1] = newPosition[1]; if (AVOCADO_SERVER) { this.state.x = newPosition[0]; this.state.y = newPosition[1]; } this.entity.emit('positionChanged', oldPosition, newPosition); } onServerPositionChanged() { this.serverPositionDirty = true; } packets(informed) { const {x, y} = this.stateDifferences(); if (x || y) { // Physics slop can end us up with negatives. Don't allow them in the // packed representation, even though it means a slight loss in accuracy. // The world bounds will (should!) keep things *eventually* correct. const x = Math.max(0, this.state.x); const y = Math.max(0, this.state.y); return new TraitUpdatePositionedPositionPacket({ position: [x, y], }); } } set relaxServerPositionConstraintIfNearerThan(nearerThan) { this._relaxServerPositionConstraintIfNearerThan = nearerThan; } listeners() { return { isTickingChanged: () => { // Snap position on ticking change. if (AVOCADO_CLIENT) { this._position = this.serverPosition; } }, }; } methods() { return { setPosition: (position) => { this._position = position; }, }; } renderTick(elapsed) { if (!this.serverPositionDirty) { return; } if (Vector.equals(this._position, this.serverPosition)) { this.serverPositionDirty = false; return; } if (Vector.equalsClose(this._position, this.serverPosition, 0.1)) { this._position = this.serverPosition; this.serverPositionDirty = false; return; } const diff = Vector.sub(this.serverPosition, this._position); const nearerThan = this._relaxServerPositionConstraintIfNearerThan; let lerp; if ( nearerThan && Vector.equalsClose(this._position, this.serverPosition, nearerThan) ) { lerp = .01; } else { lerp = .5; } this._position = Vector.add(this._position, Vector.scale(diff, lerp)); } }