rgame 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +26 -0
- data/README.md +406 -0
- data/docs/api/README.md +167 -0
- data/docs/api/app.md +192 -0
- data/docs/api/assets.md +426 -0
- data/docs/api/audio.md +208 -0
- data/docs/api/components.md +321 -0
- data/docs/api/drawing.md +330 -0
- data/docs/api/game.md +99 -0
- data/docs/api/images.md +118 -0
- data/docs/api/input.md +179 -0
- data/docs/api/internals.md +110 -0
- data/docs/api/scene_graph.md +159 -0
- data/docs/api/signals.md +142 -0
- data/docs/api/systems.md +98 -0
- data/docs/api/text.md +116 -0
- data/docs/api/toolbox.md +240 -0
- data/docs/api/values.md +101 -0
- data/ext/README.md +225 -0
- data/ext/rgame_core/app/app.c +721 -0
- data/ext/rgame_core/app/app_gl.h +64 -0
- data/ext/rgame_core/app/frame_loop.c +42 -0
- data/ext/rgame_core/app/frame_loop.h +54 -0
- data/ext/rgame_core/audio/audio.c +466 -0
- data/ext/rgame_core/audio/audio_internal.h +45 -0
- data/ext/rgame_core/audio/vorbis_decoder.c +282 -0
- data/ext/rgame_core/audio/vorbis_decoder.h +45 -0
- data/ext/rgame_core/example.rb +188 -0
- data/ext/rgame_core/extconf.rb +167 -0
- data/ext/rgame_core/graphics/backend.c +52 -0
- data/ext/rgame_core/graphics/backend.h +64 -0
- data/ext/rgame_core/graphics/canvas.c +247 -0
- data/ext/rgame_core/graphics/canvas.h +143 -0
- data/ext/rgame_core/graphics/clip.c +87 -0
- data/ext/rgame_core/graphics/clip.h +89 -0
- data/ext/rgame_core/graphics/draw_queue.c +216 -0
- data/ext/rgame_core/graphics/draw_queue.h +174 -0
- data/ext/rgame_core/graphics/gl_backend.c +122 -0
- data/ext/rgame_core/graphics/gl_backend.h +43 -0
- data/ext/rgame_core/graphics/image.c +304 -0
- data/ext/rgame_core/graphics/image_internal.h +30 -0
- data/ext/rgame_core/graphics/primitives.c +189 -0
- data/ext/rgame_core/graphics/primitives.h +111 -0
- data/ext/rgame_core/graphics/recording.c +119 -0
- data/ext/rgame_core/graphics/recording.h +88 -0
- data/ext/rgame_core/graphics/texture.c +181 -0
- data/ext/rgame_core/graphics/texture.h +165 -0
- data/ext/rgame_core/graphics/transform.c +128 -0
- data/ext/rgame_core/graphics/transform.h +106 -0
- data/ext/rgame_core/include/rgame/core.h +577 -0
- data/ext/rgame_core/input/device_slots.c +103 -0
- data/ext/rgame_core/input/device_slots.h +93 -0
- data/ext/rgame_core/input/gamepad.c +145 -0
- data/ext/rgame_core/input/gamepad.h +63 -0
- data/ext/rgame_core/input/input.c +109 -0
- data/ext/rgame_core/input/input.h +99 -0
- data/ext/rgame_core/ruby/audio_ext.c +321 -0
- data/ext/rgame_core/ruby/core_ext.c +513 -0
- data/ext/rgame_core/ruby/core_ext.h +51 -0
- data/ext/rgame_core/ruby/font_ext.c +168 -0
- data/ext/rgame_core/ruby/image_ext.c +230 -0
- data/ext/rgame_core/ruby/recording_ext.c +186 -0
- data/ext/rgame_core/ruby/renderer_ext.c +376 -0
- data/ext/rgame_core/text/atlas.c +59 -0
- data/ext/rgame_core/text/atlas.h +85 -0
- data/ext/rgame_core/text/font.c +281 -0
- data/ext/rgame_core/text/font.h +139 -0
- data/ext/rgame_core/text/font_atlas.c +385 -0
- data/ext/rgame_core/text/font_internal.h +47 -0
- data/ext/rgame_core/text/glyph_cache.c +142 -0
- data/ext/rgame_core/text/glyph_cache.h +89 -0
- data/ext/rgame_core/vendor/README.md +159 -0
- data/ext/rgame_core/vendor/miniaudio.h +95864 -0
- data/ext/rgame_core/vendor/miniaudio_impl.c +62 -0
- data/ext/rgame_core/vendor/stb_image.h +7988 -0
- data/ext/rgame_core/vendor/stb_image_impl.c +31 -0
- data/ext/rgame_core/vendor/stb_truetype.h +5079 -0
- data/ext/rgame_core/vendor/stb_truetype_impl.c +23 -0
- data/ext/rgame_core/vendor/stb_vorbis.c +5584 -0
- data/ext/rgame_core/vendor/stb_vorbis_impl.c +29 -0
- data/ext/rgame_util/color.c +19 -0
- data/ext/rgame_util/color.h +60 -0
- data/ext/rgame_util/color_ext.c +156 -0
- data/ext/rgame_util/extconf.rb +27 -0
- data/ext/rgame_util/tensor.c +186 -0
- data/ext/rgame_util/util_ext.c +27 -0
- data/ext/rgame_util/util_ext.h +16 -0
- data/lib/rgame/boot.rb +13 -0
- data/lib/rgame/core/app.rb +82 -0
- data/lib/rgame/core/asset_manager.rb +224 -0
- data/lib/rgame/core/audio.rb +124 -0
- data/lib/rgame/core/font.rb +49 -0
- data/lib/rgame/core/gamepad.rb +55 -0
- data/lib/rgame/core/image.rb +55 -0
- data/lib/rgame/core/input.rb +77 -0
- data/lib/rgame/core/nine_slice.rb +163 -0
- data/lib/rgame/core/recording.rb +52 -0
- data/lib/rgame/core/renderer.rb +363 -0
- data/lib/rgame/core/sprite_sheet.rb +108 -0
- data/lib/rgame/core/tile_map_renderer.rb +160 -0
- data/lib/rgame/core/ui_atlas.rb +86 -0
- data/lib/rgame/core.rb +24 -0
- data/lib/rgame/engine/actor.rb +53 -0
- data/lib/rgame/engine/animation_set.rb +49 -0
- data/lib/rgame/engine/animator.rb +44 -0
- data/lib/rgame/engine/audio_bus.rb +24 -0
- data/lib/rgame/engine/audio_director.rb +29 -0
- data/lib/rgame/engine/body.rb +49 -0
- data/lib/rgame/engine/cached_label.rb +33 -0
- data/lib/rgame/engine/camera.rb +33 -0
- data/lib/rgame/engine/camera_view.rb +28 -0
- data/lib/rgame/engine/circle_collider.rb +32 -0
- data/lib/rgame/engine/collision_box.rb +34 -0
- data/lib/rgame/engine/collision_system.rb +44 -0
- data/lib/rgame/engine/component.rb +30 -0
- data/lib/rgame/engine/components/action_trigger.rb +41 -0
- data/lib/rgame/engine/components/animated_sprite.rb +63 -0
- data/lib/rgame/engine/components/character_body.rb +70 -0
- data/lib/rgame/engine/components/circle_collider.rb +44 -0
- data/lib/rgame/engine/components/collision_world.rb +103 -0
- data/lib/rgame/engine/components/despawn_offscreen.rb +26 -0
- data/lib/rgame/engine/components/path_follow.rb +84 -0
- data/lib/rgame/engine/components/player_controller.rb +24 -0
- data/lib/rgame/engine/components/pool.rb +53 -0
- data/lib/rgame/engine/components/screen_wrap.rb +27 -0
- data/lib/rgame/engine/components/sprite.rb +31 -0
- data/lib/rgame/engine/components/targeting.rb +54 -0
- data/lib/rgame/engine/components/thrust_controller.rb +65 -0
- data/lib/rgame/engine/components/tile_world.rb +68 -0
- data/lib/rgame/engine/components/timer.rb +75 -0
- data/lib/rgame/engine/components/velocity.rb +27 -0
- data/lib/rgame/engine/components/wander_controller.rb +60 -0
- data/lib/rgame/engine/debug_overlay.rb +106 -0
- data/lib/rgame/engine/i18n.rb +97 -0
- data/lib/rgame/engine/input/action_mapper.rb +46 -0
- data/lib/rgame/engine/input/actions.rb +41 -0
- data/lib/rgame/engine/input/player_controller.rb +14 -0
- data/lib/rgame/engine/matrix.rb +32 -0
- data/lib/rgame/engine/node2d.rb +271 -0
- data/lib/rgame/engine/path.rb +78 -0
- data/lib/rgame/engine/pool.rb +51 -0
- data/lib/rgame/engine/resettable.rb +67 -0
- data/lib/rgame/engine/scene/scene_stack.rb +65 -0
- data/lib/rgame/engine/signal.rb +75 -0
- data/lib/rgame/engine/spatial_hash.rb +71 -0
- data/lib/rgame/engine/tile_collision.rb +78 -0
- data/lib/rgame/engine/tile_map.rb +149 -0
- data/lib/rgame/engine/tileset.rb +101 -0
- data/lib/rgame/engine/timer.rb +51 -0
- data/lib/rgame/engine.rb +68 -0
- data/lib/rgame/fonts/LiberationSans-Regular.ttf +0 -0
- data/lib/rgame/fonts/OFL.txt +102 -0
- data/lib/rgame/game.rb +129 -0
- data/lib/rgame/util/color.rb +27 -0
- data/lib/rgame/util/controls.rb +107 -0
- data/lib/rgame/util/tensor.rb +12 -0
- data/lib/rgame/util.rb +8 -0
- data/lib/rgame/version.rb +12 -0
- data/lib/rgame.rb +20 -0
- metadata +215 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#include "input/device_slots.h"
|
|
2
|
+
|
|
3
|
+
#include <string.h>
|
|
4
|
+
|
|
5
|
+
/* memcmp returns 0 when the two byte ranges are identical, so this reads as
|
|
6
|
+
* "same GUID" rather than as an inverted comparison at each call site. */
|
|
7
|
+
static int guid_equal(const rgame_device_guid *a, const rgame_device_guid *b) {
|
|
8
|
+
return memcmp(a->bytes, b->bytes, RGAME_DEVICE_GUID_BYTES) == 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static int slot_in_range(int slot) {
|
|
12
|
+
return slot >= 0 && slot < RGAME_MAX_DEVICE_SLOTS;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
void rgame_device_slots_init(rgame_device_slots *table) {
|
|
16
|
+
for (int i = 0; i < RGAME_MAX_DEVICE_SLOTS; i++) {
|
|
17
|
+
rgame_device_slot *slot = &table->slots[i];
|
|
18
|
+
slot->connected = 0;
|
|
19
|
+
slot->remembered = 0;
|
|
20
|
+
slot->instance_id = RGAME_DEVICE_SLOT_NONE;
|
|
21
|
+
memset(slot->guid.bytes, 0, RGAME_DEVICE_GUID_BYTES);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
int rgame_device_slots_find(const rgame_device_slots *table, int instance_id) {
|
|
26
|
+
for (int i = 0; i < RGAME_MAX_DEVICE_SLOTS; i++) {
|
|
27
|
+
const rgame_device_slot *slot = &table->slots[i];
|
|
28
|
+
if (slot->connected && slot->instance_id == instance_id) {
|
|
29
|
+
return i;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return RGAME_DEVICE_SLOT_NONE;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
int rgame_device_slots_connect(rgame_device_slots *table,
|
|
36
|
+
const rgame_device_guid *guid,
|
|
37
|
+
int instance_id) {
|
|
38
|
+
/* Already connected: a duplicate event must not consume a second slot. */
|
|
39
|
+
int existing = rgame_device_slots_find(table, instance_id);
|
|
40
|
+
if (existing != RGAME_DEVICE_SLOT_NONE) {
|
|
41
|
+
return existing;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Preferred: a free slot that remembers this GUID — the reconnect case.
|
|
45
|
+
* Only free slots are considered, which is what keeps two identical pads
|
|
46
|
+
* (same GUID, see the header) from fighting over one slot. */
|
|
47
|
+
for (int i = 0; i < RGAME_MAX_DEVICE_SLOTS; i++) {
|
|
48
|
+
rgame_device_slot *slot = &table->slots[i];
|
|
49
|
+
if (!slot->connected && slot->remembered && guid_equal(&slot->guid, guid)) {
|
|
50
|
+
slot->connected = 1;
|
|
51
|
+
slot->instance_id = instance_id;
|
|
52
|
+
return i;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Otherwise the lowest free slot, remembering this GUID for next time. */
|
|
57
|
+
for (int i = 0; i < RGAME_MAX_DEVICE_SLOTS; i++) {
|
|
58
|
+
rgame_device_slot *slot = &table->slots[i];
|
|
59
|
+
if (!slot->connected) {
|
|
60
|
+
slot->connected = 1;
|
|
61
|
+
slot->remembered = 1;
|
|
62
|
+
slot->instance_id = instance_id;
|
|
63
|
+
slot->guid = *guid;
|
|
64
|
+
return i;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* All four players already have a controller. */
|
|
69
|
+
return RGAME_DEVICE_SLOT_NONE;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
int rgame_device_slots_disconnect(rgame_device_slots *table, int instance_id) {
|
|
73
|
+
int index = rgame_device_slots_find(table, instance_id);
|
|
74
|
+
if (index == RGAME_DEVICE_SLOT_NONE) {
|
|
75
|
+
return RGAME_DEVICE_SLOT_NONE;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
rgame_device_slot *slot = &table->slots[index];
|
|
79
|
+
slot->connected = 0;
|
|
80
|
+
slot->instance_id = RGAME_DEVICE_SLOT_NONE;
|
|
81
|
+
/* `remembered` and `guid` deliberately survive: they are what a later
|
|
82
|
+
* reconnect matches against to reclaim this same slot. */
|
|
83
|
+
return index;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
int rgame_device_slots_connected(const rgame_device_slots *table, int slot) {
|
|
87
|
+
return slot_in_range(slot) ? table->slots[slot].connected : 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
int rgame_device_slots_instance_id(const rgame_device_slots *table, int slot) {
|
|
91
|
+
if (!slot_in_range(slot) || !table->slots[slot].connected) {
|
|
92
|
+
return RGAME_DEVICE_SLOT_NONE;
|
|
93
|
+
}
|
|
94
|
+
return table->slots[slot].instance_id;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
int rgame_device_slots_count(const rgame_device_slots *table) {
|
|
98
|
+
int count = 0;
|
|
99
|
+
for (int i = 0; i < RGAME_MAX_DEVICE_SLOTS; i++) {
|
|
100
|
+
count += table->slots[i].connected ? 1 : 0;
|
|
101
|
+
}
|
|
102
|
+
return count;
|
|
103
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#ifndef RGAME_DEVICE_SLOTS_H
|
|
2
|
+
#define RGAME_DEVICE_SLOTS_H
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Player slots for game controllers — pure logic, no SDL, no I/O.
|
|
6
|
+
*
|
|
7
|
+
* This is "layer 1" per CLAUDE.md's abstraction strategy: all the decisions
|
|
8
|
+
* about *which player a controller belongs to* live here as plain arithmetic
|
|
9
|
+
* over plain structs, so they can be exercised by the Check suite without ever
|
|
10
|
+
* plugging in real hardware.
|
|
11
|
+
*
|
|
12
|
+
* The problem it solves: the engine wants a stable "player 1 / player 2 / ..."
|
|
13
|
+
* index, and SDL does not provide one. SDL's joystick *device index* renumbers
|
|
14
|
+
* whenever anything is plugged or unplugged, and its *instance id* is unique
|
|
15
|
+
* per connection — unplug a pad and plug the same one back in and you get a
|
|
16
|
+
* fresh instance id. Either would shuffle players around mid-game, which is
|
|
17
|
+
* exactly what docs/c_engine_feature_specs.md rules out when it asks for
|
|
18
|
+
* indices "stable across a momentary disconnect/reconnect".
|
|
19
|
+
*
|
|
20
|
+
* So this table owns the mapping instead. Each slot remembers the GUID of the
|
|
21
|
+
* last device that filled it, even after that device disconnects. A pad that
|
|
22
|
+
* comes back reclaims the slot it had; a genuinely new pad takes the lowest
|
|
23
|
+
* free one.
|
|
24
|
+
*
|
|
25
|
+
* IMPORTANT — a GUID identifies a device *model*, not an individual unit. Two
|
|
26
|
+
* identical controllers report the same GUID, so "the slot remembering this
|
|
27
|
+
* GUID" can be ambiguous. The rule below resolves it by only ever reclaiming a
|
|
28
|
+
* slot that is currently free, which gives the behaviour a player expects:
|
|
29
|
+
* two matching pads take slots 0 and 1, and whichever one is unplugged gets
|
|
30
|
+
* its own slot back when it returns.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/* Four players, matching the feature spec's "up to 4 simultaneous controllers". */
|
|
34
|
+
#define RGAME_MAX_DEVICE_SLOTS 4
|
|
35
|
+
|
|
36
|
+
/* Matches the size of an SDL_JoystickGUID, without naming SDL here. */
|
|
37
|
+
#define RGAME_DEVICE_GUID_BYTES 16
|
|
38
|
+
|
|
39
|
+
/* Returned by the lookup functions when there is no such slot/device. */
|
|
40
|
+
#define RGAME_DEVICE_SLOT_NONE (-1)
|
|
41
|
+
|
|
42
|
+
typedef struct {
|
|
43
|
+
unsigned char bytes[RGAME_DEVICE_GUID_BYTES];
|
|
44
|
+
} rgame_device_guid;
|
|
45
|
+
|
|
46
|
+
typedef struct {
|
|
47
|
+
int connected; /* is a device plugged into this slot right now */
|
|
48
|
+
int remembered; /* has this slot ever been filled (i.e. is `guid` meaningful) */
|
|
49
|
+
int instance_id; /* caller's handle for the live device; NONE while empty */
|
|
50
|
+
rgame_device_guid guid;
|
|
51
|
+
} rgame_device_slot;
|
|
52
|
+
|
|
53
|
+
typedef struct {
|
|
54
|
+
rgame_device_slot slots[RGAME_MAX_DEVICE_SLOTS];
|
|
55
|
+
} rgame_device_slots;
|
|
56
|
+
|
|
57
|
+
/* Empties every slot and forgets every remembered GUID. */
|
|
58
|
+
void rgame_device_slots_init(rgame_device_slots *table);
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
* Assigns a newly connected device to a slot and returns that slot's index,
|
|
62
|
+
* or RGAME_DEVICE_SLOT_NONE if all slots are already occupied.
|
|
63
|
+
*
|
|
64
|
+
* Order of preference:
|
|
65
|
+
* 1. the device is already connected (same instance_id) — returns its slot
|
|
66
|
+
* unchanged, so a duplicate connect event is harmless;
|
|
67
|
+
* 2. the lowest *free* slot that remembers this GUID — the reconnect case;
|
|
68
|
+
* 3. the lowest free slot.
|
|
69
|
+
*/
|
|
70
|
+
int rgame_device_slots_connect(rgame_device_slots *table,
|
|
71
|
+
const rgame_device_guid *guid,
|
|
72
|
+
int instance_id);
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* Marks the slot holding `instance_id` as free and returns its index, or
|
|
76
|
+
* RGAME_DEVICE_SLOT_NONE if no slot held it. The slot keeps its remembered
|
|
77
|
+
* GUID — that is the whole point, and what lets a reconnect reclaim it.
|
|
78
|
+
*/
|
|
79
|
+
int rgame_device_slots_disconnect(rgame_device_slots *table, int instance_id);
|
|
80
|
+
|
|
81
|
+
/* The slot currently holding `instance_id`, or RGAME_DEVICE_SLOT_NONE. */
|
|
82
|
+
int rgame_device_slots_find(const rgame_device_slots *table, int instance_id);
|
|
83
|
+
|
|
84
|
+
/* Whether a device is plugged into `slot`. Out-of-range slots are simply 0. */
|
|
85
|
+
int rgame_device_slots_connected(const rgame_device_slots *table, int slot);
|
|
86
|
+
|
|
87
|
+
/* The live device in `slot`, or RGAME_DEVICE_SLOT_NONE if it is empty. */
|
|
88
|
+
int rgame_device_slots_instance_id(const rgame_device_slots *table, int slot);
|
|
89
|
+
|
|
90
|
+
/* How many slots currently have a device plugged in. */
|
|
91
|
+
int rgame_device_slots_count(const rgame_device_slots *table);
|
|
92
|
+
|
|
93
|
+
#endif /* RGAME_DEVICE_SLOTS_H */
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#include "input/gamepad.h"
|
|
2
|
+
|
|
3
|
+
#include <SDL2/SDL.h>
|
|
4
|
+
|
|
5
|
+
/* The pure modules size their arrays from these; if SDL ever grows a button or
|
|
6
|
+
* an axis, the snapshot copies below would silently truncate without this. */
|
|
7
|
+
_Static_assert(RGAME_GAMEPAD_BUTTON_COUNT == SDL_CONTROLLER_BUTTON_MAX,
|
|
8
|
+
"RGAME_GAMEPAD_BUTTON_COUNT must match SDL_CONTROLLER_BUTTON_MAX");
|
|
9
|
+
_Static_assert(RGAME_GAMEPAD_AXIS_COUNT == SDL_CONTROLLER_AXIS_MAX,
|
|
10
|
+
"RGAME_GAMEPAD_AXIS_COUNT must match SDL_CONTROLLER_AXIS_MAX");
|
|
11
|
+
|
|
12
|
+
/* Gamepad button ids are the gamepad range plus SDL's own button number, so
|
|
13
|
+
* each named id must line up with the SDL constant it mirrors. */
|
|
14
|
+
_Static_assert(RGAME_PAD_A == RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_A,
|
|
15
|
+
"pad id must match SDL controller button");
|
|
16
|
+
_Static_assert(RGAME_PAD_START == RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_START,
|
|
17
|
+
"pad id must match SDL controller button");
|
|
18
|
+
_Static_assert(RGAME_PAD_DPAD_UP == RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_DPAD_UP,
|
|
19
|
+
"pad id must match SDL controller button");
|
|
20
|
+
_Static_assert(RGAME_PAD_DPAD_RIGHT ==
|
|
21
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
|
|
22
|
+
"pad id must match SDL controller button");
|
|
23
|
+
_Static_assert(RGAME_AXIS_LEFT_X == SDL_CONTROLLER_AXIS_LEFTX, "axis id must match SDL axis");
|
|
24
|
+
_Static_assert(RGAME_AXIS_TRIGGER_RIGHT == SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
|
|
25
|
+
"axis id must match SDL axis");
|
|
26
|
+
|
|
27
|
+
/* Every named pad id must land inside the gamepad range, or a query would be
|
|
28
|
+
* rejected as belonging to a different device class. */
|
|
29
|
+
_Static_assert(RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_MAX - 1 <=
|
|
30
|
+
RGAME_BUTTON_GAMEPAD_LAST,
|
|
31
|
+
"SDL controller buttons must fit the gamepad button-id range");
|
|
32
|
+
|
|
33
|
+
static SDL_GameController *controller_at(const rgame_gamepads *pads, int slot) {
|
|
34
|
+
if (slot < 0 || slot >= RGAME_INPUT_MAX_GAMEPADS) {
|
|
35
|
+
return NULL;
|
|
36
|
+
}
|
|
37
|
+
return (SDL_GameController *)pads->controllers[slot];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
void rgame_gamepads_init(rgame_gamepads *pads) {
|
|
41
|
+
rgame_device_slots_init(&pads->slots);
|
|
42
|
+
for (int i = 0; i < RGAME_INPUT_MAX_GAMEPADS; i++) {
|
|
43
|
+
pads->controllers[i] = NULL;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void rgame_gamepads_shutdown(rgame_gamepads *pads) {
|
|
48
|
+
for (int i = 0; i < RGAME_INPUT_MAX_GAMEPADS; i++) {
|
|
49
|
+
SDL_GameController *pad = controller_at(pads, i);
|
|
50
|
+
if (pad) {
|
|
51
|
+
SDL_GameControllerClose(pad);
|
|
52
|
+
pads->controllers[i] = NULL;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
rgame_device_slots_init(&pads->slots);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
int rgame_gamepads_add(rgame_gamepads *pads, int device_index) {
|
|
59
|
+
/* A joystick is only a *game controller* if SDL has a button mapping for
|
|
60
|
+
* it. Anything else (a flight stick, a dance mat) is ignored rather than
|
|
61
|
+
* seated in a player slot with meaningless buttons. */
|
|
62
|
+
if (!SDL_IsGameController(device_index)) {
|
|
63
|
+
return RGAME_DEVICE_SLOT_NONE;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
SDL_GameController *pad = SDL_GameControllerOpen(device_index);
|
|
67
|
+
if (!pad) {
|
|
68
|
+
SDL_Log("SDL_GameControllerOpen(%d) failed: %s", device_index, SDL_GetError());
|
|
69
|
+
return RGAME_DEVICE_SLOT_NONE;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(pad);
|
|
73
|
+
SDL_JoystickID instance_id = SDL_JoystickInstanceID(joystick);
|
|
74
|
+
SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(joystick);
|
|
75
|
+
|
|
76
|
+
/* SDL_JoystickGUID is a 16-byte blob; the slot table keeps its own copy in
|
|
77
|
+
* an SDL-free struct of the same size (asserted here rather than hoped). */
|
|
78
|
+
_Static_assert(sizeof(sdl_guid.data) == RGAME_DEVICE_GUID_BYTES,
|
|
79
|
+
"SDL joystick GUID must be RGAME_DEVICE_GUID_BYTES long");
|
|
80
|
+
rgame_device_guid guid;
|
|
81
|
+
SDL_memcpy(guid.bytes, sdl_guid.data, RGAME_DEVICE_GUID_BYTES);
|
|
82
|
+
|
|
83
|
+
int slot = rgame_device_slots_connect(&pads->slots, &guid, (int)instance_id);
|
|
84
|
+
if (slot == RGAME_DEVICE_SLOT_NONE) {
|
|
85
|
+
/* All four players already have a pad: don't hold a handle we can't
|
|
86
|
+
* reach, or it would leak until shutdown. */
|
|
87
|
+
SDL_GameControllerClose(pad);
|
|
88
|
+
return RGAME_DEVICE_SLOT_NONE;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
pads->controllers[slot] = pad;
|
|
92
|
+
return slot;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
int rgame_gamepads_remove(rgame_gamepads *pads, int instance_id) {
|
|
96
|
+
int slot = rgame_device_slots_disconnect(&pads->slots, instance_id);
|
|
97
|
+
if (slot == RGAME_DEVICE_SLOT_NONE) {
|
|
98
|
+
return RGAME_DEVICE_SLOT_NONE;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
SDL_GameController *pad = controller_at(pads, slot);
|
|
102
|
+
if (pad) {
|
|
103
|
+
SDL_GameControllerClose(pad);
|
|
104
|
+
pads->controllers[slot] = NULL;
|
|
105
|
+
}
|
|
106
|
+
return slot;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
void rgame_gamepads_snapshot(const rgame_gamepads *pads, rgame_input_state *state) {
|
|
110
|
+
for (int slot = 0; slot < RGAME_INPUT_MAX_GAMEPADS; slot++) {
|
|
111
|
+
SDL_GameController *pad = controller_at(pads, slot);
|
|
112
|
+
if (!pad) {
|
|
113
|
+
/* No pad: clear rather than leave the last reading, so a button
|
|
114
|
+
* held at the moment of unplugging doesn't stay stuck down. */
|
|
115
|
+
rgame_input_state_clear_pad(state, slot);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
unsigned char buttons[RGAME_GAMEPAD_BUTTON_COUNT];
|
|
120
|
+
for (int i = 0; i < RGAME_GAMEPAD_BUTTON_COUNT; i++) {
|
|
121
|
+
buttons[i] = SDL_GameControllerGetButton(pad, (SDL_GameControllerButton)i);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
float axes[RGAME_GAMEPAD_AXIS_COUNT];
|
|
125
|
+
for (int i = 0; i < RGAME_GAMEPAD_AXIS_COUNT; i++) {
|
|
126
|
+
int raw = SDL_GameControllerGetAxis(pad, (SDL_GameControllerAxis)i);
|
|
127
|
+
axes[i] = rgame_input_axis_normalize(raw);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
rgame_input_state_set_pad(state, slot, buttons, axes);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
int rgame_gamepads_connected(const rgame_gamepads *pads, int slot) {
|
|
135
|
+
return controller_at(pads, slot) != NULL;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
int rgame_gamepads_count(const rgame_gamepads *pads) {
|
|
139
|
+
return rgame_device_slots_count(&pads->slots);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const char *rgame_gamepads_name(const rgame_gamepads *pads, int slot) {
|
|
143
|
+
SDL_GameController *pad = controller_at(pads, slot);
|
|
144
|
+
return pad ? SDL_GameControllerName(pad) : NULL;
|
|
145
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#ifndef RGAME_GAMEPAD_H
|
|
2
|
+
#define RGAME_GAMEPAD_H
|
|
3
|
+
|
|
4
|
+
#include "input/device_slots.h"
|
|
5
|
+
#include "input/input.h"
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* The game-controller shim — "layer 3", the thin real SDL part.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately dumb. Every decision about *which player a pad belongs to*
|
|
11
|
+
* lives in device_slots.{c,h}, and every decision about *what a button id
|
|
12
|
+
* means* lives in input.{c,h}; both are pure and unit-tested. What is left
|
|
13
|
+
* here is opening and closing SDL handles and copying values, which is little
|
|
14
|
+
* enough to get wrong that it justifies not unit-testing it directly (see
|
|
15
|
+
* CLAUDE.md's verification tiers).
|
|
16
|
+
*
|
|
17
|
+
* The SDL_GameController pointers are held as void* so this header stays free
|
|
18
|
+
* of SDL types and can be included from app.c without ordering constraints —
|
|
19
|
+
* the same reason include/rgame/core.h names none either.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
typedef struct {
|
|
23
|
+
rgame_device_slots slots;
|
|
24
|
+
/* SDL_GameController* per player slot; NULL when the slot is empty. */
|
|
25
|
+
void *controllers[RGAME_INPUT_MAX_GAMEPADS];
|
|
26
|
+
} rgame_gamepads;
|
|
27
|
+
|
|
28
|
+
void rgame_gamepads_init(rgame_gamepads *pads);
|
|
29
|
+
|
|
30
|
+
/* Closes every open controller. Safe to call twice. */
|
|
31
|
+
void rgame_gamepads_shutdown(rgame_gamepads *pads);
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* Opens the controller at SDL device index `device_index` and seats it in a
|
|
35
|
+
* player slot. Returns the slot, or RGAME_DEVICE_SLOT_NONE if the device isn't
|
|
36
|
+
* a game controller, can't be opened, or every slot is taken.
|
|
37
|
+
*
|
|
38
|
+
* Note `device_index` is SDL's *device index*, which is what
|
|
39
|
+
* SDL_CONTROLLERDEVICEADDED reports — not the instance id.
|
|
40
|
+
*/
|
|
41
|
+
int rgame_gamepads_add(rgame_gamepads *pads, int device_index);
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
* Releases the controller with SDL instance id `instance_id` and frees its
|
|
45
|
+
* slot, returning that slot or RGAME_DEVICE_SLOT_NONE if it wasn't seated.
|
|
46
|
+
*
|
|
47
|
+
* Note SDL_CONTROLLERDEVICEREMOVED reports an *instance id*, not a device
|
|
48
|
+
* index — the two are different numbers in the same `which` field, which is
|
|
49
|
+
* an easy and silent mistake to make.
|
|
50
|
+
*/
|
|
51
|
+
int rgame_gamepads_remove(rgame_gamepads *pads, int instance_id);
|
|
52
|
+
|
|
53
|
+
/* Copies every connected pad's buttons and axes into the frame snapshot, and
|
|
54
|
+
* zeroes the slots that have no controller. */
|
|
55
|
+
void rgame_gamepads_snapshot(const rgame_gamepads *pads, rgame_input_state *state);
|
|
56
|
+
|
|
57
|
+
int rgame_gamepads_connected(const rgame_gamepads *pads, int slot);
|
|
58
|
+
int rgame_gamepads_count(const rgame_gamepads *pads);
|
|
59
|
+
|
|
60
|
+
/* Display name of the pad in `slot`, or NULL when empty. Owned by SDL. */
|
|
61
|
+
const char *rgame_gamepads_name(const rgame_gamepads *pads, int slot);
|
|
62
|
+
|
|
63
|
+
#endif /* RGAME_GAMEPAD_H */
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#include "input/input.h"
|
|
2
|
+
|
|
3
|
+
#include <string.h>
|
|
4
|
+
|
|
5
|
+
int rgame_button_is_keyboard(int button_id) {
|
|
6
|
+
return button_id >= RGAME_BUTTON_KEYBOARD_FIRST &&
|
|
7
|
+
button_id <= RGAME_BUTTON_KEYBOARD_LAST;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
int rgame_button_is_gamepad(int button_id) {
|
|
11
|
+
return button_id >= RGAME_BUTTON_GAMEPAD_FIRST &&
|
|
12
|
+
button_id <= RGAME_BUTTON_GAMEPAD_LAST;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
int rgame_input_device_valid(int device) {
|
|
16
|
+
return device >= 0 && device < RGAME_INPUT_DEVICE_COUNT;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
int rgame_input_device_slot(int device) {
|
|
20
|
+
if (device < RGAME_INPUT_GAMEPAD_FIRST || device >= RGAME_INPUT_DEVICE_COUNT) {
|
|
21
|
+
return -1;
|
|
22
|
+
}
|
|
23
|
+
return device - RGAME_INPUT_GAMEPAD_FIRST;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
float rgame_input_axis_normalize(int raw) {
|
|
27
|
+
float value = (float)raw / 32767.0f;
|
|
28
|
+
if (value < -1.0f) {
|
|
29
|
+
return -1.0f; /* raw == -32768 is one step past the positive limit */
|
|
30
|
+
}
|
|
31
|
+
if (value > 1.0f) {
|
|
32
|
+
return 1.0f;
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
void rgame_input_state_clear(rgame_input_state *state) {
|
|
38
|
+
memset(state->keys, 0, sizeof(state->keys));
|
|
39
|
+
memset(state->pad_buttons, 0, sizeof(state->pad_buttons));
|
|
40
|
+
memset(state->pad_axes, 0, sizeof(state->pad_axes));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
void rgame_input_state_set_pad(rgame_input_state *state, int slot,
|
|
44
|
+
const unsigned char *buttons, const float *axes) {
|
|
45
|
+
if (slot < 0 || slot >= RGAME_INPUT_MAX_GAMEPADS) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
memcpy(state->pad_buttons[slot], buttons, sizeof(state->pad_buttons[slot]));
|
|
49
|
+
memcpy(state->pad_axes[slot], axes, sizeof(state->pad_axes[slot]));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
void rgame_input_state_clear_pad(rgame_input_state *state, int slot) {
|
|
53
|
+
if (slot < 0 || slot >= RGAME_INPUT_MAX_GAMEPADS) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
memset(state->pad_buttons[slot], 0, sizeof(state->pad_buttons[slot]));
|
|
57
|
+
memset(state->pad_axes[slot], 0, sizeof(state->pad_axes[slot]));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
float rgame_input_state_axis(const rgame_input_state *state, int device, int axis_id) {
|
|
61
|
+
int slot = rgame_input_device_slot(device);
|
|
62
|
+
if (slot < 0 || axis_id < 0 || axis_id >= RGAME_GAMEPAD_AXIS_COUNT) {
|
|
63
|
+
return 0.0f;
|
|
64
|
+
}
|
|
65
|
+
return state->pad_axes[slot][axis_id];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
void rgame_input_state_set_keys(rgame_input_state *state, const unsigned char *keys) {
|
|
69
|
+
memcpy(state->keys, keys, sizeof(state->keys));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
int rgame_input_state_down(const rgame_input_state *state, int device, int button_id) {
|
|
73
|
+
if (device == RGAME_INPUT_KEYBOARD) {
|
|
74
|
+
/* A gamepad button asked of the keyboard is simply not held. */
|
|
75
|
+
if (!rgame_button_is_keyboard(button_id)) {
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
/* The keyboard range (0x0FFF) is wider than the scancodes SDL actually
|
|
79
|
+
* defines, so the array bound is checked separately from the range. */
|
|
80
|
+
if (button_id >= RGAME_KEYBOARD_KEY_COUNT) {
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
return state->keys[button_id] ? 1 : 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/*
|
|
87
|
+
* A gamepad answers only for gamepad buttons — and this check has to come
|
|
88
|
+
* *before* the subtraction below, not merely alongside the bound check.
|
|
89
|
+
* For a hugely negative id, `button_id - RGAME_BUTTON_GAMEPAD_FIRST` is
|
|
90
|
+
* signed overflow, i.e. undefined behaviour, before any bound check gets a
|
|
91
|
+
* chance to reject it. (Confirmed by removing this guard: UBSan reports
|
|
92
|
+
* "signed integer overflow: -2147483648 - 4096".)
|
|
93
|
+
*/
|
|
94
|
+
if (!rgame_button_is_gamepad(button_id)) {
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* rgame_input_device_slot rejects the keyboard and any out-of-range device
|
|
99
|
+
* by returning -1, so no separate device-validity check is needed here. */
|
|
100
|
+
int slot = rgame_input_device_slot(device);
|
|
101
|
+
int index = button_id - RGAME_BUTTON_GAMEPAD_FIRST;
|
|
102
|
+
/* The gamepad range (256 ids) is wider than the buttons SDL defines, so
|
|
103
|
+
* the array bound is checked separately from the range — same split as the
|
|
104
|
+
* keyboard above. A slot with no pad simply holds zeroes. */
|
|
105
|
+
if (slot < 0 || index < 0 || index >= RGAME_GAMEPAD_BUTTON_COUNT) {
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
return state->pad_buttons[slot][index] ? 1 : 0;
|
|
109
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#ifndef RGAME_INPUT_H
|
|
2
|
+
#define RGAME_INPUT_H
|
|
3
|
+
|
|
4
|
+
#include "rgame/core.h"
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* The input snapshot — pure logic, no SDL, no I/O.
|
|
8
|
+
*
|
|
9
|
+
* "Layer 1" per CLAUDE.md: this holds the *state* and all the rules for
|
|
10
|
+
* reading it, as plain arrays and integer comparisons, so the Check suite can
|
|
11
|
+
* fill a snapshot by hand and assert on every query without a keyboard, a
|
|
12
|
+
* window, or an event loop existing at all.
|
|
13
|
+
*
|
|
14
|
+
* Only filling the snapshot from real hardware needs SDL, and that is a
|
|
15
|
+
* separate handful of lines in app.c — the thin "layer 3" shim.
|
|
16
|
+
*
|
|
17
|
+
* Why a snapshot rather than reading hardware on demand: the engine runs a
|
|
18
|
+
* fixed timestep, so one rendered frame may run zero or several simulation
|
|
19
|
+
* ticks. If a tick asked the hardware directly, the same held key could read
|
|
20
|
+
* differently between two ticks of the same frame, and how many ticks ran
|
|
21
|
+
* depends on how long the last frame took. Sampling once per frame makes the
|
|
22
|
+
* answer constant across the whole frame.
|
|
23
|
+
*
|
|
24
|
+
* Note what this does *not* fix: an edge — "pressed this tick" — is a
|
|
25
|
+
* comparison against the previous poll, so whatever polls decides what a press
|
|
26
|
+
* is. Polling per rendered frame while simulating per tick loses presses on a
|
|
27
|
+
* fast machine. See RGame::Game, which polls in `update`.
|
|
28
|
+
*
|
|
29
|
+
* The button-id space and device numbering both live in rgame/core.h, because
|
|
30
|
+
* callers outside this directory need to name them.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/* All three == the matching SDL maxima; asserted against SDL in app.c. */
|
|
34
|
+
#define RGAME_KEYBOARD_KEY_COUNT 512
|
|
35
|
+
#define RGAME_GAMEPAD_BUTTON_COUNT 21
|
|
36
|
+
#define RGAME_GAMEPAD_AXIS_COUNT 6
|
|
37
|
+
|
|
38
|
+
typedef struct {
|
|
39
|
+
/* One byte per scancode: non-zero means held. A byte array rather than a
|
|
40
|
+
* bitset because it is what SDL_GetKeyboardState already hands us, so the
|
|
41
|
+
* snapshot is a straight copy with no packing step on the frame path. */
|
|
42
|
+
unsigned char keys[RGAME_KEYBOARD_KEY_COUNT];
|
|
43
|
+
|
|
44
|
+
/* Per player slot, not per SDL device index — the slot table owns that
|
|
45
|
+
* mapping, so a pad that is unplugged and replugged keeps writing here. */
|
|
46
|
+
unsigned char pad_buttons[RGAME_INPUT_MAX_GAMEPADS][RGAME_GAMEPAD_BUTTON_COUNT];
|
|
47
|
+
float pad_axes[RGAME_INPUT_MAX_GAMEPADS][RGAME_GAMEPAD_AXIS_COUNT];
|
|
48
|
+
} rgame_input_state;
|
|
49
|
+
|
|
50
|
+
/* Clears every button to "not held". */
|
|
51
|
+
void rgame_input_state_clear(rgame_input_state *state);
|
|
52
|
+
|
|
53
|
+
/* Replaces the keyboard half of the snapshot. `keys` must have
|
|
54
|
+
* RGAME_KEYBOARD_KEY_COUNT entries, non-zero meaning held — the exact shape
|
|
55
|
+
* SDL_GetKeyboardState returns. */
|
|
56
|
+
void rgame_input_state_set_keys(rgame_input_state *state, const unsigned char *keys);
|
|
57
|
+
|
|
58
|
+
/* Replaces one slot's gamepad state. `buttons` and `axes` must have
|
|
59
|
+
* RGAME_GAMEPAD_BUTTON_COUNT / RGAME_GAMEPAD_AXIS_COUNT entries. */
|
|
60
|
+
void rgame_input_state_set_pad(rgame_input_state *state, int slot,
|
|
61
|
+
const unsigned char *buttons, const float *axes);
|
|
62
|
+
|
|
63
|
+
/* Zeroes one slot — what a disconnect must do, so a pad unplugged mid-press
|
|
64
|
+
* does not leave that button stuck held forever. */
|
|
65
|
+
void rgame_input_state_clear_pad(rgame_input_state *state, int slot);
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* Whether `button_id` is held on `device`. Returns 0 rather than raising for
|
|
69
|
+
* every out-of-range case: an unknown device, a button outside any range, or a
|
|
70
|
+
* button belonging to a different device class than the one asked about.
|
|
71
|
+
*/
|
|
72
|
+
int rgame_input_state_down(const rgame_input_state *state, int device, int button_id);
|
|
73
|
+
|
|
74
|
+
/* Current value of `axis_id` on `device`; 0.0 for anything out of range. */
|
|
75
|
+
float rgame_input_state_axis(const rgame_input_state *state, int device, int axis_id);
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
* Converts a raw SDL axis reading (-32768..32767) to -1.0..1.0.
|
|
79
|
+
*
|
|
80
|
+
* The range is asymmetric — there is one more negative value than positive —
|
|
81
|
+
* so dividing by 32767 would make full-left read slightly past -1.0. The
|
|
82
|
+
* result is clamped instead, which costs nothing and keeps callers from having
|
|
83
|
+
* to special-case a single value.
|
|
84
|
+
*/
|
|
85
|
+
float rgame_input_axis_normalize(int raw);
|
|
86
|
+
|
|
87
|
+
/* The player slot a gamepad device id refers to, or -1 for the keyboard or an
|
|
88
|
+
* invalid device. */
|
|
89
|
+
int rgame_input_device_slot(int device);
|
|
90
|
+
|
|
91
|
+
/* Range predicates for the flat button-id space. Exposed because they are the
|
|
92
|
+
* part worth testing directly, and because app.c uses them too. */
|
|
93
|
+
int rgame_button_is_keyboard(int button_id);
|
|
94
|
+
int rgame_button_is_gamepad(int button_id);
|
|
95
|
+
|
|
96
|
+
/* Whether `device` names the keyboard or a real gamepad slot. */
|
|
97
|
+
int rgame_input_device_valid(int device);
|
|
98
|
+
|
|
99
|
+
#endif /* RGAME_INPUT_H */
|