mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-24 08:02:27 +00:00
fixed various gamepad bugs in issue #193 (i think) and added unit tests
This commit is contained in:
1 parent
9491bdce74
commit
b4ae363ebb
7 files changed
+299
-35
No files matched your search
@@ -941,11 +941,13 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
|
||||
resume() {
|
||||
this.timer.start();
|
||||
this.audio && this.audio.start();
|
||||
this.poller && this.poller.start();
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.timer.stop();
|
||||
this.audio && this.audio.stop();
|
||||
this.poller && this.poller.stop();
|
||||
}
|
||||
|
||||
// so probe views stick around TODO: must be a better way?
|
||||
|
||||
+39
-24
@@ -565,9 +565,13 @@ export function makeKeycodeMap(table: [KeyDef, number, number][]): KeyCodeMap {
|
||||
return map;
|
||||
}
|
||||
|
||||
// TODO: P2_GP_B and P2_GP_C share keycodes with P2_B and P2_A, so they can't
|
||||
// be listed here without button 1/2 double-binding on player 2.
|
||||
const DEFAULT_CONTROLLER_KEYS: KeyDef[] = [
|
||||
Keys.UP, Keys.DOWN, Keys.LEFT, Keys.RIGHT, Keys.A, Keys.B, Keys.SELECT, Keys.START,
|
||||
Keys.GP_A, Keys.GP_B, Keys.GP_C, Keys.GP_D, Keys.OPTION,
|
||||
Keys.P2_UP, Keys.P2_DOWN, Keys.P2_LEFT, Keys.P2_RIGHT, Keys.P2_A, Keys.P2_B, Keys.P2_SELECT, Keys.P2_START,
|
||||
Keys.P2_GP_A, Keys.P2_GP_D,
|
||||
];
|
||||
|
||||
// Gamepad helper
|
||||
@@ -589,21 +593,26 @@ export class ControllerPoller {
|
||||
});
|
||||
}
|
||||
reset() {
|
||||
this.active = typeof navigator.getGamepads === 'function';
|
||||
if (this.active) {
|
||||
let numGamepads = navigator.getGamepads().length;
|
||||
if (typeof navigator.getGamepads === 'function') {
|
||||
let numGamepads = navigator.getGamepads()?.length;
|
||||
this.state = new Array(numGamepads);
|
||||
this.lastState = new Array(numGamepads);
|
||||
for (var i = 0; i < numGamepads; i++) {
|
||||
this.state[i] = new Int32Array(64);
|
||||
this.lastState[i] = new Int32Array(64);
|
||||
}
|
||||
console.log(this);
|
||||
this.active = this.state?.length > 0;
|
||||
}
|
||||
}
|
||||
start() {
|
||||
this.reset();
|
||||
}
|
||||
stop() {
|
||||
this.active = false;
|
||||
}
|
||||
poll() {
|
||||
if (!this.active) return;
|
||||
var gamepads = navigator.getGamepads();
|
||||
var gamepads = navigator.getGamepads && navigator.getGamepads();
|
||||
for (var gpi = 0; gpi < gamepads.length; gpi++) {
|
||||
let state = this.state[gpi];
|
||||
let lastState = this.lastState[gpi];
|
||||
@@ -628,28 +637,34 @@ export class ControllerPoller {
|
||||
}
|
||||
handleStateChange(gpi: number, k: number) {
|
||||
var axis = k - this.AXIS0;
|
||||
var state = this.state[gpi][k];
|
||||
var lastState = this.lastState[gpi][k];
|
||||
// TODO: this is slow
|
||||
for (var def of DEFAULT_CONTROLLER_KEYS) {
|
||||
// is this a gamepad entry? same player #?
|
||||
if (def && def.plyr == gpi) {
|
||||
var code = def.c;
|
||||
var state = this.state[gpi][k];
|
||||
var lastState = this.lastState[gpi][k];
|
||||
// check for button/axis match
|
||||
if (k == def.button || (axis == 0 && def.xaxis == state) || (axis == 1 && def.yaxis == state)) {
|
||||
//console.log("Gamepad", gpi, code, state);
|
||||
if (state != 0) {
|
||||
this.handler(code, 0, KeyFlags.KeyDown);
|
||||
} else {
|
||||
this.handler(code, 0, KeyFlags.KeyUp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// joystick released?
|
||||
else if (state == 0 && (axis == 0 && def.xaxis == lastState) || (axis == 1 && def.yaxis == lastState)) {
|
||||
this.handler(code, 0, KeyFlags.KeyUp);
|
||||
break;
|
||||
}
|
||||
if (!def || def.plyr != gpi) continue;
|
||||
// does this key map to the button/axis that changed?
|
||||
// an axis has to be compared against the direction the key stands for,
|
||||
// so that e.g. a left-to-right flick releases LEFT and presses RIGHT
|
||||
var active: boolean, wasActive: boolean;
|
||||
if (def.button != null && k == def.button) {
|
||||
active = state != 0;
|
||||
wasActive = lastState != 0;
|
||||
} else if (axis == 0 && def.xaxis != null) {
|
||||
active = def.xaxis == state;
|
||||
wasActive = def.xaxis == lastState;
|
||||
} else if (axis == 1 && def.yaxis != null) {
|
||||
active = def.yaxis == state;
|
||||
wasActive = def.yaxis == lastState;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
// don't break -- several keys can share a button (e.g. A and GP_A)
|
||||
//console.log("Gamepad", gpi, def.c, state);
|
||||
if (active && !wasActive) {
|
||||
this.handler(def.c, 0, KeyFlags.KeyDown);
|
||||
} else if (!active && wasActive) {
|
||||
this.handler(def.c, 0, KeyFlags.KeyUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-6
@@ -47,7 +47,7 @@ var AtariVectorPlatform = function(mainElement) {
|
||||
var cpuCyclesPerNMI = Math.round(cpuFrequency*12/(XTAL/4096)); // ~250 Hz
|
||||
var cpuCyclesPerFrame = Math.round(cpuFrequency/60);
|
||||
var cpu, cpuram, dvgram, rom, vecrom, bus, dvg;
|
||||
var video, audio, timer;
|
||||
var video, audio, timer, poller;
|
||||
var clock;
|
||||
var watchdog = 0;
|
||||
var switches = new RAM(16).mem;
|
||||
@@ -98,9 +98,11 @@ var AtariVectorPlatform = function(mainElement) {
|
||||
audio = newPOKEYAudio(2);
|
||||
video.create();
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
setKeyboardFromMap(video, switches, ASTEROIDS_KEYCODE_MAP);
|
||||
poller = setKeyboardFromMap(video, switches, ASTEROIDS_KEYCODE_MAP);
|
||||
}
|
||||
|
||||
this.pollControls = function() { poller && poller.poll(); }
|
||||
|
||||
this.advance = (novideo) => {
|
||||
if (!novideo) video.clear();
|
||||
var debugCond = this.getDebugCallback();
|
||||
@@ -189,7 +191,7 @@ var AtariColorVectorPlatform = function(mainElement) {
|
||||
var cpuCyclesPerNMI = Math.round(cpuFrequency / nmiFrequency);
|
||||
var cpuCyclesPerFrame = Math.round(cpuFrequency / 60);
|
||||
var cpu, cpuram, dvgram, rom, vecrom, bus, dvg, earom;
|
||||
var video, audio, timer;
|
||||
var video, audio, timer, poller;
|
||||
var clock;
|
||||
var switches = new RAM(16).mem;
|
||||
var nmicount = cpuCyclesPerNMI;
|
||||
@@ -259,9 +261,11 @@ var AtariColorVectorPlatform = function(mainElement) {
|
||||
audio = newPOKEYAudio(2);
|
||||
video.create();
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
setKeyboardFromMap(video, switches, GRAVITAR_KEYCODE_MAP);
|
||||
poller = setKeyboardFromMap(video, switches, GRAVITAR_KEYCODE_MAP);
|
||||
}
|
||||
|
||||
this.pollControls = function() { poller && poller.poll(); }
|
||||
|
||||
this.advance = (novideo) => {
|
||||
if (!novideo) video.clear();
|
||||
var debugCond = this.getDebugCallback();
|
||||
@@ -343,7 +347,7 @@ var Z80ColorVectorPlatform = function(mainElement, proto) {
|
||||
var cpuFrequency = 4000000.0;
|
||||
var cpuCyclesPerFrame = Math.round(cpuFrequency/60);
|
||||
var cpu, cpuram, dvgram, rom, bus, dvg;
|
||||
var video, audio, timer;
|
||||
var video, audio, timer, poller;
|
||||
var clock;
|
||||
var switches = new RAM(16).mem;
|
||||
var mathram = new RAM(16).mem;
|
||||
@@ -404,9 +408,11 @@ var Z80ColorVectorPlatform = function(mainElement, proto) {
|
||||
audio = newPOKEYAudio(2);
|
||||
video.create();
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
setKeyboardFromMap(video, switches, GRAVITAR_KEYCODE_MAP);
|
||||
poller = setKeyboardFromMap(video, switches, GRAVITAR_KEYCODE_MAP);
|
||||
}
|
||||
|
||||
this.pollControls = function() { poller && poller.poll(); }
|
||||
|
||||
this.advance = (novideo) => {
|
||||
if (!novideo) video.clear();
|
||||
this.runCPU(cpu, cpuCyclesPerFrame);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
import { Platform, BaseZ80Platform, Base6502Platform, Base6809Platform } from "../common/baseplatform";
|
||||
import { PLATFORMS, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, VectorVideo, Keys, makeKeycodeMap } from "../common/emu";
|
||||
import { PLATFORMS, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, VectorVideo, Keys, makeKeycodeMap, ControllerPoller } from "../common/emu";
|
||||
import { hex, lzgmini, stringToByteArray, safe_extend } from "../common/util";
|
||||
import { MasterAudio, AY38910_Audio } from "../common/audio";
|
||||
import { ProbeRecorder } from "../common/probe";
|
||||
@@ -785,6 +785,7 @@ class VectrexPlatform extends Base6809Platform {
|
||||
psg: AY38910_Audio;
|
||||
audio;
|
||||
timer: AnimationTimer;
|
||||
poller: ControllerPoller;
|
||||
|
||||
constructor(mainElement) {
|
||||
super();
|
||||
@@ -831,9 +832,11 @@ class VectrexPlatform extends Base6809Platform {
|
||||
this.psg = new AY38910_Audio(this.audio);
|
||||
this.video.create();
|
||||
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
setKeyboardFromMap(this.video, this.inputs, VECTREX_KEYCODE_MAP); // true = always send function);
|
||||
this.poller = setKeyboardFromMap(this.video, this.inputs, VECTREX_KEYCODE_MAP); // true = always send function);
|
||||
}
|
||||
|
||||
pollControls() { this.poller && this.poller.poll(); }
|
||||
|
||||
// TODO: loadControlsState
|
||||
updateControls() {
|
||||
// joystick (analog simulation)
|
||||
|
||||
@@ -31,7 +31,7 @@ var WilliamsPlatform = function(mainElement, proto, options) {
|
||||
var pia6821 = new RAM(8).mem;
|
||||
var blitregs = new RAM(8).mem;
|
||||
|
||||
var video, timer, pixels, displayPCs;
|
||||
var video, timer, pixels, displayPCs, poller;
|
||||
var screenNeedsRefresh = false;
|
||||
var membus;
|
||||
var video_counter;
|
||||
@@ -317,11 +317,13 @@ var WilliamsPlatform = function(mainElement, proto, options) {
|
||||
if (displayPCs) console.log(x, y, hex(addr, 4), "PC", hex(displayPCs[addr], 4));
|
||||
});
|
||||
var idata = video.getFrameData();
|
||||
setKeyboardFromMap(video, pia6821, KEYCODE_MAP);
|
||||
poller = setKeyboardFromMap(video, pia6821, KEYCODE_MAP);
|
||||
pixels = video.getFrameData();
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
}
|
||||
|
||||
this.pollControls = function() { poller && poller.poll(); }
|
||||
|
||||
this.getRasterScanline = function() { return video_counter; }
|
||||
|
||||
this.advance = function(novideo: boolean) {
|
||||
|
||||
@@ -404,3 +404,90 @@ describe('Platform Replay', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Platform Input', () => {
|
||||
|
||||
// node's global navigator is a getter, so it has to be replaced this way
|
||||
const realNavigator = Object.getOwnPropertyDescriptor(global, 'navigator');
|
||||
let pads;
|
||||
|
||||
function fakePads() {
|
||||
pads = [0, 1].map(() => ({
|
||||
axes: [0, 0],
|
||||
buttons: new Array(16).fill(null).map(() => ({ pressed: false })),
|
||||
}));
|
||||
Object.defineProperty(global, 'navigator', {
|
||||
value: { getGamepads: () => pads }, configurable: true, writable: true
|
||||
});
|
||||
}
|
||||
|
||||
after(() => {
|
||||
if (realNavigator === undefined) delete global.navigator;
|
||||
else Object.defineProperty(global, 'navigator', realNavigator);
|
||||
});
|
||||
|
||||
// starts a platform with a gamepad plugged in, then runs `press` (which can
|
||||
// poke the pads or call keycallback) and reports the input port before/after
|
||||
async function withGamepad(platid, romname, press, addr) {
|
||||
fakePads();
|
||||
var platform = new emu.PLATFORMS[platid](document.getElementById('emulator'));
|
||||
await platform.start();
|
||||
platform.loadROM("ROM", new Uint8Array(fs.readFileSync('./test/roms/' + platid + '/' + romname)));
|
||||
platform.resume();
|
||||
// the poller only wakes up once a pad announces itself
|
||||
window.dispatchEvent(new window.Event('gamepadconnected'));
|
||||
for (var i = 0; i < 4; i++) platform.nextFrame();
|
||||
var before = platform.readAddress(addr);
|
||||
press(pads);
|
||||
platform.nextFrame();
|
||||
var after = platform.readAddress(addr);
|
||||
platform.pause();
|
||||
return { before, after };
|
||||
}
|
||||
|
||||
// Robotron-style twin stick: player 1's stick is bound to the P2_* keys,
|
||||
// so it comes from gamepad #1, and firing comes from gamepad #0
|
||||
it('Should read a gamepad on williams-z80', async () => {
|
||||
var r = await withGamepad('williams-z80', 'game1.c.rom',
|
||||
(pads) => { pads[1].axes[0] = -1; }, 0xc804); // input0, LEFT1 = 0x4
|
||||
assert.equal(r.before & 0x4, 0);
|
||||
assert.equal(r.after & 0x4, 0x4, "gamepad left should set LEFT1");
|
||||
});
|
||||
|
||||
it('Should read gamepad buttons on williams-z80', async () => {
|
||||
var r = await withGamepad('williams-z80', 'game1.c.rom',
|
||||
(pads) => { pads[0].buttons[8].pressed = true; }, 0xc80c); // input2, COIN1 = 0x10
|
||||
assert.equal(r.before & 0x10, 0);
|
||||
assert.equal(r.after & 0x10, 0x10, "gamepad button 8 should set COIN1");
|
||||
});
|
||||
|
||||
// GRAVITAR_KEYCODE_MAP is active low, so a press clears the bit
|
||||
it('Should read a gamepad on vector-z80color', async () => {
|
||||
var r = await withGamepad('vector-z80color', 'game.c.rom',
|
||||
(pads) => { pads[0].axes[0] = -1; }, 0x8001); // input1, LEFT1 = 0x8
|
||||
assert.equal(r.before & 0x8, 0x8);
|
||||
assert.equal(r.after & 0x8, 0, "gamepad left should clear LEFT1");
|
||||
});
|
||||
|
||||
it('Should read gamepad axis 1 on vector-z80color', async () => {
|
||||
var r = await withGamepad('vector-z80color', 'game.c.rom',
|
||||
(pads) => { pads[0].axes[1] = -1; }, 0x8001); // input1, UP1 = 0x10
|
||||
assert.equal(r.before & 0x10, 0x10);
|
||||
assert.equal(r.after & 0x10, 0, "gamepad up should clear UP1");
|
||||
});
|
||||
|
||||
// the coin slots are on the number keys, which no gamepad button reaches
|
||||
it('Should read the coin keys on vector-z80color', async () => {
|
||||
var r = await withGamepad('vector-z80color', 'game.c.rom',
|
||||
() => { keycallback(Keys.VK_5.c, Keys.VK_5.c, 1); }, 0x8000); // input0, COIN1 = 0x2
|
||||
assert.equal(r.before & 0x2, 0);
|
||||
assert.equal(r.after & 0x2, 0x2, "'5' should set COIN1");
|
||||
});
|
||||
|
||||
it('Should read the coin keys on williams-z80', async () => {
|
||||
var r = await withGamepad('williams-z80', 'game1.c.rom',
|
||||
() => { keycallback(Keys.SELECT.c, Keys.SELECT.c, 1); }, 0xc80c); // input2, COIN1 = 0x10
|
||||
assert.equal(r.before & 0x10, 0);
|
||||
assert.equal(r.after & 0x10, 0x10, "'\\' should set COIN1");
|
||||
});
|
||||
});
|
||||
+150
-1
@@ -4,7 +4,7 @@ import {
|
||||
noise, getNoiseSeed, setNoiseSeed, KeyFlags, RAM, EmuHalt,
|
||||
AnimationTimer, dumpRAM, Keys, makeKeycodeMap, newKeyboardHandler,
|
||||
_setKeyboardEvents, padBytes, AddressDecoder, newAddressDecoder,
|
||||
getMousePos, drawCrosshair,
|
||||
getMousePos, drawCrosshair, ControllerPoller,
|
||||
} from "../../src/common/emu";
|
||||
|
||||
describe('Noise PRNG', function () {
|
||||
@@ -469,6 +469,155 @@ describe('drawCrosshair', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ControllerPoller', function () {
|
||||
const realWindow = (global as any).window;
|
||||
// node defines a getter-only global navigator, so it can't just be assigned
|
||||
const realNavigator = Object.getOwnPropertyDescriptor(global, 'navigator');
|
||||
function setNavigator(nav) {
|
||||
Object.defineProperty(global, 'navigator', { value: nav, configurable: true, writable: true });
|
||||
}
|
||||
|
||||
let pads: any[];
|
||||
let listeners: { [name: string]: (e: any) => void };
|
||||
let events: [number, number][];
|
||||
|
||||
// builds a poller wired to two fake standard-layout gamepads
|
||||
function newPoller() {
|
||||
pads = [0, 1].map(() => ({
|
||||
axes: [0, 0],
|
||||
buttons: new Array(16).fill(null).map(() => ({ pressed: false })),
|
||||
}));
|
||||
listeners = {};
|
||||
events = [];
|
||||
(global as any).window = { addEventListener: (n, f) => { listeners[n] = f; } };
|
||||
setNavigator({ getGamepads: () => pads });
|
||||
return new ControllerPoller((key, code, flags) => { events.push([key, flags]); });
|
||||
}
|
||||
|
||||
// polls and returns the keys that went down/up since the last call
|
||||
function poll(poller: ControllerPoller) {
|
||||
events = [];
|
||||
poller.poll();
|
||||
return events;
|
||||
}
|
||||
|
||||
function down(k) { return [k.c, KeyFlags.KeyDown]; }
|
||||
function up(k) { return [k.c, KeyFlags.KeyUp]; }
|
||||
|
||||
after(function () {
|
||||
if (realWindow === undefined) delete (global as any).window;
|
||||
else (global as any).window = realWindow;
|
||||
if (realNavigator === undefined) delete (global as any).navigator;
|
||||
else Object.defineProperty(global, 'navigator', realNavigator);
|
||||
});
|
||||
|
||||
it('should stay inactive until a gamepad connects', function () {
|
||||
const poller = newPoller();
|
||||
assert.strictEqual(poller.active, false);
|
||||
pads[0].buttons[0].pressed = true;
|
||||
assert.deepStrictEqual(poll(poller), []); // no state array yet, must not throw
|
||||
});
|
||||
|
||||
it('should activate on the gamepadconnected event', function () {
|
||||
const poller = newPoller();
|
||||
listeners['gamepadconnected']({});
|
||||
assert.strictEqual(poller.active, true);
|
||||
assert.deepStrictEqual(poll(poller), []); // nothing pressed yet
|
||||
});
|
||||
|
||||
it('should report button presses and releases once each', function () {
|
||||
const poller = newPoller();
|
||||
listeners['gamepadconnected']({});
|
||||
poll(poller);
|
||||
pads[0].buttons[9].pressed = true;
|
||||
assert.deepStrictEqual(poll(poller), [down(Keys.START)]);
|
||||
assert.deepStrictEqual(poll(poller), []); // held, not re-sent
|
||||
pads[0].buttons[9].pressed = false;
|
||||
assert.deepStrictEqual(poll(poller), [up(Keys.START)]);
|
||||
});
|
||||
|
||||
it('should send every key sharing a button, not just the first', function () {
|
||||
// A and GP_A are both button 0 -- platforms bind one or the other
|
||||
const poller = newPoller();
|
||||
listeners['gamepadconnected']({});
|
||||
poll(poller);
|
||||
pads[0].buttons[0].pressed = true;
|
||||
assert.deepStrictEqual(poll(poller), [down(Keys.A), down(Keys.GP_A)]);
|
||||
});
|
||||
|
||||
it('should map the face buttons past B', function () {
|
||||
const poller = newPoller();
|
||||
listeners['gamepadconnected']({});
|
||||
poll(poller);
|
||||
pads[0].buttons[2].pressed = true;
|
||||
pads[0].buttons[3].pressed = true;
|
||||
assert.deepStrictEqual(poll(poller), [down(Keys.GP_C), down(Keys.GP_D)]);
|
||||
});
|
||||
|
||||
it('should translate axes into direction keys', function () {
|
||||
const poller = newPoller();
|
||||
listeners['gamepadconnected']({});
|
||||
poll(poller);
|
||||
pads[0].axes[0] = -1;
|
||||
assert.deepStrictEqual(poll(poller), [down(Keys.LEFT)]);
|
||||
pads[0].axes[0] = 0;
|
||||
assert.deepStrictEqual(poll(poller), [up(Keys.LEFT)]);
|
||||
pads[0].axes[1] = 1;
|
||||
assert.deepStrictEqual(poll(poller), [down(Keys.DOWN)]);
|
||||
pads[0].axes[1] = 0;
|
||||
assert.deepStrictEqual(poll(poller), [up(Keys.DOWN)]);
|
||||
});
|
||||
|
||||
it('should release the old direction when an axis flicks across center', function () {
|
||||
const poller = newPoller();
|
||||
listeners['gamepadconnected']({});
|
||||
poll(poller);
|
||||
pads[0].axes[0] = -1;
|
||||
assert.deepStrictEqual(poll(poller), [down(Keys.LEFT)]);
|
||||
// -1 to +1 in one poll: LEFT must not stay stuck down
|
||||
pads[0].axes[0] = 1;
|
||||
assert.deepStrictEqual(poll(poller), [up(Keys.LEFT), down(Keys.RIGHT)]);
|
||||
pads[0].axes[1] = -1;
|
||||
assert.deepStrictEqual(poll(poller), [down(Keys.UP)]);
|
||||
pads[0].axes[1] = 1;
|
||||
assert.deepStrictEqual(poll(poller), [up(Keys.UP), down(Keys.DOWN)]);
|
||||
});
|
||||
|
||||
it('should route the second gamepad to the player 2 keys', function () {
|
||||
const poller = newPoller();
|
||||
listeners['gamepadconnected']({});
|
||||
poll(poller);
|
||||
pads[1].axes[1] = -1;
|
||||
pads[1].buttons[9].pressed = true;
|
||||
assert.deepStrictEqual(poll(poller), [down(Keys.P2_UP), down(Keys.P2_START)]);
|
||||
// player 1 keys must not fire for player 2's pad
|
||||
assert.ok(!events.some(e => e[0] == Keys.UP.c || e[0] == Keys.START.c));
|
||||
});
|
||||
|
||||
it('should ignore axes a key does not use', function () {
|
||||
// A is button-only, so it must not react to any axis movement
|
||||
const poller = newPoller();
|
||||
listeners['gamepadconnected']({});
|
||||
poll(poller);
|
||||
pads[0].axes[0] = 1;
|
||||
pads[0].axes[1] = 1;
|
||||
const codes = poll(poller).map(e => e[0]);
|
||||
assert.ok(!codes.includes(Keys.A.c));
|
||||
assert.deepStrictEqual(codes, [Keys.RIGHT.c, Keys.DOWN.c]); // x axis polled before y
|
||||
});
|
||||
|
||||
it('should drop gamepad state on disconnect', function () {
|
||||
const poller = newPoller();
|
||||
listeners['gamepadconnected']({});
|
||||
poll(poller);
|
||||
pads[0].buttons[0].pressed = true;
|
||||
poll(poller);
|
||||
listeners['gamepaddisconnected']({});
|
||||
// state is cleared, so a held button reads as a fresh press
|
||||
assert.deepStrictEqual(poll(poller), [down(Keys.A), down(Keys.GP_A)]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMousePos', function () {
|
||||
const realWindow = (global as any).window;
|
||||
|
||||
|
||||
Reference in new issue
Block a user