From 9543851867c8c443dcc9d3d8015e97c633a04c21 Mon Sep 17 00:00:00 2001 From: Jack O'Sullivan Date: Sun, 15 Dec 2024 17:30:08 +0000 Subject: [PATCH] firmware+controller: Add round start state --- controller/hid-test.py | 14 ++++- controller/valconomy.py | 18 +++++- firmware/main/ui.c | 124 ++++++++++++++++++++++++++++++++++++++-- firmware/main/ui.h | 13 +++++ firmware/main/usb.c | 7 +++ firmware/main/usb.h | 1 + 6 files changed, 170 insertions(+), 7 deletions(-) diff --git a/controller/hid-test.py b/controller/hid-test.py index 0bac34b..5401975 100755 --- a/controller/hid-test.py +++ b/controller/hid-test.py @@ -2,7 +2,7 @@ import logging import valconomy -from valconomy import ValorantPlayerInfo, RiotPlayerInfo +from valconomy import ValorantPlayerInfo, RiotPlayerInfo, EconomyDecision def main(): logging.basicConfig( @@ -26,6 +26,18 @@ def main(): h.game_start(None) h.game_over(None, False) h.game_over(None, True) + h.round_start( + RiotPlayerInfo.dummy(valorant=ValorantPlayerInfo(score=3, enemy_score=7)), + won=True, economy=EconomyDecision.SAVE) + h.round_start( + RiotPlayerInfo.dummy(valorant=ValorantPlayerInfo(score=10, enemy_score=2)), + won=False, economy=EconomyDecision.BUY) + h.round_start( + RiotPlayerInfo.dummy(valorant=ValorantPlayerInfo(score=10, enemy_score=2)), + won=None, economy=EconomyDecision.MATCH_TEAM) + h.round_start( + RiotPlayerInfo.dummy(valorant=ValorantPlayerInfo(score=2, enemy_score=0)), + won=True, economy=EconomyDecision.BONUS) h.service() finally: diff --git a/controller/valconomy.py b/controller/valconomy.py index 412166a..ff0c984 100644 --- a/controller/valconomy.py +++ b/controller/valconomy.py @@ -340,8 +340,24 @@ class HIDValconomyHandler(ValconomyHandler): def game_start(self, info: RiotPlayerInfo): self._enq(7) + def round_start(self, info: RiotPlayerInfo, won: bool, economy: EconomyDecision): + match won: + case False: + won_val = 0 + case True: + won_val = 1 + case None: + won_val = 2 + + self._enq( + 8, + info.valorant.score, info.valorant.enemy_score, + won_val, + economy.value, + fmt='BBBB') + def game_over(self, info: RiotPlayerInfo, won: bool): - self._enq(8, 1 if won else 0, fmt='B') + self._enq(9, 1 if won else 0, fmt='B') class GameState(Enum): NONE = 0 diff --git a/firmware/main/ui.c b/firmware/main/ui.c index 6021f2b..6d52e8e 100644 --- a/firmware/main/ui.c +++ b/firmware/main/ui.c @@ -2,7 +2,6 @@ #include "ui.h" -static const char* M_VAL_TIME = "VAL TIME?"; const char *val_ext_gamemodes[] = { "TDM", "DEATHMATCH", @@ -12,10 +11,25 @@ const char *val_ext_gamemodes[] = { "GAME", }; +static const char* M_VAL_TIME = "VAL TIME?"; +static const char *val_eco_titles[] = { + "BUY", + "SAVE", + "BONUS", + "MATCH TEAM", +}; +static const char *val_eco_subtitles[] = { + "SPEND ALL YOUR MONEY!", + "MAKE SURE YOU CAN BUY NEXT ROUND!", + "KEEP YOUR GUN FROM LAST ROUND!", + "FOLLOW THE TEAM ECONOMY", +}; + static const lv_font_t *font_normal = &lv_font_montserrat_24; static lv_color_t color_primary, color_secondary; -static lv_color_t color_text_hero, color_text_subtitle, color_text_victory; +static lv_color_t color_text_hero, color_text_subtitle; +static lv_color_t color_text_won, color_text_lost; static lv_style_t s_hero, s_subtitle; @@ -509,6 +523,105 @@ void val_ui_game_start() { state_ready = true; } +static void anim_text_color_mix_hero_won(void *var, int32_t v) { + lv_obj_set_style_text_color( + var, lv_color_mix(color_text_hero, color_text_won, v), 0); +} +static void anim_text_color_mix_hero_lost(void *var, int32_t v) { + lv_obj_set_style_text_color( + var, lv_color_mix(color_text_hero, color_text_lost, v), 0); +} +void val_ui_round_start(uint8_t score, uint8_t score_enemy, val_won_t won, val_eco_decision_t eco) { + assert(won <= ROUND_NONE && eco <= ECO_MATCH_TEAM); + setup_next_state(); + + // Widgets + lv_obj_t *l_score = lv_label_create(o_active); + lv_obj_add_style(l_score, &s_hero, 0); + lv_obj_center(l_score); + lv_label_set_text_fmt(l_score, "%hhu-%hhu", score, score_enemy); + + lv_obj_t *l_eco = lv_label_create(o_active); + lv_obj_add_style(l_eco, &s_hero, 0); + lv_obj_center(l_eco); + lv_label_set_text_static(l_eco, val_eco_titles[eco]); + + lv_obj_t *l_advice = lv_label_create(o_active); + lv_obj_add_style(l_advice, &s_subtitle, 0); + lv_obj_center(l_advice); + lv_label_set_text_static(l_advice, val_eco_subtitles[eco]); + + lv_obj_update_layout(o_active); + lv_obj_set_y( + l_score, + -(lv_obj_get_height(l_score) / 2) - 20); + lv_obj_set_y( + l_eco, + lv_obj_get_height(l_eco) / 2); + + // Animations + // Score and eco fade in + lv_anim_t a_fade_in; + lv_anim_init(&a_fade_in); + lv_anim_set_values(&a_fade_in, 0, 255); + lv_anim_set_exec_cb(&a_fade_in, anim_opa_cb); + lv_anim_set_path_cb(&a_fade_in, lv_anim_path_ease_in); + lv_anim_set_duration(&a_fade_in, 750); + + lv_anim_set_var(&a_fade_in, l_score); + lv_anim_timeline_add(at_active, 0, &a_fade_in); + + lv_anim_set_var(&a_fade_in, l_eco); + lv_anim_timeline_add(at_active, 0, &a_fade_in); + + // Advice slide up + lv_anim_t a_advice; + lv_anim_init(&a_advice); + lv_anim_set_var(&a_advice, l_advice); + lv_anim_set_values( + &a_advice, + (lv_obj_get_height(o_container) + lv_obj_get_height(l_advice)) / 2, + lv_obj_get_height(l_eco) + (lv_obj_get_height(l_advice) / 2) + 5); + lv_anim_set_exec_cb(&a_advice, anim_y_cb); + lv_anim_set_path_cb(&a_advice, lv_anim_path_ease_out); + lv_anim_set_duration(&a_advice, 750); + + if (won != ROUND_NONE) { + // Score colour + lv_anim_t a_score; + lv_anim_init(&a_score); + lv_anim_set_var(&a_score, l_score); + lv_anim_set_path_cb(&a_score, lv_anim_path_ease_in); + lv_anim_set_exec_cb( + &a_score, + won == ROUND_WON ? anim_text_color_mix_hero_won : anim_text_color_mix_hero_lost); + lv_anim_set_duration(&a_score, 2000); + lv_anim_set_values(&a_score, 0, 255); + lv_anim_set_completed_cb(&a_score, anim_state_ready_cb); + lv_anim_timeline_add(at_active, 0, &a_score); + } else { + lv_anim_set_completed_cb(&a_advice, anim_state_ready_cb); + } + lv_anim_timeline_add(at_active, 0, &a_advice); + + // Eco flash + lv_anim_t a_eco; + lv_anim_init(&a_eco); + lv_anim_set_early_apply(&a_eco, false); + lv_anim_set_var(&a_eco, l_eco); + lv_anim_set_path_cb(&a_eco, lv_anim_path_linear); + lv_anim_set_duration(&a_eco, 1000); + lv_anim_set_exec_cb(&a_eco, anim_text_color_mix_hero_sub); + + lv_anim_set_values(&a_eco, 255, 0); + lv_anim_timeline_add(at_active_rep, 0, &a_eco); + + lv_anim_set_values(&a_eco, 0, 255); + lv_anim_timeline_add(at_active_rep, 1000, &a_eco); + + lv_anim_timeline_start(at_active); +} + void val_ui_game_over(bool won) { setup_next_state(); @@ -517,7 +630,7 @@ void val_ui_game_over(bool won) { lv_obj_add_style(l_main, &s_hero, 0); lv_obj_center(l_main); lv_label_set_text_static(l_main, won ? "VICTORY" : "DEFEAT"); - if (won) lv_obj_set_style_text_color(l_main, color_text_victory, 0); + lv_obj_set_style_text_color(l_main, won ? color_text_won : color_text_lost, 0); lv_obj_t *l_subtitle = lv_label_create(o_active); lv_obj_add_style(l_subtitle, &s_subtitle, 0); @@ -553,7 +666,7 @@ void val_ui_game_over(bool won) { lv_anim_timeline_add(at_active, 0, &a_fly); - // Subtitle from bottom right + // Subtitle from bottom-right // Y lv_anim_set_var(&a_fly, l_subtitle); lv_anim_set_exec_cb(&a_fly, anim_y_cb); @@ -595,7 +708,8 @@ void val_lvgl_ui(lv_display_t *disp) { color_text_hero = lv_palette_lighten(LV_PALETTE_GREY, 2); color_text_subtitle = lv_palette_darken(LV_PALETTE_GREY, 1); - color_text_victory = lv_palette_lighten(LV_PALETTE_GREEN, 1); + color_text_won = lv_palette_lighten(LV_PALETTE_GREEN, 1); + color_text_lost = lv_palette_darken(LV_PALETTE_RED, 4); // init default theme lv_theme_default_init( diff --git a/firmware/main/ui.h b/firmware/main/ui.h index 6a5fa11..eb05c87 100644 --- a/firmware/main/ui.h +++ b/firmware/main/ui.h @@ -5,6 +5,18 @@ extern const char *val_ext_gamemodes[]; #define VAL_EXT_GAMEMODES_SIZE 6 +typedef enum val_eco_decision { + ECO_BUY, + ECO_SAVE, + ECO_BONUS, + ECO_MATCH_TEAM, +} val_eco_decision_t; +typedef enum val_won { + ROUND_LOST, + ROUND_WON, + ROUND_NONE, +} val_won_t; + LV_FONT_DECLARE(lv_font_tungsten_40) LV_FONT_DECLARE(lv_font_tungsten_180) @@ -23,6 +35,7 @@ void val_ui_match_found(bool is_premier); void val_ui_pregame(bool is_split); void val_ui_game_generic(const char *gamemode); void val_ui_game_start(); +void val_ui_round_start(uint8_t score, uint8_t score_enemy, val_won_t won, val_eco_decision_t eco); void val_ui_game_over(bool won); void val_lvgl_ui(lv_display_t *disp); diff --git a/firmware/main/usb.c b/firmware/main/usb.c index 876073d..f2a036c 100644 --- a/firmware/main/usb.c +++ b/firmware/main/usb.c @@ -154,6 +154,13 @@ void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_ case ST_GAME_START: val_ui_game_start(); break; + case ST_ROUND_START: + if (bufsize < 5 || buf[3] > ROUND_NONE || buf[4] > ECO_MATCH_TEAM) { + ESP_LOGE(TAG, "Invalid ST_ROUND_START command"); + goto ret; + } + val_ui_round_start(buf[1], buf[2], buf[3], buf[4]); + break; case ST_GAME_OVER: if (bufsize < 2) { ESP_LOGE(TAG, "Invalid ST_GAME_OVER command"); diff --git a/firmware/main/usb.h b/firmware/main/usb.h index 8f4ae25..d10a97b 100644 --- a/firmware/main/usb.h +++ b/firmware/main/usb.h @@ -14,6 +14,7 @@ typedef enum val_state { ST_PREGAME, ST_GAME_GENERIC, ST_GAME_START, + ST_ROUND_START, ST_GAME_OVER, } val_state_t;