|
|
|
@ -17,6 +17,8 @@
|
|
|
|
#include <math.h>
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
|
|
|
|
// ── Global state ──────────────────────────────────────────────────────────────
|
|
|
|
// ── Global state ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
int g_sleep_mode = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// Game
|
|
|
|
// Game
|
|
|
|
int g_life;
|
|
|
|
int g_life;
|
|
|
|
int g_cmdr_damage[MAX_OPPONENTS];
|
|
|
|
int g_cmdr_damage[MAX_OPPONENTS];
|
|
|
|
@ -30,6 +32,7 @@ int g_active_counter;
|
|
|
|
int g_active_player;
|
|
|
|
int g_active_player;
|
|
|
|
int g_active_setting;
|
|
|
|
int g_active_setting;
|
|
|
|
int g_name_cursor;
|
|
|
|
int g_name_cursor;
|
|
|
|
|
|
|
|
int g_life_select;
|
|
|
|
|
|
|
|
|
|
|
|
// Settings
|
|
|
|
// Settings
|
|
|
|
int g_brightness_pct = 10;
|
|
|
|
int g_brightness_pct = 10;
|
|
|
|
@ -40,6 +43,9 @@ int g_ble_enabled = 0;
|
|
|
|
uint8_t g_led_max = 26;
|
|
|
|
uint8_t g_led_max = 26;
|
|
|
|
int g_game_id_cursor = 0;
|
|
|
|
int g_game_id_cursor = 0;
|
|
|
|
uint8_t g_game_id[2] = {BLE_GAME_ID_0, BLE_GAME_ID_1};
|
|
|
|
uint8_t g_game_id[2] = {BLE_GAME_ID_0, BLE_GAME_ID_1};
|
|
|
|
|
|
|
|
int g_menu_hold_ms = 500;
|
|
|
|
|
|
|
|
int g_lr_hold_ms = 500;
|
|
|
|
|
|
|
|
int g_display_flip = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// Peers
|
|
|
|
// Peers
|
|
|
|
ble_peer_t g_peers[MAX_BLE_PEERS];
|
|
|
|
ble_peer_t g_peers[MAX_BLE_PEERS];
|
|
|
|
@ -47,6 +53,11 @@ ble_peer_t g_peers[MAX_BLE_PEERS];
|
|
|
|
// Timing
|
|
|
|
// Timing
|
|
|
|
uint32_t g_tick;
|
|
|
|
uint32_t g_tick;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Life delta overlay
|
|
|
|
|
|
|
|
int g_life_delta = 0;
|
|
|
|
|
|
|
|
int g_life_delta_tick = 0;
|
|
|
|
|
|
|
|
int g_delta_timeout_ms = 1000;
|
|
|
|
|
|
|
|
|
|
|
|
// ── Serial command task ───────────────────────────────────────────────────────
|
|
|
|
// ── Serial command task ───────────────────────────────────────────────────────
|
|
|
|
static void serial_print_state(void)
|
|
|
|
static void serial_print_state(void)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
@ -140,13 +151,16 @@ void app_main(void)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
g_life = start_life_opts[g_start_life_index];
|
|
|
|
g_life = start_life_opts[g_start_life_index];
|
|
|
|
settings_load();
|
|
|
|
settings_load();
|
|
|
|
|
|
|
|
check_elimination();
|
|
|
|
g_led_max = (uint8_t)(255 * g_brightness_pct / 100);
|
|
|
|
g_led_max = (uint8_t)(255 * g_brightness_pct / 100);
|
|
|
|
|
|
|
|
|
|
|
|
oled_init();
|
|
|
|
oled_init();
|
|
|
|
|
|
|
|
oled_set_flip(g_display_flip);
|
|
|
|
oled_clear();
|
|
|
|
oled_clear();
|
|
|
|
|
|
|
|
|
|
|
|
gpio_config_t gpio_cfg = {
|
|
|
|
gpio_config_t gpio_cfg = {
|
|
|
|
.pin_bit_mask = (1ULL<<BTN_RIGHT_GPIO)|(1ULL<<BTN_LEFT_GPIO)|(1ULL<<BTN_MID_GPIO),
|
|
|
|
.pin_bit_mask = (1ULL<<BTN_FORWARD_GPIO)|(1ULL<<BTN_LEFT_GPIO)
|
|
|
|
|
|
|
|
|(1ULL<<BTN_RIGHT_GPIO)|(1ULL<<BTN_BACK_GPIO),
|
|
|
|
.mode=GPIO_MODE_INPUT, .pull_up_en=GPIO_PULLUP_ENABLE,
|
|
|
|
.mode=GPIO_MODE_INPUT, .pull_up_en=GPIO_PULLUP_ENABLE,
|
|
|
|
.pull_down_en=GPIO_PULLDOWN_DISABLE, .intr_type=GPIO_INTR_DISABLE,
|
|
|
|
.pull_down_en=GPIO_PULLDOWN_DISABLE, .intr_type=GPIO_INTR_DISABLE,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
@ -160,39 +174,87 @@ void app_main(void)
|
|
|
|
xTaskCreate(serial_cmd_task, "serial_cmd", 4096, NULL, 3, NULL);
|
|
|
|
xTaskCreate(serial_cmd_task, "serial_cmd", 4096, NULL, 3, NULL);
|
|
|
|
|
|
|
|
|
|
|
|
oled_draw_header();
|
|
|
|
oled_draw_header();
|
|
|
|
{ char ibuf[12]; snprintf(ibuf, sizeof(ibuf), "%d", g_life); oled_draw_centered(ibuf); }
|
|
|
|
oled_draw_life();
|
|
|
|
led_update_for_count(g_life, 255);
|
|
|
|
led_update_for_count(g_life, 255);
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
#ifdef DEBUG
|
|
|
|
serial_print_state();
|
|
|
|
serial_print_state();
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
int prev_right=1, prev_left=1, prev_mid=1;
|
|
|
|
int prev_fwd=1, prev_right=1, prev_left=1, prev_back=1;
|
|
|
|
int hold_right=0, hold_left=0, hold_mid=0, mid_fired_hold=0;
|
|
|
|
int hold_fwd=0, hold_back=0, hold_right=0, hold_left=0;
|
|
|
|
|
|
|
|
int combo_tick=0, combo_fired_hold=0;
|
|
|
|
|
|
|
|
int fwd_in_combo=0, back_in_combo=0;
|
|
|
|
|
|
|
|
int fwd_menu_fired=0, back_menu_fired=0;
|
|
|
|
// Require 2 consecutive released ticks before arming hold; prevents accidental
|
|
|
|
// Require 2 consecutive released ticks before arming hold; prevents accidental
|
|
|
|
// hold fires when rapid taps have sub-10ms releases that the sampler misses.
|
|
|
|
// hold fires when rapid taps have sub-10ms releases that the sampler misses.
|
|
|
|
int release_r=2, release_l=2;
|
|
|
|
int release_r=2, release_l=2;
|
|
|
|
int hold_armed_r=1, hold_armed_l=1;
|
|
|
|
int hold_armed_r=1, hold_armed_l=1;
|
|
|
|
int players_tick=0;
|
|
|
|
int players_tick=0;
|
|
|
|
int settings_dirty=0, settings_save_tick=0;
|
|
|
|
int settings_dirty=0, settings_save_tick=0;
|
|
|
|
|
|
|
|
int sleep_tick=0, sleep_fired=0, ble_pre_sleep=0;
|
|
|
|
#define MARK_DIRTY() do { settings_dirty = 1; settings_save_tick = 0; } while (0)
|
|
|
|
#define MARK_DIRTY() do { settings_dirty = 1; settings_save_tick = 0; } while (0)
|
|
|
|
float breath_phase=0.0f;
|
|
|
|
float breath_phase=0.0f;
|
|
|
|
char nbuf[12];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char opponent_labels[MAX_OPPONENTS][PLAYER_NAME_LEN+1];
|
|
|
|
char opponent_labels[MAX_OPPONENTS][PLAYER_NAME_LEN+1];
|
|
|
|
const char *opponent_label_ptrs[MAX_OPPONENTS];
|
|
|
|
const char *opponent_label_ptrs[MAX_OPPONENTS];
|
|
|
|
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
while (1) {
|
|
|
|
g_tick++;
|
|
|
|
g_tick++;
|
|
|
|
|
|
|
|
int menu_ticks = g_menu_hold_ms / 10;
|
|
|
|
|
|
|
|
int lr_ticks = g_lr_hold_ms / 10;
|
|
|
|
|
|
|
|
int fwd = gpio_get_level(BTN_FORWARD_GPIO);
|
|
|
|
int right = gpio_get_level(BTN_RIGHT_GPIO);
|
|
|
|
int right = gpio_get_level(BTN_RIGHT_GPIO);
|
|
|
|
int left = gpio_get_level(BTN_LEFT_GPIO);
|
|
|
|
int left = gpio_get_level(BTN_LEFT_GPIO);
|
|
|
|
int mid = gpio_get_level(BTN_MID_GPIO);
|
|
|
|
int back = gpio_get_level(BTN_BACK_GPIO);
|
|
|
|
int changed = 0;
|
|
|
|
int changed = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// ── Middle button ────────────────────────────────────────────────────
|
|
|
|
// ── Combo: all 4 buttons → sleep toggle ──────────────────────────────
|
|
|
|
if (mid == 0) {
|
|
|
|
int all4 = (fwd==0 && back==0 && left==0 && right==0);
|
|
|
|
hold_mid++;
|
|
|
|
if (all4) {
|
|
|
|
if (!mid_fired_hold && hold_mid == MID_HOLD_SETTINGS) {
|
|
|
|
sleep_tick++;
|
|
|
|
|
|
|
|
if (!sleep_fired && sleep_tick == COMBO_HOLD_SLEEP) {
|
|
|
|
|
|
|
|
sleep_fired = 1;
|
|
|
|
|
|
|
|
hold_fwd = hold_back = hold_left = hold_right = 0;
|
|
|
|
|
|
|
|
combo_tick = 0; combo_fired_hold = 0;
|
|
|
|
|
|
|
|
if (!g_sleep_mode) {
|
|
|
|
|
|
|
|
g_sleep_mode = 1;
|
|
|
|
|
|
|
|
ble_pre_sleep = g_ble_enabled;
|
|
|
|
|
|
|
|
oled_draw_sleep();
|
|
|
|
|
|
|
|
oled_set_contrast(SLEEP_CONTRAST);
|
|
|
|
|
|
|
|
led_off();
|
|
|
|
|
|
|
|
if (g_ble_enabled) { g_ble_enabled = 0; ble_adv_update(); }
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
g_sleep_mode = 0;
|
|
|
|
|
|
|
|
oled_set_contrast(0xCF);
|
|
|
|
|
|
|
|
g_ble_enabled = ble_pre_sleep;
|
|
|
|
|
|
|
|
if (g_ble_enabled) ble_adv_update();
|
|
|
|
|
|
|
|
oled_draw_header();
|
|
|
|
|
|
|
|
breath_phase = 0.0f;
|
|
|
|
|
|
|
|
led_update_for_count(g_life, 255);
|
|
|
|
|
|
|
|
changed = 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
sleep_tick = 0; sleep_fired = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ── Sleep mode: skip all further processing ───────────────────────────
|
|
|
|
|
|
|
|
if (g_sleep_mode) {
|
|
|
|
|
|
|
|
prev_fwd = fwd; prev_right = right; prev_left = left; prev_back = back;
|
|
|
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ── Combo: FORWARD + BACK simultaneously (settings toggle) ──────────
|
|
|
|
|
|
|
|
int both = (fwd == 0 && back == 0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (both) {
|
|
|
|
|
|
|
|
hold_fwd = hold_back = 0;
|
|
|
|
|
|
|
|
fwd_in_combo = back_in_combo = 1;
|
|
|
|
|
|
|
|
fwd_menu_fired = back_menu_fired = 0;
|
|
|
|
|
|
|
|
combo_tick++;
|
|
|
|
|
|
|
|
if (!combo_fired_hold && combo_tick == COMBO_HOLD_SETTINGS) {
|
|
|
|
if (g_active_menu == MENU_SETTINGS) {
|
|
|
|
if (g_active_menu == MENU_SETTINGS) {
|
|
|
|
g_active_menu = 0;
|
|
|
|
g_active_menu = 0;
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
@ -201,23 +263,32 @@ void app_main(void)
|
|
|
|
g_name_cursor = 0;
|
|
|
|
g_name_cursor = 0;
|
|
|
|
g_game_id_cursor = 0;
|
|
|
|
g_game_id_cursor = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mid_fired_hold = 1;
|
|
|
|
combo_fired_hold = 1;
|
|
|
|
hold_right = hold_left = players_tick = 0;
|
|
|
|
hold_right = hold_left = players_tick = 0;
|
|
|
|
oled_draw_header();
|
|
|
|
oled_draw_header();
|
|
|
|
changed = 1;
|
|
|
|
changed = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
if (prev_mid == 0 && !mid_fired_hold) {
|
|
|
|
combo_tick = 0;
|
|
|
|
if (hold_mid >= MID_HOLD_MENU) {
|
|
|
|
combo_fired_hold = 0;
|
|
|
|
if (g_active_menu == MENU_SETTINGS) {
|
|
|
|
|
|
|
|
g_active_menu = 0;
|
|
|
|
// ── FORWARD: long press = next menu (+ repeat), short press = sub-item fwd ──
|
|
|
|
} else {
|
|
|
|
if (fwd == 0) {
|
|
|
|
|
|
|
|
hold_fwd++;
|
|
|
|
|
|
|
|
if (g_active_menu != MENU_SETTINGS && !fwd_in_combo) {
|
|
|
|
|
|
|
|
if (hold_fwd == menu_ticks ||
|
|
|
|
|
|
|
|
(hold_fwd > menu_ticks && (hold_fwd - menu_ticks) % menu_ticks == 0)) {
|
|
|
|
g_active_menu = (g_active_menu + 1) % (NUM_MENUS - 1);
|
|
|
|
g_active_menu = (g_active_menu + 1) % (NUM_MENUS - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
hold_right = hold_left = players_tick = 0;
|
|
|
|
hold_right = hold_left = players_tick = 0;
|
|
|
|
oled_draw_header();
|
|
|
|
oled_draw_header();
|
|
|
|
changed = 1;
|
|
|
|
changed = 1;
|
|
|
|
} else if (g_active_menu == MENU_SETTINGS) {
|
|
|
|
fwd_menu_fired = 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (prev_fwd == 0 && !fwd_in_combo && !fwd_menu_fired) {
|
|
|
|
|
|
|
|
if (hold_fwd < menu_ticks) {
|
|
|
|
|
|
|
|
if (g_active_menu == MENU_SETTINGS) {
|
|
|
|
if (g_active_setting == SET_PLAYER_NAME) {
|
|
|
|
if (g_active_setting == SET_PLAYER_NAME) {
|
|
|
|
g_name_cursor++;
|
|
|
|
g_name_cursor++;
|
|
|
|
if (g_name_cursor >= PLAYER_NAME_LEN) {
|
|
|
|
if (g_name_cursor >= PLAYER_NAME_LEN) {
|
|
|
|
@ -250,40 +321,158 @@ void app_main(void)
|
|
|
|
} else if (g_active_menu == MENU_COUNTERS) {
|
|
|
|
} else if (g_active_menu == MENU_COUNTERS) {
|
|
|
|
g_active_counter = (g_active_counter + 1) % NUM_COUNTERS;
|
|
|
|
g_active_counter = (g_active_counter + 1) % NUM_COUNTERS;
|
|
|
|
changed = 1;
|
|
|
|
changed = 1;
|
|
|
|
|
|
|
|
} else if (g_active_menu == MENU_LIFE) {
|
|
|
|
|
|
|
|
int nc = g_ble_enabled ? 0 : g_num_opponents;
|
|
|
|
|
|
|
|
if (g_ble_enabled)
|
|
|
|
|
|
|
|
for (int i = 0; i < MAX_BLE_PEERS; i++)
|
|
|
|
|
|
|
|
if (g_peers[i].active) nc++;
|
|
|
|
|
|
|
|
g_life_select = (g_life_select + 1) % (3 + nc);
|
|
|
|
|
|
|
|
changed = 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
hold_fwd = 0;
|
|
|
|
|
|
|
|
fwd_in_combo = 0;
|
|
|
|
|
|
|
|
fwd_menu_fired = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ── BACK: long press = prev menu (+ repeat), short press = sub-item back ──
|
|
|
|
|
|
|
|
if (back == 0) {
|
|
|
|
|
|
|
|
hold_back++;
|
|
|
|
|
|
|
|
if (g_active_menu != MENU_SETTINGS && !back_in_combo) {
|
|
|
|
|
|
|
|
if (hold_back == menu_ticks ||
|
|
|
|
|
|
|
|
(hold_back > menu_ticks && (hold_back - menu_ticks) % menu_ticks == 0)) {
|
|
|
|
|
|
|
|
g_active_menu = (g_active_menu - 1 + (NUM_MENUS - 1)) % (NUM_MENUS - 1);
|
|
|
|
|
|
|
|
hold_right = hold_left = players_tick = 0;
|
|
|
|
|
|
|
|
oled_draw_header();
|
|
|
|
|
|
|
|
changed = 1;
|
|
|
|
|
|
|
|
back_menu_fired = 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (prev_back == 0 && !back_in_combo && !back_menu_fired) {
|
|
|
|
|
|
|
|
if (hold_back < menu_ticks) {
|
|
|
|
|
|
|
|
if (g_active_menu == MENU_SETTINGS) {
|
|
|
|
|
|
|
|
if (g_active_setting == SET_PLAYER_NAME) {
|
|
|
|
|
|
|
|
if (g_name_cursor > 0) {
|
|
|
|
|
|
|
|
g_name_cursor--;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
g_active_setting = (g_active_setting - 1 + NUM_SETTINGS) % NUM_SETTINGS;
|
|
|
|
|
|
|
|
g_name_cursor = (g_active_setting == SET_PLAYER_NAME) ? PLAYER_NAME_LEN - 1 : 0;
|
|
|
|
|
|
|
|
g_game_id_cursor = (g_active_setting == SET_GAME_ID) ? GAME_ID_DIGITS - 1 : 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (g_active_setting == SET_GAME_ID) {
|
|
|
|
|
|
|
|
if (g_game_id_cursor > 0) {
|
|
|
|
|
|
|
|
g_game_id_cursor--;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
g_active_setting = (g_active_setting - 1 + NUM_SETTINGS) % NUM_SETTINGS;
|
|
|
|
|
|
|
|
g_name_cursor = (g_active_setting == SET_PLAYER_NAME) ? PLAYER_NAME_LEN - 1 : 0;
|
|
|
|
|
|
|
|
g_game_id_cursor = (g_active_setting == SET_GAME_ID) ? GAME_ID_DIGITS - 1 : 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
g_active_setting = (g_active_setting - 1 + NUM_SETTINGS) % NUM_SETTINGS;
|
|
|
|
|
|
|
|
g_name_cursor = (g_active_setting == SET_PLAYER_NAME) ? PLAYER_NAME_LEN - 1 : 0;
|
|
|
|
|
|
|
|
g_game_id_cursor = (g_active_setting == SET_GAME_ID) ? GAME_ID_DIGITS - 1 : 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
changed = 1;
|
|
|
|
|
|
|
|
} else if (g_active_menu == MENU_CMDR) {
|
|
|
|
|
|
|
|
if (g_ble_enabled) {
|
|
|
|
|
|
|
|
int total = MAX_BLE_PEERS + 1;
|
|
|
|
|
|
|
|
int next = (g_active_player - 1 + total) % total;
|
|
|
|
|
|
|
|
while (next != g_active_player && next != 0 && !g_peers[next - 1].active)
|
|
|
|
|
|
|
|
next = (next - 1 + total) % total;
|
|
|
|
|
|
|
|
g_active_player = next;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
g_active_opponent = (g_active_opponent - 1 + g_num_opponents) % g_num_opponents;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
changed = 1;
|
|
|
|
|
|
|
|
} else if (g_active_menu == MENU_COUNTERS) {
|
|
|
|
|
|
|
|
g_active_counter = (g_active_counter - 1 + NUM_COUNTERS) % NUM_COUNTERS;
|
|
|
|
|
|
|
|
changed = 1;
|
|
|
|
|
|
|
|
} else if (g_active_menu == MENU_LIFE) {
|
|
|
|
|
|
|
|
int nc = g_ble_enabled ? 0 : g_num_opponents;
|
|
|
|
|
|
|
|
if (g_ble_enabled)
|
|
|
|
|
|
|
|
for (int i = 0; i < MAX_BLE_PEERS; i++)
|
|
|
|
|
|
|
|
if (g_peers[i].active) nc++;
|
|
|
|
|
|
|
|
int total = 3 + nc;
|
|
|
|
|
|
|
|
g_life_select = (g_life_select - 1 + total) % total;
|
|
|
|
|
|
|
|
changed = 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
hold_mid = 0; mid_fired_hold = 0;
|
|
|
|
hold_back = 0;
|
|
|
|
|
|
|
|
back_in_combo = 0;
|
|
|
|
|
|
|
|
back_menu_fired = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Left / Right ─────────────────────────────────────────────────────
|
|
|
|
// ── delta[0]: value change (right / left) ────────────────────────────
|
|
|
|
int delta_r=0, delta_l=0;
|
|
|
|
int delta_r=0, delta_l=0;
|
|
|
|
|
|
|
|
if (right==0 && left==0) {
|
|
|
|
|
|
|
|
// both held: reset counters so they stay in sync and neither fires
|
|
|
|
|
|
|
|
hold_right=0; hold_left=0; release_r=0; release_l=0;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
if (right==0) {
|
|
|
|
if (right==0) {
|
|
|
|
if (prev_right==1) {
|
|
|
|
if (prev_right==1) {
|
|
|
|
delta_r=1; hold_right=0;
|
|
|
|
delta_r=1; hold_right=0;
|
|
|
|
hold_armed_r = (release_r >= 2);
|
|
|
|
hold_armed_r = (release_r >= 2); release_r=0;
|
|
|
|
release_r = 0;
|
|
|
|
} else if (hold_armed_r && ++hold_right>=lr_ticks
|
|
|
|
} else if (hold_armed_r && ++hold_right>=HOLD_DELAY && (hold_right-HOLD_DELAY)%HOLD_REPEAT==0) {
|
|
|
|
&& (hold_right-lr_ticks)%HOLD_REPEAT==0) {
|
|
|
|
delta_r=1;
|
|
|
|
delta_r=1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else { hold_right=0; if (release_r<2) release_r++; }
|
|
|
|
} else { hold_right=0; if (release_r<2) release_r++; }
|
|
|
|
if (left==0) {
|
|
|
|
if (left==0) {
|
|
|
|
if (prev_left==1) {
|
|
|
|
if (prev_left==1) {
|
|
|
|
delta_l=1; hold_left=0;
|
|
|
|
delta_l=1; hold_left=0;
|
|
|
|
hold_armed_l = (release_l >= 2);
|
|
|
|
hold_armed_l = (release_l >= 2); release_l=0;
|
|
|
|
release_l = 0;
|
|
|
|
} else if (hold_armed_l && ++hold_left>=lr_ticks
|
|
|
|
} else if (hold_armed_l && ++hold_left>=HOLD_DELAY && (hold_left-HOLD_DELAY)%HOLD_REPEAT==0) {
|
|
|
|
&& (hold_left-lr_ticks)%HOLD_REPEAT==0) {
|
|
|
|
delta_l=1;
|
|
|
|
delta_l=1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else { hold_left=0; if (release_l<2) release_l++; }
|
|
|
|
} else { hold_left=0; if (release_l<2) release_l++; }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (delta_r || delta_l) {
|
|
|
|
if (delta_r || delta_l) {
|
|
|
|
int d = delta_r - delta_l;
|
|
|
|
int d = delta_r - delta_l;
|
|
|
|
if (g_active_menu == MENU_LIFE) {
|
|
|
|
if (g_active_menu == MENU_LIFE) {
|
|
|
|
g_life += d; changed = 1; MARK_DIRTY(); check_elimination();
|
|
|
|
if (g_life_select == 0) {
|
|
|
|
|
|
|
|
g_life += d;
|
|
|
|
|
|
|
|
g_life_delta += d;
|
|
|
|
|
|
|
|
g_life_delta_tick = 0;
|
|
|
|
|
|
|
|
check_elimination();
|
|
|
|
|
|
|
|
} else if (g_life_select == 1) {
|
|
|
|
|
|
|
|
g_counters[COUNTER_STORM] += d;
|
|
|
|
|
|
|
|
if (g_counters[COUNTER_STORM] < 0) g_counters[COUNTER_STORM] = 0;
|
|
|
|
|
|
|
|
} else if (g_life_select == 2) {
|
|
|
|
|
|
|
|
g_counters[COUNTER_POISON] += d;
|
|
|
|
|
|
|
|
if (g_counters[COUNTER_POISON] < 0) g_counters[COUNTER_POISON] = 0;
|
|
|
|
|
|
|
|
check_elimination();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
int si = g_life_select - 3;
|
|
|
|
|
|
|
|
if (g_ble_enabled) {
|
|
|
|
|
|
|
|
int found = 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < MAX_BLE_PEERS; i++) {
|
|
|
|
|
|
|
|
if (g_peers[i].active && found++ == si) {
|
|
|
|
|
|
|
|
g_cmdr_damage[i] += d;
|
|
|
|
|
|
|
|
if (g_cmdr_damage[i] < 0) g_cmdr_damage[i] = 0;
|
|
|
|
|
|
|
|
check_elimination();
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (si < g_num_opponents) {
|
|
|
|
|
|
|
|
g_cmdr_damage[si] += d;
|
|
|
|
|
|
|
|
if (g_cmdr_damage[si] < 0) g_cmdr_damage[si] = 0;
|
|
|
|
|
|
|
|
check_elimination();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
changed = 1; MARK_DIRTY();
|
|
|
|
} else if (g_active_menu == MENU_CMDR) {
|
|
|
|
} else if (g_active_menu == MENU_CMDR) {
|
|
|
|
if (g_ble_enabled) {
|
|
|
|
if (g_ble_enabled) {
|
|
|
|
if (g_active_player == 0) {
|
|
|
|
if (g_active_player == 0) {
|
|
|
|
g_life += d; check_elimination();
|
|
|
|
g_life += d;
|
|
|
|
|
|
|
|
g_life_delta += d;
|
|
|
|
|
|
|
|
g_life_delta_tick = 0;
|
|
|
|
|
|
|
|
check_elimination();
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
int slot = g_active_player - 1;
|
|
|
|
int slot = g_active_player - 1;
|
|
|
|
g_cmdr_damage[slot] += d;
|
|
|
|
g_cmdr_damage[slot] += d;
|
|
|
|
@ -356,6 +545,25 @@ void app_main(void)
|
|
|
|
g_reset_cmd_ticks = 1500;
|
|
|
|
g_reset_cmd_ticks = 1500;
|
|
|
|
ble_adv_update();
|
|
|
|
ble_adv_update();
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SET_MENU_HOLD:
|
|
|
|
|
|
|
|
g_menu_hold_ms += d * 10;
|
|
|
|
|
|
|
|
if (g_menu_hold_ms < HOLD_MS_MIN) g_menu_hold_ms = HOLD_MS_MIN;
|
|
|
|
|
|
|
|
if (g_menu_hold_ms > HOLD_MS_MAX) g_menu_hold_ms = HOLD_MS_MAX;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SET_LR_HOLD:
|
|
|
|
|
|
|
|
g_lr_hold_ms += d * 10;
|
|
|
|
|
|
|
|
if (g_lr_hold_ms < HOLD_MS_MIN) g_lr_hold_ms = HOLD_MS_MIN;
|
|
|
|
|
|
|
|
if (g_lr_hold_ms > HOLD_MS_MAX) g_lr_hold_ms = HOLD_MS_MAX;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SET_DISPLAY_FLIP:
|
|
|
|
|
|
|
|
g_display_flip ^= 1;
|
|
|
|
|
|
|
|
oled_set_flip(g_display_flip);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SET_DELTA_TIMEOUT:
|
|
|
|
|
|
|
|
g_delta_timeout_ms += d * 100;
|
|
|
|
|
|
|
|
if (g_delta_timeout_ms < DELTA_TIMEOUT_MIN) g_delta_timeout_ms = DELTA_TIMEOUT_MIN;
|
|
|
|
|
|
|
|
if (g_delta_timeout_ms > DELTA_TIMEOUT_MAX) g_delta_timeout_ms = DELTA_TIMEOUT_MAX;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -380,6 +588,11 @@ void app_main(void)
|
|
|
|
printf("DBG PEER_EXPIRE slot=%d name=%-8.8s\n", i, g_peers[i].name);
|
|
|
|
printf("DBG PEER_EXPIRE slot=%d name=%-8.8s\n", i, g_peers[i].name);
|
|
|
|
fflush(stdout);
|
|
|
|
fflush(stdout);
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (g_active_menu == MENU_LIFE && g_life_select >= 3 && g_ble_enabled) {
|
|
|
|
|
|
|
|
int slot = 0;
|
|
|
|
|
|
|
|
for (int j = 0; j < i; j++) if (g_peers[j].active) slot++;
|
|
|
|
|
|
|
|
if (g_life_select - 3 == slot) { g_life_select = 0; changed = 1; }
|
|
|
|
|
|
|
|
}
|
|
|
|
g_peers[i].active = 0;
|
|
|
|
g_peers[i].active = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -391,6 +604,15 @@ void app_main(void)
|
|
|
|
settings_save(); settings_dirty = 0; settings_save_tick = 0;
|
|
|
|
settings_save(); settings_dirty = 0; settings_save_tick = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ── Life delta timeout ────────────────────────────────────────────────
|
|
|
|
|
|
|
|
if (g_life_delta != 0) {
|
|
|
|
|
|
|
|
if (++g_life_delta_tick >= g_delta_timeout_ms / 10) {
|
|
|
|
|
|
|
|
g_life_delta = 0;
|
|
|
|
|
|
|
|
g_life_delta_tick = 0;
|
|
|
|
|
|
|
|
if (g_active_menu == MENU_LIFE) changed = 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── BLE adv update on game state change ───────────────────────────────
|
|
|
|
// ── BLE adv update on game state change ───────────────────────────────
|
|
|
|
if (changed && (g_active_menu==MENU_LIFE || g_active_menu==MENU_COUNTERS ||
|
|
|
|
if (changed && (g_active_menu==MENU_LIFE || g_active_menu==MENU_COUNTERS ||
|
|
|
|
g_active_menu==MENU_CMDR)) {
|
|
|
|
g_active_menu==MENU_CMDR)) {
|
|
|
|
@ -405,10 +627,11 @@ void app_main(void)
|
|
|
|
} else { players_tick = 0; }
|
|
|
|
} else { players_tick = 0; }
|
|
|
|
|
|
|
|
|
|
|
|
// ── LED ───────────────────────────────────────────────────────────────
|
|
|
|
// ── LED ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
int bthresh = start_life_opts[g_start_life_index] / 4;
|
|
|
|
if (g_life == 0) {
|
|
|
|
if (g_life == 0) {
|
|
|
|
led_update_for_count(0, 255);
|
|
|
|
led_update_for_count(0, 255);
|
|
|
|
} else if (g_life < 10) {
|
|
|
|
} else if (g_life < bthresh) {
|
|
|
|
float speed = 1.0f + (10.0f - g_life) / 9.0f * 2.0f;
|
|
|
|
float speed = 1.0f + ((float)bthresh - g_life) / (float)(bthresh - 1) * 2.0f;
|
|
|
|
breath_phase += 2.0f * 3.14159265f * speed / 200.0f;
|
|
|
|
breath_phase += 2.0f * 3.14159265f * speed / 200.0f;
|
|
|
|
uint8_t sc = (uint8_t)(127.5f + 127.5f * sinf(breath_phase));
|
|
|
|
uint8_t sc = (uint8_t)(127.5f + 127.5f * sinf(breath_phase));
|
|
|
|
led_update_for_count(g_life, sc);
|
|
|
|
led_update_for_count(g_life, sc);
|
|
|
|
@ -420,8 +643,7 @@ void app_main(void)
|
|
|
|
if (changed) {
|
|
|
|
if (changed) {
|
|
|
|
switch (g_active_menu) {
|
|
|
|
switch (g_active_menu) {
|
|
|
|
case MENU_LIFE:
|
|
|
|
case MENU_LIFE:
|
|
|
|
snprintf(nbuf, sizeof(nbuf), "%d", g_life);
|
|
|
|
oled_draw_life();
|
|
|
|
oled_draw_centered(nbuf);
|
|
|
|
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case MENU_CMDR:
|
|
|
|
case MENU_CMDR:
|
|
|
|
for (int i = 0; i < g_num_opponents; i++) {
|
|
|
|
for (int i = 0; i < g_num_opponents; i++) {
|
|
|
|
@ -458,7 +680,7 @@ void app_main(void)
|
|
|
|
serial_print_state();
|
|
|
|
serial_print_state();
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
prev_right = right; prev_left = left; prev_mid = mid;
|
|
|
|
prev_fwd = fwd; prev_right = right; prev_left = left; prev_back = back;
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|