refactor: input greatness

This commit is contained in:
cha0s 2019-10-11 02:17:07 -05:00
parent 350ca435c1
commit fcf09044dc
4 changed files with 75 additions and 97 deletions

View File

@ -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);

11
common/action-ids.js Normal file
View File

@ -0,0 +1,11 @@
export function actionIds() {
return {
'MoveUp': 0,
'MoveLeft': 1,
'MoveDown': 2,
'MoveRight': 3,
'UseItem': 4,
'Interact': 5,
};
}

View File

@ -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';
}
}

View File

@ -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;
}
};
}