Compare commits
15 Commits
76d8557f36
...
master
Author | SHA1 | Date | |
---|---|---|---|
522fc7c8c4 | |||
f62f2414b0 | |||
f501dc046e | |||
244a340764 | |||
9543851867 | |||
1caf43c6de | |||
d9c063cba7 | |||
8adb89ef2b | |||
ea5f1a7902 | |||
18831c3e0e | |||
4d1074e60c | |||
514198d136 | |||
277eb4ee3b | |||
6d0db71305 | |||
4fa183ab2d |
@@ -1,6 +1,7 @@
|
||||
import configparser
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
import infi.systray
|
||||
@@ -42,12 +43,16 @@ def main():
|
||||
|
||||
conf = configparser.ConfigParser()
|
||||
conf.read_dict({
|
||||
'general': {'log_level': 'info'},
|
||||
'general': {
|
||||
'log_level': 'info',
|
||||
'dummy_impl': False,
|
||||
},
|
||||
'valorant': {'player_uuid': ''},
|
||||
})
|
||||
conf.read(CONFIG_FILE)
|
||||
with open(CONFIG_FILE, 'w') as f:
|
||||
conf.write(f)
|
||||
use_dummy_impl = conf.getboolean('general', 'dummy_impl')
|
||||
|
||||
log_level = parse_log_level(conf['general']['log_level'])
|
||||
logging.basicConfig(
|
||||
@@ -59,20 +64,28 @@ def main():
|
||||
log.error(f'No player UUID set, exiting...')
|
||||
sys.exit(1)
|
||||
|
||||
val_handler = valconomy.ValconomyHandler()
|
||||
if use_dummy_impl:
|
||||
val_handler = valconomy.ValconomyHandler()
|
||||
else:
|
||||
val_handler = valconomy.HIDValconomyHandler()
|
||||
val_handler.start()
|
||||
val_sm = valconomy.ValconomyStateMachine(conf['valorant']['player_uuid'], val_handler)
|
||||
|
||||
val_client = valconomy.ValorantLocalClient(val_sm.handle_presence)
|
||||
def do_quit(tray: infi.systray.SysTrayIcon):
|
||||
def do_quit(*args):
|
||||
log.info('Shutting down')
|
||||
val_client.stop()
|
||||
if not use_dummy_impl:
|
||||
val_handler.stop()
|
||||
|
||||
with infi.systray.SysTrayIcon(
|
||||
data_file('icon.ico'), 'Valconomy', on_quit=do_quit,
|
||||
menu_options=(
|
||||
('Open data directory', None, do_open_datadir),
|
||||
)) as tray:
|
||||
val_client.run()
|
||||
signal.signal(signal.SIGINT, do_quit)
|
||||
signal.signal(signal.SIGTERM, do_quit)
|
||||
val_client.run()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@@ -1,17 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
import hid
|
||||
import logging
|
||||
|
||||
USB_VID = 0x6969
|
||||
USB_PID = 0x0004
|
||||
import valconomy
|
||||
from valconomy import ValorantPlayerInfo, RiotPlayerInfo, EconomyDecision
|
||||
|
||||
def main():
|
||||
hid_handle = hid.device()
|
||||
hid_handle.open(vendor_id=USB_VID, product_id=USB_PID)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s %(name)s %(levelname)s %(message)s', level=logging.INFO)
|
||||
|
||||
h = valconomy.HIDValconomyHandler()
|
||||
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:
|
||||
hid_handle.close()
|
||||
h.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@@ -81,7 +81,8 @@ class TestSm:
|
||||
('round_start', True, 2, 0, EconomyDecision.BONUS),
|
||||
('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, 11, EconomyDecision.SAVE),
|
||||
('round_start', False, 2, 12, EconomyDecision.BUY),
|
||||
@@ -112,7 +113,8 @@ class TestSm:
|
||||
('round_start', True, 1, 0, EconomyDecision.BUY),
|
||||
('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, 12, EconomyDecision.BUY),
|
||||
] + [
|
||||
@@ -133,8 +135,10 @@ class TestSm:
|
||||
|
||||
self.do(queue_type='swiftplay', game_state='PREGAME')
|
||||
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)
|
||||
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')
|
||||
|
||||
assert self.mock.seq == [
|
||||
@@ -144,9 +148,15 @@ class TestSm:
|
||||
('pregame', False),
|
||||
'game_start',
|
||||
('round_start', None, 0, 0, EconomyDecision.BUY),
|
||||
] + [
|
||||
('round_start', False, 0, 1 + i, EconomyDecision.BUY) for i in range(4)] + [
|
||||
('game_over', False),
|
||||
('round_start', False, 0, 1, EconomyDecision.BUY),
|
||||
('round_start', False, 0, 2, EconomyDecision.BUY),
|
||||
('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):
|
||||
|
@@ -5,6 +5,9 @@ import json
|
||||
import logging
|
||||
from pprint import pprint
|
||||
import os
|
||||
import queue
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
from typing import Callable
|
||||
|
||||
@@ -45,16 +48,20 @@ class RiotPlayerInfo:
|
||||
|
||||
valorant: ValorantPlayerInfo = None
|
||||
|
||||
@classmethod
|
||||
def dummy(cls, **kwargs):
|
||||
return cls('00000000-0000-0000-0000-000000000000', 'Player', 'gamer', 'dnd', **kwargs)
|
||||
|
||||
def full_name(self) -> str:
|
||||
return f'{self.name}#{self.tag}'
|
||||
|
||||
class ValorantLocalClient:
|
||||
lockfile_path = os.path.join(os.environ['LOCALAPPDATA'], r'Riot Games\Riot Client\Config\lockfile')
|
||||
poll_interval = 0.5
|
||||
|
||||
def __init__(self, callback: Callable[[RiotPlayerInfo, bool], None]):
|
||||
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.running = True
|
||||
self.players = {}
|
||||
@@ -228,7 +235,143 @@ class ValconomyHandler:
|
||||
log.info('Hard luck...')
|
||||
|
||||
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):
|
||||
NONE = 0
|
||||
@@ -265,17 +408,16 @@ class ValconomyStateMachine:
|
||||
won_last = True if p.valorant.score > self.score else False
|
||||
self.round_history.append(won_last)
|
||||
|
||||
over = False
|
||||
if p.valorant.queue_type == 'swiftplay':
|
||||
if p.valorant.score == 5 or p.valorant.enemy_score == 5:
|
||||
# Game is over
|
||||
return
|
||||
over = True
|
||||
|
||||
eco = EconomyDecision.BUY
|
||||
else:
|
||||
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:
|
||||
# Match is over
|
||||
return
|
||||
over = True
|
||||
|
||||
eco = EconomyDecision.MATCH_TEAM
|
||||
rounds_played = p.valorant.score + p.valorant.enemy_score
|
||||
@@ -300,8 +442,12 @@ class ValconomyStateMachine:
|
||||
elif rounds_played >= 24:
|
||||
# Sudden death or overtime (buy either way)
|
||||
eco = EconomyDecision.BUY
|
||||
elif rounds_played == 11:
|
||||
# Last round of half
|
||||
eco = EconomyDecision.BUY
|
||||
|
||||
self.handler.round_start(p, won_last, eco)
|
||||
if not over:
|
||||
self.handler.round_start(p, won_last, eco)
|
||||
self.score = p.valorant.score
|
||||
self.score_enemy = p.valorant.enemy_score
|
||||
self.last = GameState.IN_GAME
|
||||
|
1
firmware/.gitignore
vendored
1
firmware/.gitignore
vendored
@@ -7,3 +7,4 @@ sdkconfig.old
|
||||
/main/img/*.c
|
||||
/assets/moon.png
|
||||
/assets/star.png
|
||||
/assets/sleep.png
|
||||
|
BIN
firmware/assets/sleep_orig.png
Normal file
BIN
firmware/assets/sleep_orig.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
@@ -27,8 +27,9 @@
|
||||
done
|
||||
'';
|
||||
gen-imgs.exec = ''
|
||||
magick assets/moon_orig.png -resize 180x180 assets/moon.png
|
||||
magick assets/star_orig.png -resize 180x180 assets/star.png
|
||||
for e in moon star sleep; do
|
||||
magick assets/''${e}_orig.png -resize 120x120 assets/$e.png
|
||||
done
|
||||
|
||||
${genImgsPy}/bin/python gen-imgs.py
|
||||
'';
|
||||
|
@@ -11,7 +11,7 @@ def rgb2h(r, g, b):
|
||||
def bytes_line(bs):
|
||||
return ' ' + ' '.join(map(lambda i: f'{i:#x},', bs)) + '\n'
|
||||
|
||||
imgs = ['bg.png', 'moon.png', 'star.png']
|
||||
imgs = ['bg.png', 'moon.png', 'star.png', 'sleep.png']
|
||||
for fname in imgs:
|
||||
alpha = []
|
||||
with Image.open(os.path.join('assets/', fname)) as img:
|
||||
|
@@ -1,3 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS "valconomy.c" "ui.c" "lcd.c" "usb.c" "font/tungsten_40.c" "font/tungsten_180.c" "img/bg.c" "img/moon.c" "img/star.c"
|
||||
SRCS
|
||||
"font/tungsten_40.c" "font/tungsten_180.c"
|
||||
"img/bg.c" "img/moon.c" "img/star.c" "img/sleep.c"
|
||||
"valconomy.c" "ui.c" "lcd.c" "usb.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
@@ -2,17 +2,43 @@
|
||||
|
||||
#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 *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;
|
||||
static lv_color_t color_text_won, color_text_lost;
|
||||
|
||||
static lv_style_t s_hero, s_subtitle;
|
||||
|
||||
static lv_obj_t *o_container = 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 bool state_ready = true;
|
||||
|
||||
static const void* imgfont_get_path(
|
||||
const lv_font_t *font, uint32_t unicode, uint32_t unicode_next, int32_t *offset_y, void *user_data) {
|
||||
@@ -26,6 +52,8 @@ static const void* imgfont_get_path(
|
||||
return &ui_img_moon;
|
||||
case 0x2B50:
|
||||
return &ui_img_star;
|
||||
case 0x1F634:
|
||||
return &ui_img_sleep;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
@@ -33,16 +61,23 @@ static const void* imgfont_get_path(
|
||||
|
||||
static void b_cfg_cb(lv_event_t *e) {
|
||||
lv_obj_t *box = lv_msgbox_create(NULL);
|
||||
lv_msgbox_add_title(box, "Hello");
|
||||
lv_msgbox_add_text(box, "test message");
|
||||
lv_msgbox_add_title(box, "Settings");
|
||||
lv_msgbox_add_text(box, "Sorry, there aren't any right now.");
|
||||
lv_msgbox_add_close_button(box);
|
||||
}
|
||||
|
||||
static void setup_next_state() {
|
||||
assert(state_ready);
|
||||
state_ready = false;
|
||||
|
||||
if (at_active) {
|
||||
lv_anim_timeline_delete(at_active);
|
||||
at_active = NULL;
|
||||
}
|
||||
if (at_active_rep) {
|
||||
lv_anim_timeline_delete(at_active_rep);
|
||||
at_active_rep = NULL;
|
||||
}
|
||||
if (o_active) {
|
||||
lv_obj_delete(o_active);
|
||||
o_active = NULL;
|
||||
@@ -53,27 +88,42 @@ static void setup_next_state() {
|
||||
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_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_rep = lv_anim_timeline_create();
|
||||
lv_anim_timeline_set_repeat_count(at_active_rep, LV_ANIM_REPEAT_INFINITE);
|
||||
}
|
||||
|
||||
bool val_ui_state_ready() {
|
||||
return state_ready;
|
||||
}
|
||||
|
||||
static void anim_state_ready_cb(lv_anim_t *anim) {
|
||||
lv_anim_timeline_start(at_active_rep);
|
||||
state_ready = true;
|
||||
}
|
||||
static void anim_x_cb(void *var, int32_t v) {
|
||||
lv_obj_set_x(var, v);
|
||||
}
|
||||
static void anim_y_cb(void *var, int32_t v) {
|
||||
lv_obj_set_y(var, v);
|
||||
}
|
||||
static void anim_opa_cb(void *var, int32_t v) {
|
||||
lv_obj_set_style_opa(var, v, 0);
|
||||
}
|
||||
static void anim_val_time_text(lv_anim_t *anim) {
|
||||
lv_label_set_text_static(anim->var, "VAL TIME?");
|
||||
}
|
||||
|
||||
static void anim_val_time_text(lv_anim_t *anim) {
|
||||
lv_label_set_text_static(anim->var, M_VAL_TIME);
|
||||
}
|
||||
void val_ui_none() {
|
||||
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, "HELLO \U0001F319\u2B50!");
|
||||
lv_label_set_text_static(l_main, M_VAL_TIME);
|
||||
|
||||
lv_obj_t *l_subtitle = lv_label_create(o_active);
|
||||
lv_obj_add_style(l_subtitle, &s_subtitle, 0);
|
||||
@@ -82,7 +132,12 @@ void val_ui_none() {
|
||||
|
||||
lv_obj_update_layout(o_active);
|
||||
lv_obj_set_pos(
|
||||
l_subtitle, -(lv_obj_get_width(l_main) - lv_obj_get_width(l_subtitle)) / 2, lv_obj_get_height(l_main) / 2);
|
||||
l_subtitle,
|
||||
-(lv_obj_get_width(l_main) - lv_obj_get_width(l_subtitle)) / 2,
|
||||
(lv_obj_get_height(l_main) + lv_obj_get_height(l_subtitle)) / 2 + 5);
|
||||
|
||||
lv_label_set_text_static(l_main, "HELLO \U0001F319\u2B50!");
|
||||
lv_obj_update_layout(o_active);
|
||||
|
||||
// Animations
|
||||
lv_anim_t a_hello_in;
|
||||
@@ -95,14 +150,6 @@ void val_ui_none() {
|
||||
lv_anim_set_path_cb(&a_hello_in, lv_anim_path_ease_in);
|
||||
lv_anim_set_duration(&a_hello_in, 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, 0, 255);
|
||||
lv_anim_set_exec_cb(&a_sub, anim_opa_cb);
|
||||
lv_anim_set_path_cb(&a_sub, lv_anim_path_linear);
|
||||
lv_anim_set_duration(&a_sub, 750);
|
||||
|
||||
lv_anim_t a_hello_out;
|
||||
lv_anim_init(&a_hello_out);
|
||||
lv_anim_set_var(&a_hello_out, l_main);
|
||||
@@ -121,11 +168,538 @@ void val_ui_none() {
|
||||
lv_anim_set_path_cb(&a_val, lv_anim_path_linear);
|
||||
lv_anim_set_duration(&a_val, 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, 0, 255);
|
||||
lv_anim_set_exec_cb(&a_sub, anim_opa_cb);
|
||||
lv_anim_set_path_cb(&a_sub, lv_anim_path_linear);
|
||||
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_hello_in);
|
||||
lv_anim_timeline_add(at_active, 3000, &a_hello_out);
|
||||
lv_anim_timeline_add(at_active, 3250, &a_val);
|
||||
lv_anim_timeline_add(at_active, 3750, &a_sub);
|
||||
lv_anim_timeline_start(at_active);
|
||||
}
|
||||
|
||||
void val_ui_menu(bool was_idle) {
|
||||
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, "LET'S GO");
|
||||
|
||||
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, was_idle ? "WELCOME BACK!" : "GET QUEUEING!");
|
||||
|
||||
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_subtitle)) / 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_add(at_active, 750, &a_hello_out);
|
||||
lv_anim_timeline_add(at_active, 1000, &a_val);
|
||||
// lv_anim_timeline_start(at_active);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
void val_ui_idle() {
|
||||
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, "\U0001F634");
|
||||
|
||||
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, "COME BACK SOON...");
|
||||
|
||||
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, 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -133,16 +707,18 @@ void val_lvgl_ui(lv_display_t *disp) {
|
||||
color_secondary = lv_color_hex(0xf7518f);
|
||||
|
||||
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
|
||||
lv_theme_default_init(
|
||||
disp, color_primary, color_secondary,
|
||||
true, // dark theme
|
||||
font_normal);
|
||||
// lv_sysmon_hide_performance(disp);
|
||||
lv_sysmon_hide_performance(disp);
|
||||
|
||||
lv_font_t *f_hero_emoji = lv_imgfont_create(180, imgfont_get_path, NULL);
|
||||
lv_font_t *f_hero_emoji = lv_imgfont_create(120, imgfont_get_path, NULL);
|
||||
assert(f_hero_emoji);
|
||||
f_hero_emoji->fallback = &lv_font_tungsten_180;
|
||||
|
||||
@@ -164,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_pad_all(o_container, 0, 0);
|
||||
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
|
||||
lv_obj_t *b_cfg = lv_button_create(lv_screen_active());
|
||||
|
@@ -2,11 +2,40 @@
|
||||
|
||||
#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_180)
|
||||
|
||||
LV_IMAGE_DECLARE(ui_img_bg);
|
||||
LV_IMAGE_DECLARE(ui_img_moon);
|
||||
LV_IMAGE_DECLARE(ui_img_star);
|
||||
LV_IMAGE_DECLARE(ui_img_sleep);
|
||||
|
||||
bool val_ui_state_ready();
|
||||
|
||||
void val_ui_none();
|
||||
void val_ui_menu(bool was_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);
|
||||
|
@@ -8,6 +8,7 @@
|
||||
#include "common.h"
|
||||
#include "usb.h"
|
||||
#include "lcd.h"
|
||||
#include "ui.h"
|
||||
|
||||
#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
|
||||
// Application must fill buffer report's content and return its length.
|
||||
// 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) report_id;
|
||||
(void) report_type;
|
||||
(void) buffer;
|
||||
(void) reqlen;
|
||||
if (report_id != 0 || reqlen < 1) {
|
||||
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
|
||||
// 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) {
|
||||
assert(report_id == 0 && report_type == HID_REPORT_TYPE_OUTPUT);
|
||||
ESP_LOGI(TAG, "Got %hu bytes report %hhu", bufsize, report_id);
|
||||
for (uint16_t i = 0; i < bufsize; i++) {
|
||||
ESP_LOGI(TAG, "b: %02hhx", buffer[i]);
|
||||
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) {
|
||||
(void) instance;
|
||||
assert(report_id == 0 && report_type == HID_REPORT_TYPE_OUTPUT && bufsize >= 1);
|
||||
if (!val_lvgl_lock(-1)) {
|
||||
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) {
|
||||
|
@@ -5,4 +5,17 @@
|
||||
#define EPNUM_HID 0x01
|
||||
#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);
|
||||
|
@@ -21,7 +21,6 @@ CONFIG_ESP_WIFI_DPP_SUPPORT=y
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_TINYUSB_DEBUG_LEVEL=0
|
||||
CONFIG_TINYUSB_HID_COUNT=1
|
||||
CONFIG_LV_DEF_REFR_PERIOD=24
|
||||
CONFIG_LV_USE_LOG=y
|
||||
CONFIG_LV_LOG_PRINTF=y
|
||||
CONFIG_LV_FONT_MONTSERRAT_24=y
|
||||
|
Reference in New Issue
Block a user