diff --git a/client/app.js b/client/app.js index 1217f09..a3c8cc4 100644 --- a/client/app.js +++ b/client/app.js @@ -18,6 +18,7 @@ import {clearAnimation, setAnimation} from '@avocado/timing'; import {World} from '@avocado/physics/matter/world'; import {Room, RoomView} from '@avocado/topdown'; // 1st party. +import {actionIds} from '../common/action-ids'; import {augmentParserWithThroughput} from '../common/parser-throughput'; import {SelfEntityPacket} from '../common/packets/self-entity.packet'; import {WorldTime} from '../common/world-time.synchronized'; @@ -83,14 +84,21 @@ export class App extends decorate(class {}) { // Input. this.actionRegistry = new ActionRegistry(); this.actionRegistry.mapKeysToActions(config.actionKeyMap); - this.actionState = this.actionRegistry.state; + this.actionRegistry.setActionTransformerFor('UseItem', (type) => { + if ('keyDown' === type) { + return this.selfEntity.activeSlotIndex; + } + else { + return -1; + } + }) + InputPacket.setActionIds(actionIds()); this.inputHandle = undefined; this.actionRegistry.listen(this.stage.inputNormalizer); this.onBlur = this.onBlur.bind(this); this.onFocus = this.onFocus.bind(this); // Global keys. this.pointingAt = [-1, -1]; - this.pointerMovementHandle = undefined; // Net. this.AugmentedParser = augmentParserWithThroughput(SocketIoParser); this.hasReceivedState = false; @@ -219,7 +227,7 @@ export class App extends decorate(class {}) { } createMoveToNormal(position) { - if (!this.selfEntity) { + if (!this.selfEntity || !this.selfEntity.is('existent')) { return; } const realEntityPosition = Vector.sub( @@ -286,14 +294,6 @@ export class App extends decorate(class {}) { const slotIndex = (parseInt(key) + 9) % 10; this.setActiveSlotIndex(slotIndex); break; - case 'ArrowLeft': - if (this.selfEntity) { - this.actionRegistry.state = this.actionRegistry.state.set( - 'UseItem', - this.selfEntity.activeSlotIndex - ); - } - break; } } @@ -415,10 +415,12 @@ export class App extends decorate(class {}) { readConfig() { return { actionKeyMap: { - 'w': 'MoveUp', - 'a': 'MoveLeft', - 's': 'MoveDown', - 'd': 'MoveRight', + 'MoveUp': 'w', + 'MoveLeft': 'a', + 'MoveDown': 's', + 'MoveRight': 'd', + 'UseItem': 'ArrowLeft', + 'Interact': 'Enter', }, connectionUrl: window.location.protocol + '//' + window.location.hostname + ':8420/', doPhysicsSimulation: true, @@ -533,43 +535,15 @@ export class App extends decorate(class {}) { this.stage.element.addEventListener('focus', this.onFocus); // Input messages. this.inputHandle = setInterval(() => { - if (this.actionState !== this.actionRegistry.state) { - this.actionState = this.actionRegistry.state; - this.socket.send(InputPacket.fromState(this.actionState)); - this.actionRegistry.state = this.actionRegistry.state.delete( - 'UseItem' - ); + const inputStream = this.actionRegistry.drain(); + if (inputStream.length > 0) { + // Inject input. + if (this.selfEntity) { + this.selfEntity.inputStream = inputStream; + } + this.socket.send(new InputPacket(inputStream)); } }, 1000 * config.inputFrequency); - // Mouse/touch movement. - this.pointerMovementHandle = setInterval(() => { - do { - let normal; - if (this.isPointingAtAnything()) { - const toVector = Vector.scale(this.pointingAt, this.isDebugging ? 2 : 1); - normal = this.createMoveToNormal(toVector); - if (normal) { - this.actionRegistry.state = this.actionRegistry.state.withMutations((state) => { - if (normal[0]) { - state.set('MoveToX', Math.floor(normal[0] * 127)); - } - if (normal[1]) { - state.set('MoveToY', Math.floor(normal[1] * 127)); - } - }); - break; - } - } - this.actionRegistry.state = this.actionRegistry.state.withMutations((state) => { - if (!normal || 0 === normal[0]) { - state.delete('MoveToX'); - } - if (!normal || 0 === normal[1]) { - state.delete('MoveToY'); - } - }); - } while (false); - }, 1000 * config.pointerMovementFrequency); // Focus the stage. this.stage.focus(); this.isFocused = true; @@ -606,10 +580,6 @@ export class App extends decorate(class {}) { const now = performance.now(); const elapsed = (now - lastTime) / 1000; lastTime = now; - // Inject input. - if (this.selfEntity) { - this.selfEntity.inputState = this.actionState; - } // Tick. this.worldTime.tick(elapsed); if (this.room) { @@ -621,8 +591,6 @@ export class App extends decorate(class {}) { } stopProcessingInput() { - clearInterval(this.pointerMovementHandle); - this.pointerMovementHandle = undefined; clearInterval(this.inputHandle); this.inputHandle = undefined; this.stage.element.removeEventListener('focus', this.onFocus); diff --git a/common/action-ids.js b/common/action-ids.js new file mode 100644 index 0000000..d4da587 --- /dev/null +++ b/common/action-ids.js @@ -0,0 +1,11 @@ +export function actionIds() { + return { + 'MoveUp': 0, + 'MoveLeft': 1, + 'MoveDown': 2, + 'MoveRight': 3, + 'UseItem': 4, + 'Interact': 5, + }; +} + diff --git a/common/traits/controllable.trait.js b/common/traits/controllable.trait.js index d481fd1..1b0918f 100644 --- a/common/traits/controllable.trait.js +++ b/common/traits/controllable.trait.js @@ -1,7 +1,6 @@ -import * as I from 'immutable'; - import {TickingPromise} from '@avocado/core'; import {Trait} from '@avocado/entity'; +import {Vector} from '@avocado/math'; // Input handling. export class Controllable extends Trait { @@ -12,56 +11,54 @@ export class Controllable extends Trait { constructor(entity, params, state) { super(entity, params, state); - this._inputState = I.Map(); this._itemPromise = undefined; + this._movementVector = [0, 0]; } - set inputState(inputState) { - this._inputState = I.Map(inputState); - if (AVOCADO_SERVER) { - if (inputState.has('UseItem')) { - const slotIndex = inputState.get('UseItem'); - this._itemPromise = this.entity.useItemInSlot(slotIndex); - Promise.resolve(this._itemPromise).then(() => { - this._itemPromise = undefined; - }); + set inputStream(inputStream) { + for (let i = 0; i < inputStream.length; i++) { + const {action, value} = inputStream[i]; + const normalized = 0 === value ? -1 : 1; + switch (action) { + case 'MoveUp': + this._movementVector[1] -= normalized; + break; + case 'MoveRight': + this._movementVector[0] += normalized; + break; + case 'MoveDown': + this._movementVector[1] += normalized; + break; + case 'MoveLeft': + this._movementVector[0] -= normalized; + break; + case 'UseItem': + if (AVOCADO_SERVER) { + if (-1 !== value && !this._itemPromise) { + this._itemPromise = this.entity.useItemInSlot(value); + Promise.resolve(this._itemPromise).then(() => { + this._itemPromise = undefined; + }); + } + } + break; } } + if (Vector.isZero(this._movementVector)) { + this.entity.currentAnimation = 'idle'; + } + else { + this.entity.currentAnimation = 'moving'; + } } tick(elapsed) { if (this._itemPromise && this._itemPromise instanceof TickingPromise) { this._itemPromise.tick(elapsed); } - const {_inputState: inputState} = this; - if (0 === inputState.size) { - this.entity.currentAnimation = 'idle'; - return; + if (!Vector.isZero(this._movementVector)) { + this.entity.requestMovement(this._movementVector); } - const movementVector = [0, 0]; - if (inputState.has('MoveToX')) { - movementVector[0] = inputState.get('MoveToX') / 127; - } - if (inputState.has('MoveToY')) { - movementVector[1] = inputState.get('MoveToY') / 127; - } - if (inputState.has('MoveUp')) { - movementVector[1] -= 1; - } - if (inputState.has('MoveRight')) { - movementVector[0] += 1; - } - if (inputState.has('MoveDown')) { - movementVector[1] += 1; - } - if (inputState.has('MoveLeft')) { - movementVector[0] -= 1; - } - if (0 === movementVector[0] && 0 === movementVector[1]) { - return; - } - this.entity.requestMovement(movementVector); - this.entity.currentAnimation = 'moving'; } } diff --git a/server/game.js b/server/game.js index b204a4d..e1e4895 100644 --- a/server/game.js +++ b/server/game.js @@ -8,6 +8,7 @@ import {World} from '@avocado/physics/matter/world'; import {Ticker} from '@avocado/timing'; import {Room} from '@avocado/topdown'; // 1st party. +import {actionIds} from '../common/action-ids'; import {SelfEntityPacket} from '../common/packets/self-entity.packet'; import {WorldTime} from '../common/world-time.synchronized'; import {createEntityForConnection} from './create-entity-for-connection'; @@ -31,6 +32,7 @@ export default class Game { // State synchronization. this.informTicker = new Ticker(config.informInterval); this.informTicker.on('tick', this.inform, this); + InputPacket.setActionIds(actionIds()); // Simulation. this.mainLoopHandle = setInterval( this.createMainLoop(), @@ -104,7 +106,7 @@ export default class Game { const {entity} = socket; return (packet) => { if (packet instanceof InputPacket) { - entity.inputState = packet.toState(); + entity.inputStream = packet.data; } }; }