like2d 1.0.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.
- package/README.md +66 -0
- package/dist/audio.d.ts +52 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/audio.js +250 -0
- package/dist/events.d.ts +36 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +1 -0
- package/dist/gamecontrollerdb.txt +2219 -0
- package/dist/gamepad-button-map.d.ts +5 -0
- package/dist/gamepad-button-map.d.ts.map +1 -0
- package/dist/gamepad-button-map.js +56 -0
- package/dist/gamepad-db.d.ts +49 -0
- package/dist/gamepad-db.d.ts.map +1 -0
- package/dist/gamepad-db.js +192 -0
- package/dist/gamepad-mapping.d.ts +31 -0
- package/dist/gamepad-mapping.d.ts.map +1 -0
- package/dist/gamepad-mapping.js +191 -0
- package/dist/gamepad.d.ts +56 -0
- package/dist/gamepad.d.ts.map +1 -0
- package/dist/gamepad.js +216 -0
- package/dist/graphics.d.ts +80 -0
- package/dist/graphics.d.ts.map +1 -0
- package/dist/graphics.js +388 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +250 -0
- package/dist/input-state.d.ts +14 -0
- package/dist/input-state.d.ts.map +1 -0
- package/dist/input-state.js +50 -0
- package/dist/input.d.ts +36 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +127 -0
- package/dist/keyboard.d.ts +9 -0
- package/dist/keyboard.d.ts.map +1 -0
- package/dist/keyboard.js +33 -0
- package/dist/mouse.d.ts +20 -0
- package/dist/mouse.d.ts.map +1 -0
- package/dist/mouse.js +84 -0
- package/dist/rect.d.ts +27 -0
- package/dist/rect.d.ts.map +1 -0
- package/dist/rect.js +132 -0
- package/dist/scene.d.ts +10 -0
- package/dist/scene.d.ts.map +1 -0
- package/dist/scene.js +1 -0
- package/dist/timer.d.ts +19 -0
- package/dist/timer.d.ts.map +1 -0
- package/dist/timer.js +86 -0
- package/dist/vector2.d.ts +32 -0
- package/dist/vector2.d.ts.map +1 -0
- package/dist/vector2.js +105 -0
- package/package.json +64 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const GAMEPAD_BUTTON_MAP: Record<string, number>;
|
|
2
|
+
export declare const GAMEPAD_BUTTON_NAMES: Record<number, string>;
|
|
3
|
+
export declare function getButtonName(buttonIndex: number): string;
|
|
4
|
+
export declare function getButtonIndex(buttonName: string): number | undefined;
|
|
5
|
+
//# sourceMappingURL=gamepad-button-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gamepad-button-map.d.ts","sourceRoot":"","sources":["../src/gamepad-button-map.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAiCrD,CAAC;AAGF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAiBvD,CAAC;AAEF,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErE"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Button name to index mapping using physical layout
|
|
2
|
+
export const GAMEPAD_BUTTON_MAP = {
|
|
3
|
+
// Face buttons - physical positions (not labels)
|
|
4
|
+
'ButtonBottom': 0,
|
|
5
|
+
'ButtonRight': 1,
|
|
6
|
+
'ButtonLeft': 2,
|
|
7
|
+
'ButtonTop': 3,
|
|
8
|
+
// Bumpers and triggers
|
|
9
|
+
'LB': 4,
|
|
10
|
+
'L1': 4,
|
|
11
|
+
'RB': 5,
|
|
12
|
+
'R1': 5,
|
|
13
|
+
'LT': 6,
|
|
14
|
+
'L2': 6,
|
|
15
|
+
'RT': 7,
|
|
16
|
+
'R2': 7,
|
|
17
|
+
// Menu buttons
|
|
18
|
+
'Back': 8,
|
|
19
|
+
'Select': 8,
|
|
20
|
+
'Start': 9,
|
|
21
|
+
// Stick presses
|
|
22
|
+
'LS': 10,
|
|
23
|
+
'L3': 10,
|
|
24
|
+
'RS': 11,
|
|
25
|
+
'R3': 11,
|
|
26
|
+
// D-Pad
|
|
27
|
+
'DPadUp': 12,
|
|
28
|
+
'DPadDown': 13,
|
|
29
|
+
'DPadLeft': 14,
|
|
30
|
+
'DPadRight': 15,
|
|
31
|
+
};
|
|
32
|
+
// Reverse mapping: index to primary name
|
|
33
|
+
export const GAMEPAD_BUTTON_NAMES = {
|
|
34
|
+
0: 'ButtonBottom',
|
|
35
|
+
1: 'ButtonRight',
|
|
36
|
+
2: 'ButtonLeft',
|
|
37
|
+
3: 'ButtonTop',
|
|
38
|
+
4: 'LB',
|
|
39
|
+
5: 'RB',
|
|
40
|
+
6: 'LT',
|
|
41
|
+
7: 'RT',
|
|
42
|
+
8: 'Back',
|
|
43
|
+
9: 'Start',
|
|
44
|
+
10: 'LS',
|
|
45
|
+
11: 'RS',
|
|
46
|
+
12: 'DPadUp',
|
|
47
|
+
13: 'DPadDown',
|
|
48
|
+
14: 'DPadLeft',
|
|
49
|
+
15: 'DPadRight',
|
|
50
|
+
};
|
|
51
|
+
export function getButtonName(buttonIndex) {
|
|
52
|
+
return GAMEPAD_BUTTON_NAMES[buttonIndex] ?? `Button${buttonIndex}`;
|
|
53
|
+
}
|
|
54
|
+
export function getButtonIndex(buttonName) {
|
|
55
|
+
return GAMEPAD_BUTTON_MAP[buttonName];
|
|
56
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface ControllerMapping {
|
|
2
|
+
guid: string;
|
|
3
|
+
name: string;
|
|
4
|
+
platform: string;
|
|
5
|
+
buttons: Map<string, number>;
|
|
6
|
+
axes: Map<string, number>;
|
|
7
|
+
dpads: Map<string, {
|
|
8
|
+
hat: number;
|
|
9
|
+
value: number;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export declare const SDL_BUTTONS: readonly ["a", "b", "x", "y", "back", "start", "guide", "leftshoulder", "rightshoulder", "lefttrigger", "righttrigger", "leftstick", "rightstick", "dpup", "dpdown", "dpleft", "dpright", "misc1", "misc2", "paddle1", "paddle2", "paddle3", "paddle4", "touchpad"];
|
|
13
|
+
export type SDLButton = (typeof SDL_BUTTONS)[number];
|
|
14
|
+
export declare const SDL_AXES: readonly ["leftx", "lefty", "rightx", "righty"];
|
|
15
|
+
export type SDLAxis = (typeof SDL_AXES)[number];
|
|
16
|
+
export declare class GamepadDatabase {
|
|
17
|
+
private mappings;
|
|
18
|
+
private vendorProductIndex;
|
|
19
|
+
private loaded;
|
|
20
|
+
load(dbContent: string): void;
|
|
21
|
+
private extractVendorProductKey;
|
|
22
|
+
/**
|
|
23
|
+
* Check if the database has been loaded
|
|
24
|
+
*/
|
|
25
|
+
isLoaded(): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Get the number of loaded mappings
|
|
28
|
+
*/
|
|
29
|
+
getMappingCount(): number;
|
|
30
|
+
/**
|
|
31
|
+
* Look up a controller mapping by GUID
|
|
32
|
+
*/
|
|
33
|
+
getMapping(guid: string): ControllerMapping | undefined;
|
|
34
|
+
getMappingByVendorProduct(vendor: number, product: number): ControllerMapping | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Check if a controller is in the database
|
|
37
|
+
*/
|
|
38
|
+
hasController(guid: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Get all mappings for a specific platform
|
|
41
|
+
*/
|
|
42
|
+
getMappingsByPlatform(platform: string): ControllerMapping[];
|
|
43
|
+
/**
|
|
44
|
+
* Parse a single database line
|
|
45
|
+
*/
|
|
46
|
+
private parseLine;
|
|
47
|
+
}
|
|
48
|
+
export declare const gamepadDatabase: GamepadDatabase;
|
|
49
|
+
//# sourceMappingURL=gamepad-db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gamepad-db.d.ts","sourceRoot":"","sources":["../src/gamepad-db.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7B,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE1B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAGD,eAAO,MAAM,WAAW,qQASd,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAGrD,eAAO,MAAM,QAAQ,iDAGX,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,kBAAkB,CAAwC;IAClE,OAAO,CAAC,MAAM,CAAS;IAEvB,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IA0B7B,OAAO,CAAC,uBAAuB;IAU/B;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,eAAe,IAAI,MAAM;IAIzB;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAIvD,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAKzF;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIpC;;OAEG;IACH,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,EAAE;IAa5D;;OAEG;IACH,OAAO,CAAC,SAAS;CAiFlB;AAGD,eAAO,MAAM,eAAe,iBAAwB,CAAC"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// SDL Game Controller Database parser
|
|
2
|
+
// Parses the gamecontrollerdb.txt file format
|
|
3
|
+
// SDL button names that we care about for standard layout
|
|
4
|
+
export const SDL_BUTTONS = [
|
|
5
|
+
'a', 'b', 'x', 'y',
|
|
6
|
+
'back', 'start', 'guide',
|
|
7
|
+
'leftshoulder', 'rightshoulder',
|
|
8
|
+
'lefttrigger', 'righttrigger',
|
|
9
|
+
'leftstick', 'rightstick',
|
|
10
|
+
'dpup', 'dpdown', 'dpleft', 'dpright',
|
|
11
|
+
'misc1', 'misc2', 'paddle1', 'paddle2', 'paddle3', 'paddle4',
|
|
12
|
+
'touchpad',
|
|
13
|
+
];
|
|
14
|
+
// SDL axis names
|
|
15
|
+
export const SDL_AXES = [
|
|
16
|
+
'leftx', 'lefty',
|
|
17
|
+
'rightx', 'righty',
|
|
18
|
+
];
|
|
19
|
+
export class GamepadDatabase {
|
|
20
|
+
constructor() {
|
|
21
|
+
Object.defineProperty(this, "mappings", {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
writable: true,
|
|
25
|
+
value: new Map()
|
|
26
|
+
});
|
|
27
|
+
Object.defineProperty(this, "vendorProductIndex", {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
configurable: true,
|
|
30
|
+
writable: true,
|
|
31
|
+
value: new Map()
|
|
32
|
+
});
|
|
33
|
+
Object.defineProperty(this, "loaded", {
|
|
34
|
+
enumerable: true,
|
|
35
|
+
configurable: true,
|
|
36
|
+
writable: true,
|
|
37
|
+
value: false
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
load(dbContent) {
|
|
41
|
+
this.mappings.clear();
|
|
42
|
+
this.vendorProductIndex.clear();
|
|
43
|
+
const lines = dbContent.split('\n');
|
|
44
|
+
for (const line of lines) {
|
|
45
|
+
const trimmed = line.trim();
|
|
46
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const mapping = this.parseLine(trimmed);
|
|
50
|
+
if (mapping) {
|
|
51
|
+
this.mappings.set(mapping.guid, mapping);
|
|
52
|
+
const vpKey = this.extractVendorProductKey(mapping.guid);
|
|
53
|
+
if (vpKey !== null && !this.vendorProductIndex.has(vpKey)) {
|
|
54
|
+
this.vendorProductIndex.set(vpKey, mapping);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
this.loaded = true;
|
|
59
|
+
}
|
|
60
|
+
extractVendorProductKey(guid) {
|
|
61
|
+
if (guid.length < 20)
|
|
62
|
+
return null;
|
|
63
|
+
const vendorHex = guid.substring(8, 12);
|
|
64
|
+
const productHex = guid.substring(16, 20);
|
|
65
|
+
const vendor = parseInt(vendorHex.substring(2, 4) + vendorHex.substring(0, 2), 16);
|
|
66
|
+
const product = parseInt(productHex.substring(2, 4) + productHex.substring(0, 2), 16);
|
|
67
|
+
if (isNaN(vendor) || isNaN(product))
|
|
68
|
+
return null;
|
|
69
|
+
return 0x10000 * vendor + product;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Check if the database has been loaded
|
|
73
|
+
*/
|
|
74
|
+
isLoaded() {
|
|
75
|
+
return this.loaded;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get the number of loaded mappings
|
|
79
|
+
*/
|
|
80
|
+
getMappingCount() {
|
|
81
|
+
return this.mappings.size;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Look up a controller mapping by GUID
|
|
85
|
+
*/
|
|
86
|
+
getMapping(guid) {
|
|
87
|
+
return this.mappings.get(guid.toLowerCase());
|
|
88
|
+
}
|
|
89
|
+
getMappingByVendorProduct(vendor, product) {
|
|
90
|
+
const key = 0x10000 * vendor + product;
|
|
91
|
+
return this.vendorProductIndex.get(key);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if a controller is in the database
|
|
95
|
+
*/
|
|
96
|
+
hasController(guid) {
|
|
97
|
+
return this.mappings.has(guid.toLowerCase());
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get all mappings for a specific platform
|
|
101
|
+
*/
|
|
102
|
+
getMappingsByPlatform(platform) {
|
|
103
|
+
const result = [];
|
|
104
|
+
const platformLower = platform.toLowerCase();
|
|
105
|
+
for (const mapping of this.mappings.values()) {
|
|
106
|
+
if (mapping.platform.toLowerCase() === platformLower) {
|
|
107
|
+
result.push(mapping);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Parse a single database line
|
|
114
|
+
*/
|
|
115
|
+
parseLine(line) {
|
|
116
|
+
// Format: GUID,Name,mapping pairs...,platform:Platform,
|
|
117
|
+
const parts = line.split(',');
|
|
118
|
+
if (parts.length < 3) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const guid = parts[0].toLowerCase().trim();
|
|
122
|
+
const name = parts[1].trim();
|
|
123
|
+
const buttons = new Map();
|
|
124
|
+
const axes = new Map();
|
|
125
|
+
const dpads = new Map();
|
|
126
|
+
let platform = '';
|
|
127
|
+
// Parse mapping pairs (key:value)
|
|
128
|
+
for (let i = 2; i < parts.length; i++) {
|
|
129
|
+
const part = parts[i].trim();
|
|
130
|
+
if (!part)
|
|
131
|
+
continue;
|
|
132
|
+
// Check for platform specifier
|
|
133
|
+
if (part.startsWith('platform:')) {
|
|
134
|
+
platform = part.substring('platform:'.length).trim();
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// Parse key:value pairs
|
|
138
|
+
const colonIndex = part.indexOf(':');
|
|
139
|
+
if (colonIndex === -1)
|
|
140
|
+
continue;
|
|
141
|
+
const key = part.substring(0, colonIndex).trim();
|
|
142
|
+
const value = part.substring(colonIndex + 1).trim();
|
|
143
|
+
if (!key || !value)
|
|
144
|
+
continue;
|
|
145
|
+
// Parse button mapping (b0, b1, etc.)
|
|
146
|
+
if (value.startsWith('b')) {
|
|
147
|
+
const buttonIndex = parseInt(value.substring(1), 10);
|
|
148
|
+
if (!isNaN(buttonIndex)) {
|
|
149
|
+
buttons.set(key, buttonIndex);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Parse axis mapping (a0, a1, etc.)
|
|
153
|
+
else if (value.startsWith('a')) {
|
|
154
|
+
const axisIndex = parseInt(value.substring(1), 10);
|
|
155
|
+
if (!isNaN(axisIndex)) {
|
|
156
|
+
axes.set(key, axisIndex);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Parse hat mapping (h0.1, h0.4, etc.) - used for D-pad
|
|
160
|
+
else if (value.startsWith('h')) {
|
|
161
|
+
const hatParts = value.substring(1).split('.');
|
|
162
|
+
if (hatParts.length === 2) {
|
|
163
|
+
const hatIndex = parseInt(hatParts[0], 10);
|
|
164
|
+
const hatValue = parseInt(hatParts[1], 10);
|
|
165
|
+
if (!isNaN(hatIndex) && !isNaN(hatValue)) {
|
|
166
|
+
dpads.set(key, { hat: hatIndex, value: hatValue });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Parse axis-as-button mappings (+a0, -a0) - some controllers use axes for D-pad
|
|
171
|
+
else if (value.startsWith('+a') || value.startsWith('-a')) {
|
|
172
|
+
const axisIndex = parseInt(value.substring(2), 10);
|
|
173
|
+
const direction = value[0];
|
|
174
|
+
if (!isNaN(axisIndex)) {
|
|
175
|
+
// Store as special button with axis info
|
|
176
|
+
// We'll handle this in the mapping layer
|
|
177
|
+
buttons.set(`${key}:${direction}`, axisIndex);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
guid,
|
|
183
|
+
name,
|
|
184
|
+
platform,
|
|
185
|
+
buttons,
|
|
186
|
+
axes,
|
|
187
|
+
dpads,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Singleton instance
|
|
192
|
+
export const gamepadDatabase = new GamepadDatabase();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface ButtonMapping {
|
|
2
|
+
toStandard: Map<number, number>;
|
|
3
|
+
fromStandard: Map<number, number>;
|
|
4
|
+
controllerName: string;
|
|
5
|
+
hasMapping: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare class GamepadMapping {
|
|
8
|
+
private mappings;
|
|
9
|
+
private dbLoaded;
|
|
10
|
+
loadDatabase(): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Load database from raw text content
|
|
13
|
+
*/
|
|
14
|
+
loadDatabaseFromText(content: string): void;
|
|
15
|
+
/**
|
|
16
|
+
* Get or create a button mapping for a specific gamepad
|
|
17
|
+
*/
|
|
18
|
+
getMapping(gamepad: Gamepad): ButtonMapping;
|
|
19
|
+
/**
|
|
20
|
+
* Clear all cached mappings
|
|
21
|
+
*/
|
|
22
|
+
clear(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Create a new button mapping for a gamepad
|
|
25
|
+
*/
|
|
26
|
+
private createMapping;
|
|
27
|
+
private findDatabaseMapping;
|
|
28
|
+
private extractVendorProduct;
|
|
29
|
+
}
|
|
30
|
+
export declare const gamepadMapping: GamepadMapping;
|
|
31
|
+
//# sourceMappingURL=gamepad-mapping.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gamepad-mapping.d.ts","sourceRoot":"","sources":["../src/gamepad-mapping.ts"],"names":[],"mappings":"AA6CA,MAAM,WAAW,aAAa;IAE5B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAElC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,QAAQ,CAAS;IAEnB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IA+BnC;;OAEG;IACH,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK3C;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa;IAa3C;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,OAAO,CAAC,aAAa;IAuDrB,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,oBAAoB;CAuB7B;AAGD,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Gamepad button mapping layer
|
|
2
|
+
// Bridges SDL database mappings with our internal button naming system
|
|
3
|
+
import { gamepadDatabase } from './gamepad-db';
|
|
4
|
+
import { getButtonIndex } from './gamepad-button-map';
|
|
5
|
+
// Map SDL button names to our internal button names
|
|
6
|
+
const SDL_TO_INTERNAL_BUTTON = {
|
|
7
|
+
// Face buttons
|
|
8
|
+
'a': 'ButtonBottom',
|
|
9
|
+
'b': 'ButtonRight',
|
|
10
|
+
'x': 'ButtonLeft',
|
|
11
|
+
'y': 'ButtonTop',
|
|
12
|
+
// Bumpers and triggers
|
|
13
|
+
'leftshoulder': 'LB',
|
|
14
|
+
'rightshoulder': 'RB',
|
|
15
|
+
'lefttrigger': 'LT',
|
|
16
|
+
'righttrigger': 'RT',
|
|
17
|
+
// Menu buttons
|
|
18
|
+
'back': 'Back',
|
|
19
|
+
'start': 'Start',
|
|
20
|
+
'guide': 'Guide',
|
|
21
|
+
// Stick presses
|
|
22
|
+
'leftstick': 'LS',
|
|
23
|
+
'rightstick': 'RS',
|
|
24
|
+
// D-Pad
|
|
25
|
+
'dpup': 'DPadUp',
|
|
26
|
+
'dpdown': 'DPadDown',
|
|
27
|
+
'dpleft': 'DPadLeft',
|
|
28
|
+
'dpright': 'DPadRight',
|
|
29
|
+
// Misc buttons
|
|
30
|
+
'misc1': 'Misc1',
|
|
31
|
+
'misc2': 'Misc2',
|
|
32
|
+
'paddle1': 'Paddle1',
|
|
33
|
+
'paddle2': 'Paddle2',
|
|
34
|
+
'paddle3': 'Paddle3',
|
|
35
|
+
'paddle4': 'Paddle4',
|
|
36
|
+
'touchpad': 'Touchpad',
|
|
37
|
+
};
|
|
38
|
+
export class GamepadMapping {
|
|
39
|
+
constructor() {
|
|
40
|
+
Object.defineProperty(this, "mappings", {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
writable: true,
|
|
44
|
+
value: new Map()
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(this, "dbLoaded", {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
writable: true,
|
|
50
|
+
value: false
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async loadDatabase() {
|
|
54
|
+
if (this.dbLoaded)
|
|
55
|
+
return;
|
|
56
|
+
const sources = ['/like/gamecontrollerdb.txt', './gamecontrollerdb.txt'];
|
|
57
|
+
for (const src of sources) {
|
|
58
|
+
try {
|
|
59
|
+
const res = await fetch(src);
|
|
60
|
+
if (res.ok) {
|
|
61
|
+
gamepadDatabase.load(await res.text());
|
|
62
|
+
this.dbLoaded = true;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch { }
|
|
67
|
+
}
|
|
68
|
+
if (!this.dbLoaded) {
|
|
69
|
+
try {
|
|
70
|
+
// @ts-ignore - Vite handles ?raw imports
|
|
71
|
+
const module = await import('./gamecontrollerdb.txt?raw');
|
|
72
|
+
if (typeof module.default === 'string') {
|
|
73
|
+
gamepadDatabase.load(module.default);
|
|
74
|
+
this.dbLoaded = true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch { }
|
|
78
|
+
}
|
|
79
|
+
if (this.dbLoaded) {
|
|
80
|
+
console.log(`[Gamepad] Loaded ${gamepadDatabase.getMappingCount()} controller mappings`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Load database from raw text content
|
|
85
|
+
*/
|
|
86
|
+
loadDatabaseFromText(content) {
|
|
87
|
+
gamepadDatabase.load(content);
|
|
88
|
+
this.dbLoaded = true;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get or create a button mapping for a specific gamepad
|
|
92
|
+
*/
|
|
93
|
+
getMapping(gamepad) {
|
|
94
|
+
// Use the gamepad's id as a key (this contains the GUID in most browsers)
|
|
95
|
+
const key = `${gamepad.id}_${gamepad.index}`;
|
|
96
|
+
if (this.mappings.has(key)) {
|
|
97
|
+
return this.mappings.get(key);
|
|
98
|
+
}
|
|
99
|
+
const mapping = this.createMapping(gamepad);
|
|
100
|
+
this.mappings.set(key, mapping);
|
|
101
|
+
return mapping;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Clear all cached mappings
|
|
105
|
+
*/
|
|
106
|
+
clear() {
|
|
107
|
+
this.mappings.clear();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Create a new button mapping for a gamepad
|
|
111
|
+
*/
|
|
112
|
+
createMapping(gamepad) {
|
|
113
|
+
const toStandard = new Map();
|
|
114
|
+
const fromStandard = new Map();
|
|
115
|
+
// If browser provides "standard" mapping, trust it - don't override with DB
|
|
116
|
+
if (gamepad.mapping === 'standard') {
|
|
117
|
+
for (let i = 0; i < gamepad.buttons.length; i++) {
|
|
118
|
+
toStandard.set(i, i);
|
|
119
|
+
fromStandard.set(i, i);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
toStandard,
|
|
123
|
+
fromStandard,
|
|
124
|
+
controllerName: gamepad.id,
|
|
125
|
+
hasMapping: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// Try to find a mapping in the database
|
|
129
|
+
const dbMapping = this.findDatabaseMapping(gamepad);
|
|
130
|
+
if (dbMapping) {
|
|
131
|
+
for (const [sdlButton, controllerButtonIndex] of dbMapping.buttons) {
|
|
132
|
+
const internalName = SDL_TO_INTERNAL_BUTTON[sdlButton];
|
|
133
|
+
if (internalName) {
|
|
134
|
+
const standardIndex = getButtonIndex(internalName);
|
|
135
|
+
if (standardIndex !== undefined) {
|
|
136
|
+
toStandard.set(controllerButtonIndex, standardIndex);
|
|
137
|
+
fromStandard.set(standardIndex, controllerButtonIndex);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
toStandard,
|
|
143
|
+
fromStandard,
|
|
144
|
+
controllerName: dbMapping.name,
|
|
145
|
+
hasMapping: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// No database mapping found - use default layout
|
|
149
|
+
for (let i = 0; i < gamepad.buttons.length; i++) {
|
|
150
|
+
toStandard.set(i, i);
|
|
151
|
+
fromStandard.set(i, i);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
toStandard,
|
|
155
|
+
fromStandard,
|
|
156
|
+
controllerName: gamepad.id,
|
|
157
|
+
hasMapping: false,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
findDatabaseMapping(gamepad) {
|
|
161
|
+
if (!this.dbLoaded)
|
|
162
|
+
return undefined;
|
|
163
|
+
const vp = this.extractVendorProduct(gamepad);
|
|
164
|
+
if (vp) {
|
|
165
|
+
return gamepadDatabase.getMappingByVendorProduct(vp.vendor, vp.product);
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
extractVendorProduct(gamepad) {
|
|
170
|
+
const id = gamepad.id;
|
|
171
|
+
const vendorProductMatch = id.match(/Vendor:\s*([0-9a-fA-F]+)\s+Product:\s*([0-9a-fA-F]+)/i);
|
|
172
|
+
if (vendorProductMatch) {
|
|
173
|
+
const vendor = parseInt(vendorProductMatch[1], 16);
|
|
174
|
+
const product = parseInt(vendorProductMatch[2], 16);
|
|
175
|
+
if (!isNaN(vendor) && !isNaN(product)) {
|
|
176
|
+
return { vendor, product };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const hexMatch = id.match(/^([0-9a-fA-F]{4})[\s-]+([0-9a-fA-F]{4})/);
|
|
180
|
+
if (hexMatch) {
|
|
181
|
+
const vendor = parseInt(hexMatch[1], 16);
|
|
182
|
+
const product = parseInt(hexMatch[2], 16);
|
|
183
|
+
if (!isNaN(vendor) && !isNaN(product)) {
|
|
184
|
+
return { vendor, product };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Singleton instance
|
|
191
|
+
export const gamepadMapping = new GamepadMapping();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getButtonName, getButtonIndex } from './gamepad-button-map';
|
|
2
|
+
import { ButtonMapping } from './gamepad-mapping';
|
|
3
|
+
export { getButtonName, getButtonIndex };
|
|
4
|
+
export interface GamepadButtonEvent {
|
|
5
|
+
gamepadIndex: number;
|
|
6
|
+
buttonIndex: number;
|
|
7
|
+
buttonName: string;
|
|
8
|
+
rawButtonIndex: number;
|
|
9
|
+
}
|
|
10
|
+
export interface StickPosition {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
}
|
|
14
|
+
export declare class Gamepad {
|
|
15
|
+
private buttonTrackers;
|
|
16
|
+
private connectedGamepads;
|
|
17
|
+
private buttonMappings;
|
|
18
|
+
constructor();
|
|
19
|
+
init(): Promise<void>;
|
|
20
|
+
private extractVendorProduct;
|
|
21
|
+
private setupEventListeners;
|
|
22
|
+
update(): {
|
|
23
|
+
pressed: GamepadButtonEvent[];
|
|
24
|
+
released: GamepadButtonEvent[];
|
|
25
|
+
};
|
|
26
|
+
isConnected(gamepadIndex: number): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Check if a button is currently pressed on a specific gamepad
|
|
29
|
+
* Uses mapped button indices (standard layout)
|
|
30
|
+
*/
|
|
31
|
+
isButtonDown(gamepadIndex: number, button: number | string): boolean;
|
|
32
|
+
isButtonDownOnAny(button: number | string): boolean;
|
|
33
|
+
getPressedButtons(gamepadIndex: number): Set<number>;
|
|
34
|
+
getConnectedGamepads(): number[];
|
|
35
|
+
/**
|
|
36
|
+
* Get the raw Gamepad object for a specific index
|
|
37
|
+
*/
|
|
38
|
+
getGamepad(gamepadIndex: number): globalThis.Gamepad | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Get the button mapping for a specific gamepad
|
|
41
|
+
*/
|
|
42
|
+
getButtonMapping(gamepadIndex: number): ButtonMapping | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Check if a gamepad has a known mapping from the database
|
|
45
|
+
*/
|
|
46
|
+
hasMapping(gamepadIndex: number): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Get the controller name for a specific gamepad
|
|
49
|
+
*/
|
|
50
|
+
getControllerName(gamepadIndex: number): string | undefined;
|
|
51
|
+
getAxis(gamepadIndex: number, axisIndex: number): number;
|
|
52
|
+
getLeftStick(gamepadIndex: number): StickPosition;
|
|
53
|
+
getRightStick(gamepadIndex: number): StickPosition;
|
|
54
|
+
}
|
|
55
|
+
export declare const gamepad: Gamepad;
|
|
56
|
+
//# sourceMappingURL=gamepad.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gamepad.d.ts","sourceRoot":"","sources":["../src/gamepad.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,EAAkB,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC;AAEzC,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAkBD,qBAAa,OAAO;IAClB,OAAO,CAAC,cAAc,CAAgD;IACtE,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,cAAc,CAAoC;;IAMpD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,OAAO,CAAC,oBAAoB;IAwB5B,OAAO,CAAC,mBAAmB;IA6B3B,MAAM,IAAI;QAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;QAAC,QAAQ,EAAE,kBAAkB,EAAE,CAAA;KAAE;IA0D3E,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAI1C;;;OAGG;IACH,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO;IAOpE,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO;IASnD,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAKpD,oBAAoB,IAAI,MAAM,EAAE;IAIhC;;OAEG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,UAAU,CAAC,OAAO,GAAG,SAAS;IAIhE;;OAEG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIjE;;OAEG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAKzC;;OAEG;IACH,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAK3D,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAMxD,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa;IAMjD,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa;CAKnD;AAED,eAAO,MAAM,OAAO,SAAgB,CAAC"}
|