Compare commits

...

13 Commits

9 changed files with 825 additions and 46 deletions

View File

@@ -1,6 +1,7 @@
import configparser import configparser
import logging import logging
import os import os
import signal
import sys import sys
import infi.systray import infi.systray
@@ -42,12 +43,16 @@ def main():
conf = configparser.ConfigParser() conf = configparser.ConfigParser()
conf.read_dict({ conf.read_dict({
'general': {'log_level': 'info'}, 'general': {
'log_level': 'info',
'dummy_impl': False,
},
'valorant': {'player_uuid': ''}, 'valorant': {'player_uuid': ''},
}) })
conf.read(CONFIG_FILE) conf.read(CONFIG_FILE)
with open(CONFIG_FILE, 'w') as f: with open(CONFIG_FILE, 'w') as f:
conf.write(f) conf.write(f)
use_dummy_impl = conf.getboolean('general', 'dummy_impl')
log_level = parse_log_level(conf['general']['log_level']) log_level = parse_log_level(conf['general']['log_level'])
logging.basicConfig( logging.basicConfig(
@@ -59,19 +64,27 @@ def main():
log.error(f'No player UUID set, exiting...') log.error(f'No player UUID set, exiting...')
sys.exit(1) sys.exit(1)
if use_dummy_impl:
val_handler = valconomy.ValconomyHandler() val_handler = valconomy.ValconomyHandler()
else:
val_handler = valconomy.HIDValconomyHandler()
val_handler.start()
val_sm = valconomy.ValconomyStateMachine(conf['valorant']['player_uuid'], val_handler) val_sm = valconomy.ValconomyStateMachine(conf['valorant']['player_uuid'], val_handler)
val_client = valconomy.ValorantLocalClient(val_sm.handle_presence) val_client = valconomy.ValorantLocalClient(val_sm.handle_presence)
def do_quit(tray: infi.systray.SysTrayIcon): def do_quit(*args):
log.info('Shutting down') log.info('Shutting down')
val_client.stop() val_client.stop()
if not use_dummy_impl:
val_handler.stop()
with infi.systray.SysTrayIcon( with infi.systray.SysTrayIcon(
data_file('icon.ico'), 'Valconomy', on_quit=do_quit, data_file('icon.ico'), 'Valconomy', on_quit=do_quit,
menu_options=( menu_options=(
('Open data directory', None, do_open_datadir), ('Open data directory', None, do_open_datadir),
)) as tray: )) as tray:
signal.signal(signal.SIGINT, do_quit)
signal.signal(signal.SIGTERM, do_quit)
val_client.run() val_client.run()
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -1,17 +1,47 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import hid import logging
USB_VID = 0x6969 import valconomy
USB_PID = 0x0004 from valconomy import ValorantPlayerInfo, RiotPlayerInfo, EconomyDecision
def main(): def main():
hid_handle = hid.device() logging.basicConfig(
hid_handle.open(vendor_id=USB_VID, product_id=USB_PID) format='%(asctime)s %(name)s %(levelname)s %(message)s', level=logging.INFO)
h = valconomy.HIDValconomyHandler()
try: try:
hid_handle.write(b'\x00test') h.menu(None, False)
h.none()
h.queue_start(RiotPlayerInfo.dummy(
valorant=ValorantPlayerInfo()))
h.menu(None, True)
h.idle(None)
h.queue_start(RiotPlayerInfo.dummy(
valorant=ValorantPlayerInfo(queue_type='unrated', is_party_owner=True)))
h.match_found(RiotPlayerInfo.dummy(valorant=ValorantPlayerInfo(queue_type='premier-seasonmatch')))
h.pregame(RiotPlayerInfo.dummy(valorant=ValorantPlayerInfo(map='/Game/Maps/Bonsai/Bonsai')))
h.match_found(RiotPlayerInfo.dummy(valorant=ValorantPlayerInfo()))
h.pregame(RiotPlayerInfo.dummy(valorant=ValorantPlayerInfo()))
h.game_generic(RiotPlayerInfo.dummy(valorant=ValorantPlayerInfo(queue_type='ggteam')))
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: finally:
hid_handle.close() h.close()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@@ -81,7 +81,8 @@ class TestSm:
('round_start', True, 2, 0, EconomyDecision.BONUS), ('round_start', True, 2, 0, EconomyDecision.BONUS),
('round_start', False, 2, 1, EconomyDecision.MATCH_TEAM), ('round_start', False, 2, 1, EconomyDecision.MATCH_TEAM),
] + [ ] + [
('round_start', False, 2, 2 + i, EconomyDecision.MATCH_TEAM) for i in range(8)] + [ ('round_start', False, 2, 2 + i, EconomyDecision.MATCH_TEAM) for i in range(7)] + [
('round_start', False, 2, 9, EconomyDecision.BUY), # last round in half
('round_start', False, 2, 10, EconomyDecision.BUY), ('round_start', False, 2, 10, EconomyDecision.BUY),
('round_start', False, 2, 11, EconomyDecision.SAVE), ('round_start', False, 2, 11, EconomyDecision.SAVE),
('round_start', False, 2, 12, EconomyDecision.BUY), ('round_start', False, 2, 12, EconomyDecision.BUY),
@@ -112,7 +113,8 @@ class TestSm:
('round_start', True, 1, 0, EconomyDecision.BUY), ('round_start', True, 1, 0, EconomyDecision.BUY),
('round_start', False, 1, 1, EconomyDecision.SAVE), ('round_start', False, 1, 1, EconomyDecision.SAVE),
] + [ ] + [
('round_start', False, 1, 2 + i, EconomyDecision.MATCH_TEAM) for i in range(9)] + [ ('round_start', False, 1, 2 + i, EconomyDecision.MATCH_TEAM) for i in range(8)] + [
('round_start', False, 1, 10, EconomyDecision.BUY), # last round in half
('round_start', False, 1, 11, EconomyDecision.BUY), ('round_start', False, 1, 11, EconomyDecision.BUY),
('round_start', False, 1, 12, EconomyDecision.BUY), ('round_start', False, 1, 12, EconomyDecision.BUY),
] + [ ] + [
@@ -133,8 +135,10 @@ class TestSm:
self.do(queue_type='swiftplay', game_state='PREGAME') self.do(queue_type='swiftplay', game_state='PREGAME')
self.do(queue_type='swiftplay', game_state='INGAME') self.do(queue_type='swiftplay', game_state='INGAME')
for i in range(5): for i in range(4):
self.do(queue_type='swiftplay', game_state='INGAME', enemy_score=1 + i) self.do(queue_type='swiftplay', game_state='INGAME', enemy_score=1 + i)
for i in range(5):
self.do(queue_type='swiftplay', game_state='INGAME', score=1 + i, enemy_score=4)
self.do(queue_type='swiftplay', game_state='INGAME') self.do(queue_type='swiftplay', game_state='INGAME')
assert self.mock.seq == [ assert self.mock.seq == [
@@ -144,9 +148,15 @@ class TestSm:
('pregame', False), ('pregame', False),
'game_start', 'game_start',
('round_start', None, 0, 0, EconomyDecision.BUY), ('round_start', None, 0, 0, EconomyDecision.BUY),
] + [ ('round_start', False, 0, 1, EconomyDecision.BUY),
('round_start', False, 0, 1 + i, EconomyDecision.BUY) for i in range(4)] + [ ('round_start', False, 0, 2, EconomyDecision.BUY),
('game_over', False), ('round_start', False, 0, 3, EconomyDecision.BUY),
('round_start', False, 0, 4, EconomyDecision.BUY),
('round_start', True, 1, 4, EconomyDecision.BUY),
('round_start', True, 2, 4, EconomyDecision.BUY),
('round_start', True, 3, 4, EconomyDecision.BUY),
('round_start', True, 4, 4, EconomyDecision.BUY),
('game_over', True),
] ]
def test_tdm(self): def test_tdm(self):

View File

@@ -5,6 +5,9 @@ import json
import logging import logging
from pprint import pprint from pprint import pprint
import os import os
import queue
import struct
import threading
import time import time
from typing import Callable from typing import Callable
@@ -45,16 +48,20 @@ class RiotPlayerInfo:
valorant: ValorantPlayerInfo = None valorant: ValorantPlayerInfo = None
@classmethod
def dummy(cls, **kwargs):
return cls('00000000-0000-0000-0000-000000000000', 'Player', 'gamer', 'dnd', **kwargs)
def full_name(self) -> str: def full_name(self) -> str:
return f'{self.name}#{self.tag}' return f'{self.name}#{self.tag}'
class ValorantLocalClient: class ValorantLocalClient:
lockfile_path = os.path.join(os.environ['LOCALAPPDATA'], r'Riot Games\Riot Client\Config\lockfile')
poll_interval = 0.5 poll_interval = 0.5
def __init__(self, callback: Callable[[RiotPlayerInfo, bool], None]): def __init__(self, callback: Callable[[RiotPlayerInfo, bool], None]):
self.callback = callback self.callback = callback
self.lockfile_path = os.path.join(os.environ['LOCALAPPDATA'], r'Riot Games\Riot Client\Config\lockfile')
self.port, self.password = None, None self.port, self.password = None, None
self.running = True self.running = True
self.players = {} self.players = {}
@@ -228,7 +235,143 @@ class ValconomyHandler:
log.info('Hard luck...') log.info('Hard luck...')
class HIDValconomyHandler(ValconomyHandler): class HIDValconomyHandler(ValconomyHandler):
pass vid = 0x6969
pid = 0x0004
def __init__(self):
self.dev = None
self.running = True
self.queue = queue.Queue(128)
self._thread = None
def close(self):
if self.dev is not None:
self.dev.close()
def _dev_ready(self):
try:
if self.dev is None:
dev = hid.device()
dev.open(vendor_id=self.vid, product_id=self.pid)
self.dev = dev
log.info(f'USB device opened')
# 2 bytes: report ID and returned value
# We get back the same report ID and value
# Set to report size + 1 to satisfy W*ndoze
data = self.dev.get_input_report(0, 65)
assert len(data) == 2
return data[1] == 1
except OSError as ex:
if self.dev is not None:
log.warning(f'USB device lost')
self.dev = None
return False
def _do(self, cmd, *vals, fmt=None):
if fmt is None:
fmt = ''
fmt = '<BB' + fmt
# Prepend report ID 0
data = struct.pack(fmt, *(0, cmd) + vals)
self.dev.write(data)
def _enq(self, cmd, *vals, fmt=None):
self.queue.put((cmd, vals, fmt))
def service(self):
while not self.queue.empty():
cmd, vals, fmt = self.queue.get()
while not self._dev_ready():
if not self.running:
return
time.sleep(0.1)
try:
self._do(cmd, *vals, fmt=fmt)
except OSError as ex:
log.warning(f'USB device lost, state dequeuing stalled')
def run(self):
while self.running:
while self.queue.empty():
if not self.running:
return
time.sleep(0.5)
self.service()
def start(self):
assert self._thread is None
self._thread = threading.Thread(name='HIDValconomyHandler', target=self.run)
self._thread.start()
def stop(self):
if self._thread is None or not self.running:
return
self.running = False
self._thread.join()
def none(self):
self._enq(0)
def menu(self, info: RiotPlayerInfo, was_idle: bool=False):
self._enq(1, 1 if was_idle else 0, fmt='B')
def idle(self, info: RiotPlayerInfo):
self._enq(2)
def queue_start(self, info: RiotPlayerInfo):
ms_not_comp = info.valorant.is_party_owner and info.valorant.queue_type == 'unrated'
self._enq(3, 1 if ms_not_comp else 0, fmt='B')
def match_found(self, info: RiotPlayerInfo):
self._enq(4, 1 if info.valorant.queue_type == 'premier-seasonmatch' else 0, fmt='B')
def pregame(self, info: RiotPlayerInfo):
self._enq(5, 1 if info.valorant.map == '/Game/Maps/Bonsai/Bonsai' else 0, fmt='B')
def game_generic(self, info: RiotPlayerInfo):
match info.valorant.queue_type:
case 'hurm': # tdm
gm = 0
case 'deathmatch':
gm = 1
case 'ggteam': # escalation
gm = 2
case 'spikerush':
gm = 3
case _:
if info.valorant.max_party_size == 12: # custom
gm = 4
else:
gm = 5
self._enq(6, gm, fmt='B')
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(9, 1 if won else 0, fmt='B')
class GameState(Enum): class GameState(Enum):
NONE = 0 NONE = 0
@@ -265,17 +408,16 @@ class ValconomyStateMachine:
won_last = True if p.valorant.score > self.score else False won_last = True if p.valorant.score > self.score else False
self.round_history.append(won_last) self.round_history.append(won_last)
over = False
if p.valorant.queue_type == 'swiftplay': if p.valorant.queue_type == 'swiftplay':
if p.valorant.score == 5 or p.valorant.enemy_score == 5: if p.valorant.score == 5 or p.valorant.enemy_score == 5:
# Game is over over = True
return
eco = EconomyDecision.BUY eco = EconomyDecision.BUY
else: else:
if p.valorant.score > 12 or p.valorant.enemy_score > 12: if p.valorant.score > 12 or p.valorant.enemy_score > 12:
if p.valorant.queue_type == 'unrated' or abs(p.valorant.score - p.valorant.enemy_score) >= 2: if p.valorant.queue_type == 'unrated' or abs(p.valorant.score - p.valorant.enemy_score) >= 2:
# Match is over over = True
return
eco = EconomyDecision.MATCH_TEAM eco = EconomyDecision.MATCH_TEAM
rounds_played = p.valorant.score + p.valorant.enemy_score rounds_played = p.valorant.score + p.valorant.enemy_score
@@ -300,7 +442,11 @@ class ValconomyStateMachine:
elif rounds_played >= 24: elif rounds_played >= 24:
# Sudden death or overtime (buy either way) # Sudden death or overtime (buy either way)
eco = EconomyDecision.BUY eco = EconomyDecision.BUY
elif rounds_played == 11:
# Last round of half
eco = EconomyDecision.BUY
if not over:
self.handler.round_start(p, won_last, eco) self.handler.round_start(p, won_last, eco)
self.score = p.valorant.score self.score = p.valorant.score
self.score_enemy = p.valorant.enemy_score self.score_enemy = p.valorant.enemy_score

View File

@@ -2,18 +2,41 @@
#include "ui.h" #include "ui.h"
const char *val_ext_gamemodes[] = {
"TDM",
"DEATHMATCH",
"ESCALATION",
"SPIKE RUSH",
"CUSTOM",
"GAME",
};
static const char* M_VAL_TIME = "VAL TIME?"; 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 const lv_font_t *font_normal = &lv_font_montserrat_24;
static lv_color_t color_primary, color_secondary; static lv_color_t color_primary, color_secondary;
static lv_color_t color_text_hero, color_text_subtitle; 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; static lv_style_t s_hero, s_subtitle;
static lv_obj_t *o_container = NULL; static lv_obj_t *o_container = NULL;
static lv_anim_timeline_t *at_active = NULL; static lv_anim_timeline_t *at_active = NULL;
static lv_anim_timeline_t *at_active_rep = NULL;
static lv_obj_t *o_active = NULL; static lv_obj_t *o_active = NULL;
static bool state_ready = true; static bool state_ready = true;
@@ -38,8 +61,8 @@ static const void* imgfont_get_path(
static void b_cfg_cb(lv_event_t *e) { static void b_cfg_cb(lv_event_t *e) {
lv_obj_t *box = lv_msgbox_create(NULL); lv_obj_t *box = lv_msgbox_create(NULL);
lv_msgbox_add_title(box, "Hello"); lv_msgbox_add_title(box, "Settings");
lv_msgbox_add_text(box, "test message"); lv_msgbox_add_text(box, "Sorry, there aren't any right now.");
lv_msgbox_add_close_button(box); lv_msgbox_add_close_button(box);
} }
@@ -51,6 +74,10 @@ static void setup_next_state() {
lv_anim_timeline_delete(at_active); lv_anim_timeline_delete(at_active);
at_active = NULL; at_active = NULL;
} }
if (at_active_rep) {
lv_anim_timeline_delete(at_active_rep);
at_active_rep = NULL;
}
if (o_active) { if (o_active) {
lv_obj_delete(o_active); lv_obj_delete(o_active);
o_active = NULL; o_active = NULL;
@@ -61,8 +88,11 @@ static void setup_next_state() {
lv_obj_set_style_bg_opa(o_active, LV_OPA_0, 0); lv_obj_set_style_bg_opa(o_active, LV_OPA_0, 0);
lv_obj_set_style_border_width(o_active, 0, 0); lv_obj_set_style_border_width(o_active, 0, 0);
lv_obj_set_size(o_active, lv_pct(100), lv_pct(100)); lv_obj_set_size(o_active, lv_pct(100), lv_pct(100));
lv_obj_set_scrollbar_mode(o_active, LV_SCROLLBAR_MODE_OFF);
at_active = lv_anim_timeline_create(); at_active = lv_anim_timeline_create();
at_active_rep = lv_anim_timeline_create();
lv_anim_timeline_set_repeat_count(at_active_rep, LV_ANIM_REPEAT_INFINITE);
} }
bool val_ui_state_ready() { bool val_ui_state_ready() {
@@ -70,6 +100,7 @@ bool val_ui_state_ready() {
} }
static void anim_state_ready_cb(lv_anim_t *anim) { static void anim_state_ready_cb(lv_anim_t *anim) {
lv_anim_timeline_start(at_active_rep);
state_ready = true; state_ready = true;
} }
static void anim_x_cb(void *var, int32_t v) { static void anim_x_cb(void *var, int32_t v) {
@@ -81,10 +112,10 @@ static void anim_y_cb(void *var, int32_t v) {
static void anim_opa_cb(void *var, int32_t v) { static void anim_opa_cb(void *var, int32_t v) {
lv_obj_set_style_opa(var, v, 0); lv_obj_set_style_opa(var, v, 0);
} }
static void anim_val_time_text(lv_anim_t *anim) { static void anim_val_time_text(lv_anim_t *anim) {
lv_label_set_text_static(anim->var, M_VAL_TIME); lv_label_set_text_static(anim->var, M_VAL_TIME);
} }
void val_ui_none() { void val_ui_none() {
setup_next_state(); setup_next_state();
@@ -175,7 +206,7 @@ void val_ui_menu(bool was_idle) {
lv_anim_set_var(&a_title, l_main); lv_anim_set_var(&a_title, l_main);
lv_anim_set_values( lv_anim_set_values(
&a_title, &a_title,
-(lv_obj_get_height(o_container) - lv_obj_get_height(l_main)) / 2, 0); -(lv_obj_get_height(o_container) + lv_obj_get_height(l_main)) / 2, 0);
lv_anim_set_exec_cb(&a_title, anim_y_cb); lv_anim_set_exec_cb(&a_title, anim_y_cb);
lv_anim_set_path_cb(&a_title, lv_anim_path_ease_in); lv_anim_set_path_cb(&a_title, lv_anim_path_ease_in);
lv_anim_set_duration(&a_title, 500); lv_anim_set_duration(&a_title, 500);
@@ -185,7 +216,7 @@ void val_ui_menu(bool was_idle) {
lv_anim_set_var(&a_sub, l_subtitle); lv_anim_set_var(&a_sub, l_subtitle);
lv_anim_set_values( lv_anim_set_values(
&a_sub, &a_sub,
(lv_obj_get_height(o_container) - lv_obj_get_height(l_main)) / 2, (lv_obj_get_height(o_container) + lv_obj_get_height(l_subtitle)) / 2,
(lv_obj_get_height(l_main) + lv_obj_get_height(l_subtitle)) / 2 + 5); (lv_obj_get_height(l_main) + lv_obj_get_height(l_subtitle)) / 2 + 5);
lv_anim_set_exec_cb(&a_sub, anim_y_cb); lv_anim_set_exec_cb(&a_sub, anim_y_cb);
lv_anim_set_path_cb(&a_sub, lv_anim_path_ease_in); lv_anim_set_path_cb(&a_sub, lv_anim_path_ease_in);
@@ -194,6 +225,32 @@ void val_ui_menu(bool was_idle) {
lv_anim_timeline_add(at_active, 0, &a_title); lv_anim_timeline_add(at_active, 0, &a_title);
lv_anim_timeline_add(at_active, 0, &a_sub); lv_anim_timeline_add(at_active, 0, &a_sub);
// Repeating loop swing back and forth
lv_anim_t a_title_swing;
lv_anim_init(&a_title_swing);
lv_anim_set_early_apply(&a_title_swing, false);
lv_anim_set_var(&a_title_swing, l_main);
lv_anim_set_exec_cb(&a_title_swing, anim_x_cb);
// Left
lv_anim_set_path_cb(&a_title_swing, lv_anim_path_ease_out);
lv_anim_set_values(&a_title_swing, 0, -10);
lv_anim_set_duration(&a_title_swing, 500);
lv_anim_timeline_add(at_active_rep, 0, &a_title_swing);
// Left to right
lv_anim_set_path_cb(&a_title_swing, lv_anim_path_ease_in_out);
lv_anim_set_values(&a_title_swing, -10, 10);
lv_anim_set_duration(&a_title_swing, 1000);
lv_anim_timeline_add(at_active_rep, 500, &a_title_swing);
// Right to centre
lv_anim_set_path_cb(&a_title_swing, lv_anim_path_ease_in);
lv_anim_set_values(&a_title_swing, 10, 0);
lv_anim_set_duration(&a_title_swing, 500);
lv_anim_timeline_add(at_active_rep, 1500, &a_title_swing);
lv_anim_timeline_start(at_active); lv_anim_timeline_start(at_active);
} }
@@ -234,19 +291,432 @@ void val_ui_idle() {
lv_anim_timeline_start(at_active); lv_anim_timeline_start(at_active);
} }
static void anim_text_color_mix_hero_sub(void *var, int32_t v) {
lv_obj_set_style_text_color(
var, lv_color_mix(color_text_hero, color_text_subtitle, v), 0);
}
void val_ui_queue_start(bool ms_not_comp) {
setup_next_state();
// Widgets
lv_obj_t *l_main = lv_label_create(o_active);
lv_obj_add_style(l_main, &s_hero, 0);
lv_obj_center(l_main);
lv_label_set_text_static(l_main, "SEARCHING...");
lv_obj_t *l_subtitle = lv_label_create(o_active);
lv_obj_add_style(l_subtitle, &s_subtitle, 0);
lv_obj_center(l_subtitle);
lv_label_set_text_static(l_subtitle, ms_not_comp ? "UHHH SHOULD THAT BE COMP?" : "HOPE YOU FIND A GAME QUICKLY!");
lv_obj_t *spinner = lv_spinner_create(o_active);
lv_obj_set_size(spinner, 100, 100);
lv_obj_center(spinner);
lv_obj_set_style_arc_color(spinner, color_text_hero, LV_PART_INDICATOR);
lv_spinner_set_anim_params(spinner, 1500, 200);
lv_obj_update_layout(o_active);
const int32_t offset = -60;
lv_obj_set_y(l_main, offset);
lv_obj_set_y(
l_subtitle,
offset + (lv_obj_get_height(l_main) + lv_obj_get_height(l_subtitle)) / 2 + 5);
lv_obj_set_y(
spinner,
lv_obj_get_height(o_container) / 4);
// Animations
lv_anim_t a_title;
lv_anim_init(&a_title);
lv_anim_set_var(&a_title, l_main);
lv_anim_set_values(
&a_title,
-(lv_obj_get_width(o_container) + lv_obj_get_width(l_main)) / 2, 0);
lv_anim_set_exec_cb(&a_title, anim_x_cb);
lv_anim_set_path_cb(&a_title, lv_anim_path_ease_out);
lv_anim_set_duration(&a_title, 750);
lv_anim_t a_sub;
lv_anim_init(&a_sub);
lv_anim_set_var(&a_sub, l_subtitle);
lv_anim_set_values(
&a_sub,
(lv_obj_get_width(o_container) + lv_obj_get_width(l_subtitle)) / 2, 0);
lv_anim_set_exec_cb(&a_sub, anim_x_cb);
lv_anim_set_path_cb(&a_sub, lv_anim_path_ease_out);
lv_anim_set_completed_cb(&a_sub, anim_state_ready_cb);
lv_anim_set_duration(&a_sub, 750);
lv_anim_timeline_add(at_active, 0, &a_title);
lv_anim_timeline_add(at_active, 0, &a_sub);
if (ms_not_comp) {
lv_anim_t a_warn;
lv_anim_init(&a_warn);
lv_anim_set_early_apply(&a_warn, false);
lv_anim_set_var(&a_warn, l_subtitle);
lv_anim_set_path_cb(&a_warn, lv_anim_path_linear);
lv_anim_set_duration(&a_warn, 1000);
lv_anim_set_exec_cb(&a_warn, anim_text_color_mix_hero_sub);
lv_anim_set_values(&a_warn, 0, 255);
lv_anim_timeline_add(at_active_rep, 0, &a_warn);
lv_anim_set_values(&a_warn, 255, 0);
lv_anim_timeline_add(at_active_rep, 1000, &a_warn);
}
lv_anim_timeline_start(at_active);
}
void val_ui_match_found(bool is_premier) {
setup_next_state();
// Widgets
lv_obj_t *l_main = lv_label_create(o_active);
lv_obj_add_style(l_main, &s_hero, 0);
lv_obj_center(l_main);
lv_label_set_text_static(l_main, "MATCH FOUND");
lv_obj_t *l_subtitle = lv_label_create(o_active);
lv_obj_add_style(l_subtitle, &s_subtitle, 0);
lv_obj_center(l_subtitle);
lv_label_set_text_static(l_subtitle, is_premier ? "DO THE COSMONAUTS PROUD!" : "I HOPE IT'S NOT SPLIT...");
lv_obj_update_layout(o_active);
// Animations
lv_anim_t a_title;
lv_anim_init(&a_title);
lv_anim_set_var(&a_title, l_main);
lv_anim_set_values(
&a_title,
(lv_obj_get_height(o_container) + lv_obj_get_height(l_main)) / 2, 0);
lv_anim_set_exec_cb(&a_title, anim_y_cb);
lv_anim_set_path_cb(&a_title, lv_anim_path_ease_in);
lv_anim_set_duration(&a_title, 500);
lv_anim_t a_sub;
lv_anim_init(&a_sub);
lv_anim_set_var(&a_sub, l_subtitle);
lv_anim_set_values(
&a_sub,
-(lv_obj_get_height(o_container) + lv_obj_get_height(l_main)) / 2,
(lv_obj_get_height(l_main) + lv_obj_get_height(l_subtitle)) / 2 + 5);
lv_anim_set_exec_cb(&a_sub, anim_y_cb);
lv_anim_set_path_cb(&a_sub, lv_anim_path_ease_in);
lv_anim_set_completed_cb(&a_sub, anim_state_ready_cb);
lv_anim_set_duration(&a_sub, 500);
lv_anim_timeline_add(at_active, 0, &a_title);
lv_anim_timeline_add(at_active, 0, &a_sub);
lv_anim_timeline_start(at_active);
}
void val_ui_pregame(bool is_split) {
setup_next_state();
// Widgets
lv_obj_t *l_main = lv_label_create(o_active);
lv_obj_add_style(l_main, &s_hero, 0);
lv_obj_center(l_main);
lv_label_set_text_static(l_main, "CHOOSE AGENT");
lv_obj_t *l_subtitle = lv_label_create(o_active);
lv_obj_add_style(l_subtitle, &s_subtitle, 0);
lv_obj_center(l_subtitle);
lv_label_set_text_static(l_subtitle, is_split ? "EWWW, SPLIT..." : "PICK SOMETHING GOOD!");
lv_obj_update_layout(o_active);
lv_obj_set_y(
l_subtitle,
(lv_obj_get_height(l_main) + lv_obj_get_height(l_subtitle)) / 2 + 5);
// Animations
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, 500);
lv_anim_set_var(&a_fade_in, l_main);
lv_anim_timeline_add(at_active, 0, &a_fade_in);
lv_anim_set_var(&a_fade_in, l_subtitle);
lv_anim_set_completed_cb(&a_fade_in, anim_state_ready_cb);
lv_anim_timeline_add(at_active, 0, &a_fade_in);
lv_anim_t a_warn;
lv_anim_init(&a_warn);
lv_anim_set_early_apply(&a_warn, false);
lv_anim_set_var(&a_warn, l_main);
lv_anim_set_path_cb(&a_warn, lv_anim_path_linear);
lv_anim_set_duration(&a_warn, 2000);
lv_anim_set_exec_cb(&a_warn, anim_text_color_mix_hero_sub);
lv_anim_set_values(&a_warn, 255, 0);
lv_anim_timeline_add(at_active_rep, 0, &a_warn);
lv_anim_set_values(&a_warn, 0, 255);
lv_anim_timeline_add(at_active_rep, 2000, &a_warn);
lv_anim_timeline_start(at_active);
}
void val_ui_game_generic(const char *gamemode) {
setup_next_state();
// Widgets
lv_obj_t *l_main = lv_label_create(o_active);
lv_obj_add_style(l_main, &s_hero, 0);
lv_obj_center(l_main);
lv_label_set_text_static(l_main, "HAVE FUN!");
lv_obj_t *l_subtitle = lv_label_create(o_active);
lv_obj_add_style(l_subtitle, &s_subtitle, 0);
lv_obj_center(l_subtitle);
lv_label_set_text_fmt(l_subtitle, "ENJOY YOUR %s!", gamemode);
lv_obj_update_layout(o_active);
lv_obj_set_y(
l_subtitle,
(lv_obj_get_height(l_main) + lv_obj_get_height(l_subtitle)) / 2 + 5);
// Animations
lv_anim_t a_title;
lv_anim_init(&a_title);
lv_anim_set_var(&a_title, l_main);
lv_anim_set_values(
&a_title,
-(lv_obj_get_width(o_container) + lv_obj_get_width(l_main)) / 2, 0);
lv_anim_set_exec_cb(&a_title, anim_x_cb);
lv_anim_set_path_cb(&a_title, lv_anim_path_bounce);
lv_anim_set_duration(&a_title, 750);
lv_anim_t a_sub;
lv_anim_init(&a_sub);
lv_anim_set_var(&a_sub, l_subtitle);
lv_anim_set_values(
&a_sub,
-(lv_obj_get_width(o_container) + lv_obj_get_width(l_subtitle)) / 2,
-(lv_obj_get_width(l_main) - lv_obj_get_width(l_subtitle)) / 2);
lv_anim_set_exec_cb(&a_sub, anim_x_cb);
lv_anim_set_path_cb(&a_sub, lv_anim_path_bounce);
lv_anim_set_completed_cb(&a_sub, anim_state_ready_cb);
lv_anim_set_duration(&a_sub, 750);
lv_anim_timeline_add(at_active, 0, &a_title);
lv_anim_timeline_add(at_active, 0, &a_sub);
lv_anim_timeline_start(at_active);
}
void val_ui_game_start() {
setup_next_state();
// Widgets
lv_obj_t *l_main = lv_label_create(o_active);
lv_obj_add_style(l_main, &s_hero, 0);
lv_obj_center(l_main);
lv_label_set_text_static(l_main, "GOOD LUCK!");
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();
// Widgets
lv_obj_t *l_main = lv_label_create(o_active);
lv_obj_add_style(l_main, &s_hero, 0);
lv_obj_center(l_main);
lv_label_set_text_static(l_main, won ? "VICTORY" : "DEFEAT");
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);
lv_obj_center(l_subtitle);
lv_label_set_text_static(l_subtitle, won ? "WELL PLAYED!" : "HARD LUCK...");
lv_obj_update_layout(o_active);
lv_obj_set_y(
l_subtitle,
(lv_obj_get_height(l_main) + lv_obj_get_height(l_subtitle)) / 2 + 5);
// Animations
if (won) {
lv_anim_t a_fly;
lv_anim_init(&a_fly);
lv_anim_set_path_cb(&a_fly, lv_anim_path_ease_out);
lv_anim_set_duration(&a_fly, 750);
// Main text from top-left
// Y
lv_anim_set_var(&a_fly, l_main);
lv_anim_set_exec_cb(&a_fly, anim_y_cb);
lv_anim_set_values(
&a_fly,
-(lv_obj_get_height(o_container) + lv_obj_get_height(l_main)) / 2, 0);
lv_anim_timeline_add(at_active, 0, &a_fly);
// X
lv_anim_set_exec_cb(&a_fly, anim_x_cb);
lv_anim_set_values(
&a_fly,
-(lv_obj_get_width(o_container) + lv_obj_get_width(l_main)) / 2, 0);
lv_anim_timeline_add(at_active, 0, &a_fly);
// Subtitle from bottom-right
// Y
lv_anim_set_var(&a_fly, l_subtitle);
lv_anim_set_exec_cb(&a_fly, anim_y_cb);
lv_anim_set_values(
&a_fly,
(lv_obj_get_height(o_container) + lv_obj_get_height(l_subtitle)) / 2,
(lv_obj_get_height(l_main) + lv_obj_get_height(l_subtitle)) / 2 + 5);
lv_anim_timeline_add(at_active, 0, &a_fly);
// X
lv_anim_set_exec_cb(&a_fly, anim_x_cb);
lv_anim_set_values(
&a_fly,
(lv_obj_get_width(o_container) + lv_obj_get_width(l_subtitle)) / 2, 0);
lv_anim_set_completed_cb(&a_fly, anim_state_ready_cb);
lv_anim_timeline_add(at_active, 0, &a_fly);
} else {
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, 1000);
lv_anim_set_var(&a_fade_in, l_main);
lv_anim_timeline_add(at_active, 0, &a_fade_in);
lv_anim_set_var(&a_fade_in, l_subtitle);
lv_anim_set_completed_cb(&a_fade_in, anim_state_ready_cb);
lv_anim_timeline_add(at_active, 0, &a_fade_in);
}
lv_anim_timeline_start(at_active);
}
void val_lvgl_ui(lv_display_t *disp) { void val_lvgl_ui(lv_display_t *disp) {
color_primary = lv_color_hex(0xff4655); color_primary = lv_color_hex(0xff4655);
color_secondary = lv_color_hex(0xf7518f); color_secondary = lv_color_hex(0xf7518f);
color_text_hero = lv_palette_lighten(LV_PALETTE_GREY, 2); color_text_hero = lv_palette_lighten(LV_PALETTE_GREY, 2);
color_text_subtitle = lv_palette_darken(LV_PALETTE_GREY, 2); color_text_subtitle = lv_palette_darken(LV_PALETTE_GREY, 1);
color_text_won = lv_palette_lighten(LV_PALETTE_GREEN, 1);
color_text_lost = lv_palette_darken(LV_PALETTE_RED, 4);
// init default theme // init default theme
lv_theme_default_init( lv_theme_default_init(
disp, color_primary, color_secondary, disp, color_primary, color_secondary,
true, // dark theme true, // dark theme
font_normal); font_normal);
// lv_sysmon_hide_performance(disp); lv_sysmon_hide_performance(disp);
lv_font_t *f_hero_emoji = lv_imgfont_create(120, imgfont_get_path, NULL); lv_font_t *f_hero_emoji = lv_imgfont_create(120, imgfont_get_path, NULL);
assert(f_hero_emoji); assert(f_hero_emoji);
@@ -270,6 +740,7 @@ void val_lvgl_ui(lv_display_t *disp) {
lv_obj_set_style_border_width(o_container, 0, 0); lv_obj_set_style_border_width(o_container, 0, 0);
lv_obj_set_style_pad_all(o_container, 0, 0); lv_obj_set_style_pad_all(o_container, 0, 0);
lv_obj_set_size(o_container, lv_pct(100), lv_pct(100)); lv_obj_set_size(o_container, lv_pct(100), lv_pct(100));
lv_obj_set_scrollbar_mode(o_container, LV_SCROLLBAR_MODE_OFF);
// Settings button // Settings button
lv_obj_t *b_cfg = lv_button_create(lv_screen_active()); lv_obj_t *b_cfg = lv_button_create(lv_screen_active());
@@ -280,7 +751,5 @@ void val_lvgl_ui(lv_display_t *disp) {
lv_label_set_text(l_cfg, LV_SYMBOL_SETTINGS); lv_label_set_text(l_cfg, LV_SYMBOL_SETTINGS);
lv_obj_center(l_cfg); lv_obj_center(l_cfg);
// val_ui_none(); val_ui_none();
// val_ui_menu(true);
val_ui_idle();
} }

View File

@@ -2,6 +2,21 @@
#include "lvgl.h" #include "lvgl.h"
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_40)
LV_FONT_DECLARE(lv_font_tungsten_180) LV_FONT_DECLARE(lv_font_tungsten_180)
@@ -15,5 +30,12 @@ bool val_ui_state_ready();
void val_ui_none(); void val_ui_none();
void val_ui_menu(bool was_idle); void val_ui_menu(bool was_idle);
void val_ui_idle(); void val_ui_idle();
void val_ui_queue_start(bool ms_not_comp);
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); void val_lvgl_ui(lv_display_t *disp);

View File

@@ -8,6 +8,7 @@
#include "common.h" #include "common.h"
#include "usb.h" #include "usb.h"
#include "lcd.h" #include "lcd.h"
#include "ui.h"
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_DESC_LEN) #define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_DESC_LEN)
@@ -77,24 +78,100 @@ uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) {
// Invoked when received GET_REPORT control request // Invoked when received GET_REPORT control request
// Application must fill buffer report's content and return its length. // Application must fill buffer report's content and return its length.
// Return zero will cause the stack to STALL request // Return zero will cause the stack to STALL request
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) { uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buf, uint16_t reqlen) {
(void) instance; (void) instance;
(void) report_id; if (report_id != 0 || reqlen < 1) {
(void) report_type;
(void) buffer;
(void) reqlen;
return 0; return 0;
}
// ESP_LOGI(TAG, "Got %hu bytes report %hhu", reqlen, report_id);
// for (uint16_t i = 0; i < reqlen; i++) {
// ESP_LOGI(TAG, "b: %02hhx", buf[i]);
// }
buf[0] = val_ui_state_ready();
return 1;
} }
// Invoked when received SET_REPORT control request or // Invoked when received SET_REPORT control request or
// received data on OUT endpoint ( Report ID = 0, Type = 0 ) // received data on OUT endpoint ( Report ID = 0, Type = 0 )
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) { void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buf, uint16_t bufsize) {
assert(report_id == 0 && report_type == HID_REPORT_TYPE_OUTPUT); (void) instance;
ESP_LOGI(TAG, "Got %hu bytes report %hhu", bufsize, report_id); assert(report_id == 0 && report_type == HID_REPORT_TYPE_OUTPUT && bufsize >= 1);
for (uint16_t i = 0; i < bufsize; i++) { if (!val_lvgl_lock(-1)) {
ESP_LOGI(TAG, "b: %02hhx", buffer[i]); ESP_LOGE(TAG, "Failed to grab LVGL lock");
return;
} }
if (buf[0] > ST_GAME_OVER) {
ESP_LOGW(TAG, "Unknown state %hhu", buf[0]);
goto ret;
}
if (!val_ui_state_ready()) {
goto ret;
}
switch (buf[0]) {
case ST_NONE:
val_ui_none();
break;
case ST_MENU:
if (bufsize < 2) {
ESP_LOGE(TAG, "Invalid ST_MENU command");
goto ret;
}
val_ui_menu((bool)buf[1]);
break;
case ST_IDLE:
val_ui_idle();
break;
case ST_QUEUE_START:
if (bufsize < 2) {
ESP_LOGE(TAG, "Invalid ST_QUEUE_START command");
goto ret;
}
val_ui_queue_start((bool)buf[1]);
break;
case ST_MATCH_FOUND:
if (bufsize < 2) {
ESP_LOGE(TAG, "Invalid ST_MATCH_FOUND command");
goto ret;
}
val_ui_match_found((bool)buf[1]);
break;
case ST_PREGAME:
if (bufsize < 2) {
ESP_LOGE(TAG, "Invalid ST_PREGAME command");
goto ret;
}
val_ui_pregame((bool)buf[1]);
break;
case ST_GAME_GENERIC:
if (bufsize < 2 || buf[1] > VAL_EXT_GAMEMODES_SIZE) {
ESP_LOGE(TAG, "Invalid ST_PREGAME command");
goto ret;
}
val_ui_game_generic(val_ext_gamemodes[buf[1]]);
break;
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");
goto ret;
}
val_ui_game_over((bool)buf[1]);
break;
}
ret:
val_lvgl_unlock();
} }
void val_usb_init(void) { void val_usb_init(void) {

View File

@@ -5,4 +5,17 @@
#define EPNUM_HID 0x01 #define EPNUM_HID 0x01
#define USB_EP_BUFSIZE 64 #define USB_EP_BUFSIZE 64
typedef enum val_state {
ST_NONE = 0,
ST_MENU,
ST_IDLE,
ST_QUEUE_START,
ST_MATCH_FOUND,
ST_PREGAME,
ST_GAME_GENERIC,
ST_GAME_START,
ST_ROUND_START,
ST_GAME_OVER,
} val_state_t;
void val_usb_init(void); void val_usb_init(void);

View File

@@ -21,7 +21,6 @@ CONFIG_ESP_WIFI_DPP_SUPPORT=y
CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_HZ=1000
CONFIG_TINYUSB_DEBUG_LEVEL=0 CONFIG_TINYUSB_DEBUG_LEVEL=0
CONFIG_TINYUSB_HID_COUNT=1 CONFIG_TINYUSB_HID_COUNT=1
CONFIG_LV_DEF_REFR_PERIOD=24
CONFIG_LV_USE_LOG=y CONFIG_LV_USE_LOG=y
CONFIG_LV_LOG_PRINTF=y CONFIG_LV_LOG_PRINTF=y
CONFIG_LV_FONT_MONTSERRAT_24=y CONFIG_LV_FONT_MONTSERRAT_24=y