Skip to content

Commit

Permalink
Support the weird Playstation 2-Axes-As-1-Axis to fix #48 (#49)
Browse files Browse the repository at this point in the history
* added handling of weird combo axis

* improve support for the windows playstation axis handling

* support hori fight stick for both ps4 and ps5 mode on windows

* add firefox support

* fixed some windows mappings
  • Loading branch information
lunarcloud authored Jun 29, 2024
1 parent 00237be commit 66e990d
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 12 deletions.
13 changes: 13 additions & 0 deletions src/axis-as-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ class AxisAsButton {
deadZone = 0
this.deadZone = (this.direction === 'positive' ? 1 : -1) * Math.abs(deadZone)
}

/**
* Test whether the axis value matches this "button"'s direction
* @param {number} value Axis value.
* @returns {boolean}
*/
test(value) {
if (this.direction === 'positive')
return value >= this.threshold
else if (this.direction === 'negative')
return value <= this.threshold
return false
}
}

export { AxisAsButton }
82 changes: 82 additions & 0 deletions src/combined-axis-as-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { AxisAsButton } from "./axis-as-button.js"
import { GamepadAnalogStickMapping } from "./gamepad-mapping.js"

/**
* Round fraction to places
* @param {number} value number
* @param {number} [places] decimal places to keep
* @returns rounded fraction
*/
function roundF(value, places = 5) {
const factor = Math.pow(10, places)
return Math.round(value * factor) / factor
}

/**
* Schema 2-Axes-Combined-As-1-Axis as Button
*/
class CombinedAxesAsStick extends GamepadAnalogStickMapping {
/**
* Axis index.
* @type {number}
*/
index


/**
* Constructor.
* @param {number} index Axis index.
* @param {import("./gamepad-mapping.js").SchemaButtonDef} click Button/Axis for "stick click"
*/
constructor (index, click) {
super(
new CombinedAxisToButton(index, 'up'),
new CombinedAxisToButton(index, 'right'),
new CombinedAxisToButton(index, 'down'),
new CombinedAxisToButton(index, 'left'),
click)
}
}

class CombinedAxisToButton extends AxisAsButton {

/**
* Axis direction
* @type {'up'|'right'|'down'|'left'}
*/
cardinal

/**
* Constructor.
* @param {number} index Axis index.
* @param {'up'|'right'|'down'|'left'} cardinal Direction.
*/
constructor(index, cardinal) {
super(undefined, index)

this.cardinal = cardinal
}

/**
* Test whether the axis value matches this "button"'s direction
* @param {number} value Axis value.
* @returns {boolean}
*/
test(value) {
const input = roundF(value)
switch (this.cardinal) {
case "up":
return [roundF(-5/7), -1, 1].includes(input)
case "right":
return roundF(-5/7) <= input && input <= roundF(-1/7)
case "down":
return roundF(-1/7) <= input && input <= roundF(3/7)
case "left":
return roundF(3/7) <= input && input <= roundF(7/7)
default:
return false;
}
}
}

export { CombinedAxesAsStick, CombinedAxisToButton }
33 changes: 33 additions & 0 deletions src/combined-axis-as-button.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* Test of Gamepad Mapping */

import { test, expect } from '@jest/globals'
import { CombinedAxisToButton } from './combined-axis-as-button.js';

const upButton = new CombinedAxisToButton(0, 'up');
const rightButton = new CombinedAxisToButton(0, 'right');
const downButton = new CombinedAxisToButton(0, 'down');
const leftButton = new CombinedAxisToButton(0, 'left');

test('Hori Direction Values for Up', () => {
expect(upButton.test(1)).toStrictEqual(true)
expect(upButton.test(-1)).toStrictEqual(true)
expect(upButton.test(-0.71429)).toStrictEqual(true)
})

test('Hori Direction Values for Right', () => {
expect(rightButton.test(-0.14286)).toStrictEqual(true)
expect(rightButton.test(-0.42857)).toStrictEqual(true)
expect(rightButton.test(-0.71429)).toStrictEqual(true)
})

test('Hori Direction Values for Down', () => {
expect(downButton.test(-0.14286)).toStrictEqual(true)
expect(downButton.test(0.14286)).toStrictEqual(true)
expect(downButton.test(0.42857)).toStrictEqual(true)
})

test('Hori Direction Values for Left', () => {
expect(leftButton.test(1)).toStrictEqual(true)
expect(leftButton.test(0.71429)).toStrictEqual(true)
expect(leftButton.test(0.42857)).toStrictEqual(true)
})
48 changes: 40 additions & 8 deletions src/gameinput-models.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { GameInputSchema } from './gameinput-schema.js'
import { FaceDirections, GamepadAnalogStickMapping, GamepadCenterMapping, GamepadDirectionsMapping, GamepadFaceMapping, GamepadLRMapping } from './gamepad-mapping.js'
import { AxisAsButton } from './axis-as-button.js'
import { StandardCenterMapping, StandardFaceMapping, StandardGamepadMapping, StandardLeftStickMapping, StandardPlumberFaceMapping, StandardPlumberGamepadMapping, StandardShoulderMapping, StandardTriggerMapping } from './standard-gamepad-mapping.js'

const radialDpadAxis9 = undefined // weird single-axis thing on axis 9 we don't support
import { CombinedAxesAsStick } from './combined-axis-as-button.js'

const MagicSProN64AdapterLinuxMapping = StandardPlumberGamepadMapping.variant({
dpad: new GamepadDirectionsMapping(new AxisAsButton('-', 5), new AxisAsButton('+', 4), new AxisAsButton('+', 5), new AxisAsButton('-', 4)),
Expand All @@ -17,7 +16,7 @@ const MagicSProN64AdapterLinuxMapping = StandardPlumberGamepadMapping.variant({
)
})
const MagicSProN64AdapterWindowsMapping = StandardPlumberGamepadMapping.variant({
dpad: radialDpadAxis9,
dpad: new CombinedAxesAsStick(9, undefined),
face: new GamepadFaceMapping(undefined, undefined, 1, 2),
rightStick: new GamepadAnalogStickMapping(
new AxisAsButton('+', 5),
Expand All @@ -34,6 +33,12 @@ const HoriFightStickMiniChrome = StandardGamepadMapping.variant({
leftStick: new GamepadAnalogStickMapping(new AxisAsButton('-', 7), new AxisAsButton('+', 6), new AxisAsButton('+', 7), new AxisAsButton('-', 6), 10),
rightStick: new GamepadAnalogStickMapping(undefined, undefined, undefined, undefined, 11)
})
const HoriFightStickMiniChromeWindows = StandardGamepadMapping.variant({
dpad: undefined,
face: new GamepadFaceMapping(3, 2, 1, 0),
leftStick: new CombinedAxesAsStick(9, 10),
rightStick: new GamepadAnalogStickMapping(undefined, undefined, undefined, undefined, 11)
})
const HoriFightStickMiniFirefox = StandardGamepadMapping.variant({
dpad: undefined,
face: new GamepadFaceMapping(3, 17, 1, 0),
Expand Down Expand Up @@ -113,7 +118,7 @@ const GameInputModels = [
center: StandardCenterMapping,
shoulder: undefined,
trigger: new GamepadLRMapping(4, 5),
leftStick: new GamepadAnalogStickMapping(new AxisAsButton('-', 2), new AxisAsButton('+', 1), new AxisAsButton('+', 2), new AxisAsButton('-', 1)),
leftStick: new GamepadAnalogStickMapping(new AxisAsButton('-', 1), new AxisAsButton('+', 0), new AxisAsButton('+', 1), new AxisAsButton('-', 0)),
rightStick: undefined
})
),
Expand Down Expand Up @@ -156,13 +161,40 @@ const GameInputModels = [
'Linux',
StandardGamepadMapping.variant({
dpad: undefined,
face: StandardFaceMapping,
center: new GamepadCenterMapping(7, 6),
trigger: new GamepadLRMapping(new AxisAsButton('+', 2), new AxisAsButton('+', 5)),
leftStick: new GamepadAnalogStickMapping(new AxisAsButton('-', 7), new AxisAsButton('+', 6), new AxisAsButton('+', 7), new AxisAsButton('-', 6), 9),
rightStick: new GamepadAnalogStickMapping(undefined, undefined, undefined, undefined, 10)
})
),
new GameInputModel(
GameInputSchema.Ragdoll,
'joystick',
'HORI Fighting Stick mini (Vendor: 0f0d Product: 01b3)',
'Windows',
HoriFightStickMiniChromeWindows
),
new GameInputModel(
GameInputSchema.Ragdoll,
'joystick',
'HORI Fighting Stick mini (Vendor: 0f0d Product: 01b4)',
'Windows',
HoriFightStickMiniChromeWindows
),
new GameInputModel(
GameInputSchema.Ragdoll,
'joystick',
'0f0d-01b3-HORI Fighting Stick mini',
'Windows',
HoriFightStickMiniChromeWindows
),
new GameInputModel(
GameInputSchema.Ragdoll,
'joystick',
'0f0d-01b4-HORI Fighting Stick mini',
'Windows',
HoriFightStickMiniChromeWindows
),
new GameInputModel(
GameInputSchema.Plumber,
'generic',
Expand Down Expand Up @@ -653,7 +685,7 @@ const GameInputModels = [
'Mayflash Arcade Stick (Vendor: 0e8f Product: 0003)',
'Windows',
StandardGamepadMapping.variant({
dpad: new GamepadDirectionsMapping(new AxisAsButton('-', 2), new AxisAsButton('+', 1), new AxisAsButton('+', 2), new AxisAsButton('-', 1)),
dpad: new CombinedAxesAsStick(9, undefined),
center: StandardCenterMapping,
leftStick: undefined,
rightStick: undefined
Expand Down Expand Up @@ -753,7 +785,7 @@ const GameInputModels = [
'0e6f-0111-Afterglow Gamepad for PS3',
'Windows',
StandardGamepadMapping.variant({
dpad: radialDpadAxis9,
dpad: new CombinedAxesAsStick(9, 10),
rightStick: new GamepadAnalogStickMapping(new AxisAsButton('-', 5), new AxisAsButton('+', 2), new AxisAsButton('+', 5), new AxisAsButton('-', 2), 11),
face: new GamepadFaceMapping(3, 2, 1, 0)
})
Expand All @@ -764,7 +796,7 @@ const GameInputModels = [
'Afterglow Gamepad for PS3 (Vendor: 0e6f Product: 0111)',
'Windows',
StandardGamepadMapping.variant({
dpad: radialDpadAxis9,
dpad: new CombinedAxesAsStick(9, 10),
rightStick: new GamepadAnalogStickMapping(new AxisAsButton('-', 5), new AxisAsButton('+', 2), new AxisAsButton('+', 5), new AxisAsButton('-', 2), 11),
face: new GamepadFaceMapping(3, 2, 1, 0)
})
Expand Down
6 changes: 3 additions & 3 deletions src/gameinput.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Vector2 } from './vector2.js'
import { DetectedOS } from './os-detect.js'
import { GameInputOptions } from './gameinput-options.js'
import { GameInputItemState, GameInputState } from './gameinput-state.js'
import { CombinedAxesAsStick, CombinedAxisToButton } from './combined-axis-as-button.js'

/**
* Game Input System
Expand Down Expand Up @@ -322,10 +323,9 @@ class GameInput {
}
const state = player.state[sectionName][itemName]

if (buttonDef instanceof AxisAsButton) {
if (buttonDef instanceof AxisAsButton || buttonDef instanceof CombinedAxisToButton) {
state.value = currentGamepad.axes[buttonDef.index]
state.active = (state.value >= buttonDef.threshold && buttonDef.direction === 'positive') ||
(state.value <= buttonDef.threshold && buttonDef.direction === 'negative')
state.active = buttonDef.test(state.value)
} else {
state.active = currentGamepad.buttons[buttonDef]?.pressed ?? false
state.value = state.active ? 1 : 0
Expand Down
2 changes: 1 addition & 1 deletion src/gamepad-mapping.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @typedef {number|import('./axis-as-button.js').AxisAsButton} SchemaButtonDef
* @typedef {number|import('./axis-as-button.js').AxisAsButton|import('./combined-axis-as-button.js').CombinedAxisToButton} SchemaButtonDef
*/

/**
Expand Down

0 comments on commit 66e990d

Please sign in to comment.