@antha/input 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -103,6 +103,7 @@ function readPlayerBindings({ bindingsMap, activeBindingsMap, rawInputs, msSince
103
103
  holdDuration: {
104
104
  milliseconds: Math.round(durationMs),
105
105
  },
106
+ rawInputs: matchingInputs,
106
107
  value,
107
108
  actCount: previousActiveBinding?.actCount || 0,
108
109
  lastActDuration: previousActiveBinding?.lastActDuration || {
@@ -1,6 +1,6 @@
1
1
  import { type AnyDuration } from 'date-vir';
2
2
  import { NavController, type NavControllerOptions } from 'device-navigation';
3
- import { type BindingAssignments, type PlayersActiveBindings } from './player-bindings.js';
3
+ import { type BindingAssignments, type PlayerPosition, type PlayersActiveBindings } from './player-bindings.js';
4
4
  export { nav, navAttribute, NavController } from 'device-navigation';
5
5
  /**
6
6
  * All supported menu navigation bindings. To ignore any, simply don't allow players to bind to
@@ -87,6 +87,11 @@ export type MenuNavOptions = Readonly<Partial<{
87
87
  export type MenuNavModState = {
88
88
  /** Set to true to enable menu navigation. */
89
89
  isInMenu: boolean;
90
+ /**
91
+ * When defined, only players explicitly set to true may use menu navigation. Omit this or set
92
+ * to `undefined` to allow every player to run menu navigation.
93
+ */
94
+ allowedPlayerMenuNavigation: Partial<Record<PlayerPosition, boolean>> | undefined;
90
95
  /** Omit or set to `undefined` to disable menu nav. */
91
96
  menuNavOptions: Required<MenuNavOptions> | undefined;
92
97
  /** All active bindings for all players. */
@@ -102,6 +107,15 @@ export declare const defaultMenuNavOptions: Required<MenuNavOptions>;
102
107
  * @category Pre-Built Mods
103
108
  */
104
109
  export declare function createAnthaMenuNavMod(options?: Readonly<MenuNavOptions & NavControllerOptions>): import("@antha/engine").AnthaMod<NoInfer<MenuNavModState>>;
110
+ /**
111
+ * Checks whether the given player may use menu navigation under the current allowlist.
112
+ *
113
+ * @category Internal
114
+ */
115
+ export declare function isPlayerMenuNavigationAllowed({ allowedPlayerMenuNavigation, playerPosition, }: Readonly<{
116
+ allowedPlayerMenuNavigation: MenuNavModState['allowedPlayerMenuNavigation'];
117
+ playerPosition: PlayerPosition;
118
+ }>): boolean;
105
119
  /**
106
120
  * The mod created by {@link createAnthaMenuNavMod}.
107
121
  *
@@ -1,7 +1,7 @@
1
1
  import { defineAnthaMod } from '@antha/engine';
2
2
  import { KnownInput } from '@antha/gamepad-type';
3
3
  import { check } from '@augment-vir/assert';
4
- import { getObjectTypedEntries, getObjectTypedValues } from '@augment-vir/common';
4
+ import { getObjectTypedEntries } from '@augment-vir/common';
5
5
  import { convertDuration } from 'date-vir';
6
6
  import { NavController, NavDirection, NavValue } from 'device-navigation';
7
7
  import { InputDirection } from '../raw-inputs/raw-input.js';
@@ -262,6 +262,7 @@ export function createAnthaMenuNavMod(options = {}) {
262
262
  else if (!state.isInMenu) {
263
263
  consumeInactiveMenuActivationBindings({
264
264
  activeBindings: state.activeBindings,
265
+ allowedPlayerMenuNavigation: state.allowedPlayerMenuNavigation,
265
266
  });
266
267
  return;
267
268
  }
@@ -274,7 +275,13 @@ export function createAnthaMenuNavMod(options = {}) {
274
275
  const minimumDirectionalInputValue = state.menuNavOptions.minimumDirectionalInputValue;
275
276
  const bindingsToAct = {};
276
277
  const activeMenuBindings = {};
277
- getObjectTypedValues(state.activeBindings).forEach((playerActiveBindings) => {
278
+ getObjectTypedEntries(state.activeBindings).forEach(([playerPosition, playerActiveBindings,]) => {
279
+ if (!isPlayerMenuNavigationAllowed({
280
+ allowedPlayerMenuNavigation: state.allowedPlayerMenuNavigation,
281
+ playerPosition,
282
+ })) {
283
+ return;
284
+ }
278
285
  getObjectTypedEntries(playerActiveBindings).forEach(([bindingName, activeBinding,]) => {
279
286
  if (!check.isEnumValue(bindingName, MenuNavBinding)) {
280
287
  return;
@@ -284,7 +291,8 @@ export function createAnthaMenuNavMod(options = {}) {
284
291
  activeBinding.value >= minimumDirectionalInputValue) &&
285
292
  (!activeBinding.actCount ||
286
293
  (directionalMenuNavBindings.includes(bindingName) &&
287
- activeBinding.holdDuration.milliseconds >= repeatThreshold &&
294
+ activeBinding.holdDuration.milliseconds >=
295
+ repeatThreshold &&
288
296
  activeBinding.holdDuration.milliseconds -
289
297
  activeBinding.lastActDuration.milliseconds >
290
298
  repeatInterval))) {
@@ -352,8 +360,14 @@ export function createAnthaMenuNavMod(options = {}) {
352
360
  },
353
361
  });
354
362
  }
355
- function consumeInactiveMenuActivationBindings({ activeBindings, }) {
356
- getObjectTypedValues(activeBindings).forEach((playerActiveBindings) => {
363
+ function consumeInactiveMenuActivationBindings({ activeBindings, allowedPlayerMenuNavigation, }) {
364
+ getObjectTypedEntries(activeBindings).forEach(([playerPosition, playerActiveBindings,]) => {
365
+ if (!isPlayerMenuNavigationAllowed({
366
+ allowedPlayerMenuNavigation,
367
+ playerPosition,
368
+ })) {
369
+ return;
370
+ }
357
371
  [
358
372
  MenuNavBinding.MenuEnter,
359
373
  MenuNavBinding.MenuExit,
@@ -367,3 +381,11 @@ function consumeInactiveMenuActivationBindings({ activeBindings, }) {
367
381
  });
368
382
  });
369
383
  }
384
+ /**
385
+ * Checks whether the given player may use menu navigation under the current allowlist.
386
+ *
387
+ * @category Internal
388
+ */
389
+ export function isPlayerMenuNavigationAllowed({ allowedPlayerMenuNavigation, playerPosition, }) {
390
+ return (allowedPlayerMenuNavigation == undefined || !!allowedPlayerMenuNavigation[playerPosition]);
391
+ }
@@ -1,5 +1,5 @@
1
- import { type GamepadInputDeviceKey, type InputDeviceKey } from 'input-device-handler';
2
- import { type InputDirection } from '../raw-inputs/raw-input.js';
1
+ import { InputDeviceKey, type GamepadInputDeviceKey } from 'input-device-handler';
2
+ import { InputDirection, type RawInput } from '../raw-inputs/raw-input.js';
3
3
  /**
4
4
  * A mapping of gamepad keys that simply allows them to be interpreted as different keys. This is
5
5
  * useful for mapping a controller in any port to any other port. For example, mapping the
@@ -49,6 +49,28 @@ export type BindingAssignment = {
49
49
  * @category Internal
50
50
  */
51
51
  export type PlayerPosition = `${number}`;
52
+ /**
53
+ * Shape definition for {@link BindingAssignment}.
54
+ *
55
+ * @category Internal
56
+ */
57
+ export declare const bindingAssignmentShape: import("object-shape-tester").Shape<{
58
+ deviceKey: import("object-shape-tester").Shape<import("@sinclair/typebox").TUnion<(import("@sinclair/typebox").TLiteral<"0"> | import("@sinclair/typebox").TLiteral<"1"> | import("@sinclair/typebox").TLiteral<"2"> | import("@sinclair/typebox").TLiteral<"3"> | import("@sinclair/typebox").TLiteral<"mouse"> | import("@sinclair/typebox").TLiteral<"keyboard"> | import("@sinclair/typebox").TLiteral<"any-gamepad">)[]>>;
59
+ direction: import("object-shape-tester").Shape<import("@sinclair/typebox").TUnion<(import("@sinclair/typebox").TLiteral<InputDirection.Flat> | import("@sinclair/typebox").TLiteral<InputDirection.Negative> | import("@sinclair/typebox").TLiteral<InputDirection.Positive>)[]>>;
60
+ gamepadBrand: import("object-shape-tester").Shape<import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<string>>>;
61
+ inputName: import("object-shape-tester").Shape<import("@sinclair/typebox").TUnsafe<string>>;
62
+ }>;
63
+ /**
64
+ * Shape definition for {@link PlayersBindingAssignments}.
65
+ *
66
+ * @category Internal
67
+ */
68
+ export declare const playersBindingAssignmentsShape: import("object-shape-tester").Shape<import("@sinclair/typebox").TUnsafe<Partial<Record<`${number}`, Partial<Record<string, {
69
+ gamepadBrand?: string;
70
+ deviceKey: "0" | "1" | "2" | "3" | "mouse" | "keyboard" | "any-gamepad";
71
+ inputName: string;
72
+ direction: InputDirection;
73
+ }[]>>>>>>;
52
74
  /**
53
75
  * A collection of bindings for a single player. Used in `createAnthaReadBindingsMod` and
54
76
  * {@link PlayersBindingAssignments}.
@@ -69,6 +91,8 @@ export type PlayersBindingAssignments<BindingNames extends string = string> = Re
69
91
  * @category Internal
70
92
  */
71
93
  export type ActiveBinding = {
94
+ /** The currently active raw inputs that contribute to this binding. */
95
+ rawInputs: RawInput[];
72
96
  /**
73
97
  * The full duration for which the current binding has been active in its current direction.
74
98
  * When an active is first pressed, this will be 0 milliseconds.
@@ -1,6 +1,37 @@
1
+ import { InputDeviceKey } from 'input-device-handler';
2
+ import { defineShape, enumShape, nonEmptyStringShape, optionalShape, recordShape, typedStringShape, } from 'object-shape-tester';
3
+ import { InputDirection } from '../raw-inputs/raw-input.js';
1
4
  /**
2
5
  * A binding assignment device key that maps the input to _any_ connected controller.
3
6
  *
4
7
  * @category Internal
5
8
  */
6
9
  export const AnyGamepad = 'any-gamepad';
10
+ /**
11
+ * Shape definition for {@link BindingAssignment}.
12
+ *
13
+ * @category Internal
14
+ */
15
+ export const bindingAssignmentShape = defineShape({
16
+ deviceKey: enumShape({
17
+ ...InputDeviceKey,
18
+ AnyGamepad,
19
+ }),
20
+ direction: enumShape(InputDirection),
21
+ gamepadBrand: optionalShape(nonEmptyStringShape()),
22
+ inputName: nonEmptyStringShape(),
23
+ });
24
+ /**
25
+ * Shape definition for {@link PlayersBindingAssignments}.
26
+ *
27
+ * @category Internal
28
+ */
29
+ export const playersBindingAssignmentsShape = recordShape({
30
+ keys: typedStringShape(),
31
+ partial: true,
32
+ values: recordShape({
33
+ keys: '',
34
+ partial: true,
35
+ values: [bindingAssignmentShape],
36
+ }),
37
+ });
@@ -67,14 +67,15 @@ export function readRawInputs(state, { msSinceLastExecute, }) {
67
67
  const deviceInputs = {};
68
68
  Object.values(device.currentInputs).forEach((currentInput) => {
69
69
  const direction = calculateInputDirection(currentInput.inputValue);
70
- const previousRawInput = state.rawInputs?.[deviceKey]?.[currentInput.inputName];
71
- const duration = previousRawInput?.direction === direction
72
- ? {
73
- milliseconds: Math.round(previousRawInput.duration.milliseconds + msSinceLastExecute),
74
- }
75
- : {
76
- milliseconds: 0,
77
- };
70
+ const potentialPreviousRawInput = state.rawInputs?.[deviceKey]?.[currentInput.inputName];
71
+ const previousRawInput = potentialPreviousRawInput?.direction === direction
72
+ ? potentialPreviousRawInput
73
+ : undefined;
74
+ const duration = {
75
+ milliseconds: previousRawInput
76
+ ? Math.round(previousRawInput.duration.milliseconds + msSinceLastExecute)
77
+ : 0,
78
+ };
78
79
  const layout = isGamepadDeviceKey(deviceKey)
79
80
  ? findMatchingGamepadLayout({
80
81
  layouts: state.gamepadLayouts || defaultGamepadLayouts,
@@ -99,6 +100,7 @@ export function readRawInputs(state, { msSinceLastExecute, }) {
99
100
  gamepadModel: model?.gamepadModel,
100
101
  });
101
102
  const rawInput = {
103
+ consumedBy: previousRawInput?.consumedBy,
102
104
  mapped: {
103
105
  deviceName: model?.gamepadModel || device.deviceName,
104
106
  gamepadBrand: model?.gamepadBrand,
@@ -24,6 +24,8 @@ export declare function calculateInputDirection(inputValue: number): InputDirect
24
24
  * @category Internal
25
25
  */
26
26
  export type RawInput = Omit<InputValueWrapper<InputDeviceKey, any>, 'details'> & {
27
+ /** An identifier set by a game when it claims this input. */
28
+ consumedBy: string | undefined;
27
29
  mapped: SelectFrom<InputValueWrapper<InputDeviceKey, any>, {
28
30
  /**
29
31
  * `deviceName` is mapped by `gamepadModelMap`. If no mapping exists, this will always
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antha/input",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "An Antha mod for handling inputs.",
5
5
  "keywords": [
6
6
  "vir",
@@ -33,7 +33,7 @@
33
33
  "test:docs": "virmator docs check"
34
34
  },
35
35
  "dependencies": {
36
- "@antha/gamepad-type": "^0.8.0",
36
+ "@antha/gamepad-type": "^0.9.0",
37
37
  "@augment-vir/assert": "^32.2.3",
38
38
  "@augment-vir/common": "^32.2.3",
39
39
  "date-vir": "^9.1.1",
@@ -51,7 +51,7 @@
51
51
  "istanbul-smart-text-reporter": "^1.1.5"
52
52
  },
53
53
  "peerDependencies": {
54
- "@antha/engine": "^0.8.0",
54
+ "@antha/engine": "^0.9.0",
55
55
  "element-vir": ">=26"
56
56
  },
57
57
  "engines": {