input: switch autofire to tracking frames per key (#1094)

This commit is contained in:
Tony Toon
2026-02-02 00:42:10 -06:00
committed by GitHub
parent 4d1a9fc2d3
commit 5ea051dcea
3 changed files with 64 additions and 40 deletions

View File

@@ -13,7 +13,8 @@
We take the desired autofire rate in hertz and convert that to a bitmask
0 == button released
1 == button pressed
We advance through a single bit of this mask every frame on the assumption that
Input tracks how many frames the button has been held for.
We use that frame count modulo the autofire cycle length to index into the bitmask.
most games read their inputs every vsync (or is it vrefresh? once per frame.)
We display autofire rates to the user as if the game is running at 60hz, but internally
@@ -32,8 +33,6 @@ struct AutofireData {
char name[AF_NAME_LEN]; // display name
uint64_t cycle_mask; // bitmask representing the autofire cycle
int cycle_length; // length of the cycle in frames
int bit; // current bit in the cycle
int frame_count; // current frame in the cycle
};
// per-player autofire table; index 0 means disabled.
@@ -103,21 +102,21 @@ void inc_autofire_code(int player, uint32_t code, uint32_t mask) {
}
// advance all autofire patterns (run once per frame)
void autofire_tick() {
for (int i = 1; i < num_af_rates; i++)
{
autofiredata[i].bit = (autofiredata[i].cycle_mask >> autofiredata[i].frame_count) & 1u;
if (++(autofiredata[i].frame_count) >= autofiredata[i].cycle_length)
autofiredata[i].frame_count = 0;
}
}
//void autofire_tick() {
// for (int i = 1; i < num_af_rates; i++)
// {
//autofiredata[i].bit = (autofiredata[i].cycle_mask >> autofiredata[i].frame_count) & 1u;
//if (++(autofiredata[i].frame_count) >= autofiredata[i].cycle_length)
// autofiredata[i].frame_count = 0;
// }
//}
// returns whether the buttons for this code should be held or released this frame.
// (updated every time we call autofire_tick)
bool get_autofire_bit(int player, uint32_t code) {
bool get_autofire_bit(int player, uint32_t code, uint32_t frame_count) {
int rate_idx = get_autofire_code_idx(player, code);
if (rate_idx > 0) {
return autofiredata[rate_idx].bit;
return (autofiredata[rate_idx].cycle_mask >> frame_count % autofiredata[rate_idx].cycle_length) & 1u;
}
return false;
}
@@ -151,8 +150,8 @@ bool is_autofire_enabled(int player, uint32_t code) {
// some arcade shooters have pretty odd optimal autofire patterns to manage rank
// let's hide them here for my shmup buddies
static const struct AutofireData autofire_patterns[] = {
{ "GUNFRONTIER", 0b111100000ULL, 9, 0, 0 },
{ "GAREGGA", 0b1110000ULL, 7, 0, 0 },
{ "GUNFRONTIER", 0b111100000ULL, 9 },
{ "GAREGGA", 0b1110000ULL, 7 },
};
// helper for formatting binary literal patterns.
@@ -182,13 +181,11 @@ static void init_autofire_entry(struct AutofireData *data, uint64_t mask, int le
data->cycle_mask = mask;
if (data->cycle_length > 64) data->cycle_length = 64;
if (data->cycle_length < 1) data->cycle_length = 1;
data->frame_count = 0;
data->bit = (data->cycle_mask & 1ULL) ? 1 : 0;
}
static inline struct AutofireData mask_from_hertz(double hz_target)
{
struct AutofireData p = {{0}, 0, 0, 0, 0};
struct AutofireData p = {{0}, 0, 0};
if (hz_target <= 0.0) return p;
if (hz_target > 30.0) hz_target = 30.0;

View File

@@ -7,8 +7,8 @@ const char *get_autofire_rate_hz(int player, uint32_t code);
bool is_autofire_enabled(int player, uint32_t code);
void clear_autofire(int player);
void inc_autofire_code(int player, uint32_t code, uint32_t mask);
void autofire_tick();
//void autofire_tick();
bool parse_autofire_cfg();
bool get_autofire_bit(int player, uint32_t code);
bool get_autofire_bit(int player, uint32_t code, uint32_t frame_count);
#endif

View File

@@ -1902,16 +1902,38 @@ static bool handle_autofire_toggle(int num, uint32_t mask, uint32_t code, char p
static uint32_t osdbtn = 0;
// tracking of key states for handling inputs
// we OR all of them together at the end of the input path to determine which
// of the 32 virtual buttons should be pressed or released for a given player.
#define MAX_KEY_STATES 128
// we track a full mask per key because i wasn't sure how to get the full key->buttons mapping
// after it's already been set.
struct KeyStates {
uint32_t key[256];
uint32_t mask[256];
uint32_t key[MAX_KEY_STATES];
uint32_t mask[MAX_KEY_STATES];
uint32_t frames_held[MAX_KEY_STATES];
int count;
};
#define MAX_KEY_STATES 64
KeyStates key_states[NUMPLAYERS] = {};
// returns the bitmask representing all button states for a given ev->code (key)
// returns 0 if the key is not found or if key has no buttons pressed.
static uint32_t get_key_state(int player, uint32_t key)
{
for (int i = 0; i < key_states[player].count; i++)
{
if (key_states[player].key[i] == key)
{
return key_states[player].mask[i];
}
}
return 0u;
}
// updates the bitmask representing all button states for a given ev->code (key)
static void set_key_state(int player, uint32_t key, bool press, uint32_t mask)
{
for (int i = 0; i < key_states[player].count; i++)
@@ -1926,17 +1948,6 @@ static void set_key_state(int player, uint32_t key, bool press, uint32_t mask)
else
{
key_states[player].mask[i] &= ~mask;
// remove an entry if no bits are set.
if (!key_states[player].mask[i]) {
int last = key_states[player].count - 1;
if (i != last) {
key_states[player].key[i] = key_states[player].key[last];
key_states[player].mask[i] = key_states[player].mask[last];
}
key_states[player].key[last] = 0;
key_states[player].mask[last] = 0u;
key_states[player].count--;
}
return;
}
}
@@ -1958,8 +1969,9 @@ uint32_t build_joy_mask(int player)
uint32_t mask = 0u;
for (int i = 0; i < key_states[player].count; i++)
{
uint32_t key = key_states[player].key[i];
if (!is_autofire_enabled(player, key_states[player].key[i]))
mask |= key_states[player].mask[i];
mask |= get_key_state(player, key);
}
return mask;
}
@@ -1969,9 +1981,10 @@ uint32_t build_autofire_mask(int player)
uint32_t mask = 0u;
for (int i = 0; i < key_states[player].count; i++)
{
if (is_autofire_enabled(player, key_states[player].key[i]))
if (get_autofire_bit(player, key_states[player].key[i]))
mask |= key_states[player].mask[i];
uint32_t key = key_states[player].key[i];
uint32_t frames_held = key_states[player].frames_held[i];
if (is_autofire_enabled(player, key) && get_autofire_bit(player, key, frames_held))
mask |= get_key_state(player, key);
}
return mask;
}
@@ -5843,6 +5856,19 @@ int input_test(int getchar)
return 0;
}
void key_update_frames_held()
{
for (int i = 0; i < NUMPLAYERS; i++) {
for (int k = 0; k < key_states[i].count; k++) {
if (key_states[i].mask[k] != 0) {
key_states[i].frames_held[k]++;
} else {
key_states[i].frames_held[k]= 0;
}
}
}
}
int input_poll(int getchar)
{
PROFILE_FUNCTION();
@@ -5853,7 +5879,8 @@ int input_poll(int getchar)
// FRAME_TICK compares against frame_timer's counter (updated elsewhere) and fires once per frame.
static uint32_t last_frame_count = 0;
if (FRAME_TICK(last_frame_count)) {
autofire_tick(); // advance all autofire patterns by 1
key_update_frames_held();
//autofire_tick(); // advance all autofire patterns by 1
}
int ret = input_test(getchar);