diff --git a/Cargo.lock b/Cargo.lock index 02048307..7022eade 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5435,9 +5435,9 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85f0383fadd15609306383a90e85eaed44169f931a5d2be1b42c76ceff1825e" +checksum = "eac6f67be712d12f0b41328db3137e0d0757645d8904b4cb7d51cd9c2279e847" dependencies = [ "prettyplease", "proc-macro2", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index c392bc7a..bf60ae9c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -39,7 +39,7 @@ reqwest = { version = "0.12.20", features = ["multipart"] } include_dir = "0.7.4" [build-dependencies] -tonic-build = "*" +tonic-build = "0.13.1" [dev-dependencies] mockall = { version = "0.13.1", features = ["nightly"] } diff --git a/backend/build.rs b/backend/build.rs index abc7453f..10c00571 100644 --- a/backend/build.rs +++ b/backend/build.rs @@ -46,10 +46,10 @@ fn main() { let admin = resources_dir.join("admin_ideal_ratio.png"); let timer = resources_dir.join("timer_ideal_ratio.png"); let level = resources_dir.join("level_ideal_ratio.png"); - let lie_detector_shape = resources_dir.join("lie_detector_shape_ideal_ratio.png"); + let lie_detector_new = resources_dir.join("lie_detector_new_ideal_ratio.png"); + let lie_detector_old = resources_dir.join("lie_detector_old_ideal_ratio.png"); let lie_detector_shape_prepare = resources_dir.join("lie_detector_shape_prepare_ideal_ratio.png"); - let lie_detector_violetta = resources_dir.join("lie_detector_violetta_ideal_ratio.png"); let lie_detector_violetta_face = resources_dir.join("lie_detector_violetta_face_ideal_ratio.png"); let lie_detector_violetta_prepare = @@ -159,6 +159,7 @@ fn main() { let out_dir = dir.join("src").join("grpc"); tonic_build::configure() .out_dir(out_dir) + .build_server(false) .compile_protos(&[proto_file], &[proto_dir]) .unwrap(); @@ -261,16 +262,16 @@ fn main() { println!("cargo:rustc-env=TIMER_TEMPLATE={}", timer.to_str().unwrap()); println!("cargo:rustc-env=LEVEL_TEMPLATE={}", level.to_str().unwrap()); println!( - "cargo:rustc-env=LIE_DETECTOR_SHAPE_TEMPLATE={}", - lie_detector_shape.to_str().unwrap() + "cargo:rustc-env=LIE_DETECTOR_NEW_TEMPLATE={}", + lie_detector_new.to_str().unwrap() ); println!( - "cargo:rustc-env=LIE_DETECTOR_SHAPE_PREPARE_TEMPLATE={}", - lie_detector_shape_prepare.to_str().unwrap() + "cargo:rustc-env=LIE_DETECTOR_OLD_TEMPLATE={}", + lie_detector_old.to_str().unwrap() ); println!( - "cargo:rustc-env=LIE_DETECTOR_VIOLETTA_TEMPLATE={}", - lie_detector_violetta.to_str().unwrap() + "cargo:rustc-env=LIE_DETECTOR_SHAPE_PREPARE_TEMPLATE={}", + lie_detector_shape_prepare.to_str().unwrap() ); println!( "cargo:rustc-env=LIE_DETECTOR_VIOLETTA_FACE_TEMPLATE={}", diff --git a/backend/resources/lie_detector_shape_ideal_ratio.png b/backend/resources/lie_detector_new_ideal_ratio.png similarity index 100% rename from backend/resources/lie_detector_shape_ideal_ratio.png rename to backend/resources/lie_detector_new_ideal_ratio.png diff --git a/backend/resources/lie_detector_violetta_ideal_ratio.png b/backend/resources/lie_detector_old_ideal_ratio.png similarity index 100% rename from backend/resources/lie_detector_violetta_ideal_ratio.png rename to backend/resources/lie_detector_old_ideal_ratio.png diff --git a/backend/src/detect.rs b/backend/src/detect.rs index 5ffde1d3..dd0a1768 100644 --- a/backend/src/detect.rs +++ b/backend/src/detect.rs @@ -511,7 +511,7 @@ impl Detector for DefaultDetector { } fn detect_lie_detector_shape(&self) -> Result { - detect_lie_detector_shape(self.bgr()) + detect_lie_detector_shape(self.bgr(), &self.localization) } fn detect_lie_detector_shape_preparing(&self) -> bool { @@ -519,7 +519,7 @@ impl Detector for DefaultDetector { } fn detect_lie_detector_violetta(&self) -> Result { - detect_lie_detector_violetta(self.bgr()) + detect_lie_detector_violetta(self.bgr(), &self.localization) } fn detect_lie_detector_violetta_preparing(&self) -> bool { @@ -2285,16 +2285,28 @@ fn detect_timer_visible(grayscale: &impl ToInputArray, localization: &Localizati .is_ok() } -fn detect_lie_detector_shape(bgr: &impl ToInputArray) -> Result { - static TEMPLATE: LazyLock = LazyLock::new(|| { - imgcodecs::imdecode( - include_bytes!(env!("LIE_DETECTOR_SHAPE_TEMPLATE")), - IMREAD_COLOR, - ) - .unwrap() - }); +pub static LIE_DETECTOR_TRANSPARENT_SHAPE_TEMPLATE: LazyLock = LazyLock::new(|| { + imgcodecs::imdecode( + include_bytes!(env!("LIE_DETECTOR_NEW_TEMPLATE")), + IMREAD_COLOR, + ) + .unwrap() +}); - detect_template(bgr, &*TEMPLATE, Point::default(), 0.6) +fn detect_lie_detector_shape(bgr: &impl ToInputArray, localization: &Localization) -> Result { + let template = localization + .lie_detector_new_base64 + .as_ref() + .and_then(|base64| to_mat_from_base64(base64, false).ok()); + + detect_template( + bgr, + template + .as_ref() + .unwrap_or(&*LIE_DETECTOR_TRANSPARENT_SHAPE_TEMPLATE), + Point::default(), + 0.6, + ) } fn detect_lie_detector_shape_preparing(bgr: &impl ToInputArray) -> Result { @@ -2309,16 +2321,31 @@ fn detect_lie_detector_shape_preparing(bgr: &impl ToInputArray) -> Result detect_template(bgr, &*TEMPLATE, Point::default(), 0.6) } -fn detect_lie_detector_violetta(bgr: &impl ToInputArray) -> Result { - static TEMPLATE: LazyLock = LazyLock::new(|| { - imgcodecs::imdecode( - include_bytes!(env!("LIE_DETECTOR_VIOLETTA_TEMPLATE")), - IMREAD_COLOR, - ) - .unwrap() - }); +pub static LIE_DETECTOR_VIOLETTA_TEMPLATE: LazyLock = LazyLock::new(|| { + imgcodecs::imdecode( + include_bytes!(env!("LIE_DETECTOR_OLD_TEMPLATE")), + IMREAD_COLOR, + ) + .unwrap() +}); - detect_template(bgr, &*TEMPLATE, Point::default(), 0.6) +fn detect_lie_detector_violetta( + bgr: &impl ToInputArray, + localization: &Localization, +) -> Result { + let template = localization + .lie_detector_old_base64 + .as_ref() + .and_then(|base64| to_mat_from_base64(base64, false).ok()); + + detect_template( + bgr, + template + .as_ref() + .unwrap_or(&*LIE_DETECTOR_VIOLETTA_TEMPLATE), + Point::default(), + 0.6, + ) } #[allow(unused)] diff --git a/backend/src/ecs.rs b/backend/src/ecs.rs index 3b2f7dcb..ad9fdada 100644 --- a/backend/src/ecs.rs +++ b/backend/src/ecs.rs @@ -1,7 +1,11 @@ -#[cfg(test)] -use std::rc::Rc; use std::sync::Arc; +#[cfg(debug_assertions)] +use opencv::{ + core::Size, + videoio::{VideoWriter, VideoWriterTrait}, +}; + use crate::services::Event; #[cfg(test)] use crate::{Settings, bridge::MockInput, detect::MockDetector}; @@ -11,28 +15,48 @@ use crate::{ skill::SkillEntities, }; #[cfg(debug_assertions)] -use crate::{debug::save_rune_for_training, solvers::SolvedArrow}; +use crate::{debug::save_rune_for_training, run::FPS, solvers::SolvedArrow, utils::DatasetDir}; + +#[derive(Debug)] +#[cfg(debug_assertions)] +pub struct RecordingHandle { + writer: VideoWriter, +} + +impl RecordingHandle { + pub fn write(&mut self, detector: &dyn Detector) { + self.writer.write(&detector.mat()).unwrap() + } +} #[derive(Debug, Default)] #[cfg(debug_assertions)] pub struct Debug { - auto_save: bool, + pub auto_save_rune: bool, + pub auto_record_lie_detector: bool, last_rune_detector: Option>, last_rune_result: Option<[SolvedArrow; 4]>, } #[cfg(debug_assertions)] impl Debug { - pub fn auto_save_rune(&self) -> bool { - self.auto_save - } - - pub fn set_auto_save_rune(&mut self, auto_save: bool) { - self.auto_save = auto_save + pub fn new_recording(&self, frame_size: Size) -> RecordingHandle { + use rand::distr::SampleString; + use rand_distr::Alphanumeric; + + let id = Alphanumeric.sample_string(&mut rand::rng(), 8); + let file = DatasetDir::Recordings + .to_folder() + .join(format!("ld_{id}.mp4")); + let fourcc = VideoWriter::fourcc('H', 'V', 'C', '1').unwrap(); + let writer = + VideoWriter::new(file.to_str().unwrap(), fourcc, FPS as f64, frame_size, true).unwrap(); + + RecordingHandle { writer } } pub fn save_last_rune_result(&self) { - if !self.auto_save { + if !self.auto_save_rune { return; } @@ -76,7 +100,7 @@ pub struct Resources { impl Resources { #[cfg(test)] pub fn new(input: Option, detector: Option) -> Self { - use std::cell::RefCell; + use std::{cell::RefCell, rc::Rc}; use crate::operation::{OperationConfiguration, OperationState}; diff --git a/backend/src/grpc/input.rs b/backend/src/grpc/input.rs index 37c49694..1a0061fe 100644 --- a/backend/src/grpc/input.rs +++ b/backend/src/grpc/input.rs @@ -581,418 +581,3 @@ pub mod key_input_client { } } } -/// Generated server implementations. -pub mod key_input_server { - #![allow( - unused_variables, - dead_code, - missing_docs, - clippy::wildcard_imports, - clippy::let_unit_value, - )] - use tonic::codegen::*; - /// Generated trait containing gRPC methods that should be implemented for use with KeyInputServer. - #[async_trait] - pub trait KeyInput: std::marker::Send + std::marker::Sync + 'static { - async fn init( - &self, - request: tonic::Request, - ) -> std::result::Result, tonic::Status>; - async fn key_state( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - async fn send_mouse( - &self, - request: tonic::Request, - ) -> std::result::Result, tonic::Status>; - async fn send( - &self, - request: tonic::Request, - ) -> std::result::Result, tonic::Status>; - async fn send_up( - &self, - request: tonic::Request, - ) -> std::result::Result, tonic::Status>; - async fn send_down( - &self, - request: tonic::Request, - ) -> std::result::Result, tonic::Status>; - } - #[derive(Debug)] - pub struct KeyInputServer { - inner: Arc, - accept_compression_encodings: EnabledCompressionEncodings, - send_compression_encodings: EnabledCompressionEncodings, - max_decoding_message_size: Option, - max_encoding_message_size: Option, - } - impl KeyInputServer { - pub fn new(inner: T) -> Self { - Self::from_arc(Arc::new(inner)) - } - pub fn from_arc(inner: Arc) -> Self { - Self { - inner, - accept_compression_encodings: Default::default(), - send_compression_encodings: Default::default(), - max_decoding_message_size: None, - max_encoding_message_size: None, - } - } - pub fn with_interceptor( - inner: T, - interceptor: F, - ) -> InterceptedService - where - F: tonic::service::Interceptor, - { - InterceptedService::new(Self::new(inner), interceptor) - } - /// Enable decompressing requests with the given encoding. - #[must_use] - pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.accept_compression_encodings.enable(encoding); - self - } - /// Compress responses with the given encoding, if the client supports it. - #[must_use] - pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.send_compression_encodings.enable(encoding); - self - } - /// Limits the maximum size of a decoded message. - /// - /// Default: `4MB` - #[must_use] - pub fn max_decoding_message_size(mut self, limit: usize) -> Self { - self.max_decoding_message_size = Some(limit); - self - } - /// Limits the maximum size of an encoded message. - /// - /// Default: `usize::MAX` - #[must_use] - pub fn max_encoding_message_size(mut self, limit: usize) -> Self { - self.max_encoding_message_size = Some(limit); - self - } - } - impl tonic::codegen::Service> for KeyInputServer - where - T: KeyInput, - B: Body + std::marker::Send + 'static, - B::Error: Into + std::marker::Send + 'static, - { - type Response = http::Response; - type Error = std::convert::Infallible; - type Future = BoxFuture; - fn poll_ready( - &mut self, - _cx: &mut Context<'_>, - ) -> Poll> { - Poll::Ready(Ok(())) - } - fn call(&mut self, req: http::Request) -> Self::Future { - match req.uri().path() { - "/input.KeyInput/Init" => { - #[allow(non_camel_case_types)] - struct InitSvc(pub Arc); - impl tonic::server::UnaryService - for InitSvc { - type Response = super::KeyInitResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::init(&inner, request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = InitSvc(inner); - let codec = tonic::codec::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/input.KeyInput/KeyState" => { - #[allow(non_camel_case_types)] - struct KeyStateSvc(pub Arc); - impl tonic::server::UnaryService - for KeyStateSvc { - type Response = super::KeyStateResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::key_state(&inner, request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = KeyStateSvc(inner); - let codec = tonic::codec::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/input.KeyInput/SendMouse" => { - #[allow(non_camel_case_types)] - struct SendMouseSvc(pub Arc); - impl tonic::server::UnaryService - for SendMouseSvc { - type Response = super::MouseResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::send_mouse(&inner, request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = SendMouseSvc(inner); - let codec = tonic::codec::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/input.KeyInput/Send" => { - #[allow(non_camel_case_types)] - struct SendSvc(pub Arc); - impl tonic::server::UnaryService - for SendSvc { - type Response = super::KeyResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::send(&inner, request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = SendSvc(inner); - let codec = tonic::codec::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/input.KeyInput/SendUp" => { - #[allow(non_camel_case_types)] - struct SendUpSvc(pub Arc); - impl tonic::server::UnaryService - for SendUpSvc { - type Response = super::KeyUpResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::send_up(&inner, request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = SendUpSvc(inner); - let codec = tonic::codec::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/input.KeyInput/SendDown" => { - #[allow(non_camel_case_types)] - struct SendDownSvc(pub Arc); - impl tonic::server::UnaryService - for SendDownSvc { - type Response = super::KeyDownResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::send_down(&inner, request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = SendDownSvc(inner); - let codec = tonic::codec::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - _ => { - Box::pin(async move { - let mut response = http::Response::new( - tonic::body::Body::default(), - ); - let headers = response.headers_mut(); - headers - .insert( - tonic::Status::GRPC_STATUS, - (tonic::Code::Unimplemented as i32).into(), - ); - headers - .insert( - http::header::CONTENT_TYPE, - tonic::metadata::GRPC_CONTENT_TYPE, - ); - Ok(response) - }) - } - } - } - } - impl Clone for KeyInputServer { - fn clone(&self) -> Self { - let inner = self.inner.clone(); - Self { - inner, - accept_compression_encodings: self.accept_compression_encodings, - send_compression_encodings: self.send_compression_encodings, - max_decoding_message_size: self.max_decoding_message_size, - max_encoding_message_size: self.max_encoding_message_size, - } - } - } - /// Generated gRPC service name - pub const SERVICE_NAME: &str = "input.KeyInput"; - impl tonic::server::NamedService for KeyInputServer { - const NAME: &'static str = SERVICE_NAME; - } -} diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 41ca7498..10756b5d 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -122,6 +122,8 @@ enum Request { #[cfg(debug_assertions)] AutoSaveRune(bool), #[cfg(debug_assertions)] + AutoRecordLieDetector(bool), + #[cfg(debug_assertions)] RecordVideo(bool), #[cfg(debug_assertions)] TestSpinRune, @@ -155,6 +157,8 @@ enum Response { #[cfg(debug_assertions)] AutoSaveRune, #[cfg(debug_assertions)] + AutoRecordLieDetector, + #[cfg(debug_assertions)] RecordVideo, #[cfg(debug_assertions)] TestSpinRune, @@ -183,6 +187,8 @@ pub enum DetectionTemplate { HexaBoosterButton, HexaMaxButton, HexaConvertButton, + LieDetectorNew, + LieDetectorOld, } /// The four quads of a bound. @@ -200,6 +206,7 @@ pub enum BoundQuadrant { pub struct DebugState { pub is_recording: bool, pub is_rune_auto_saving: bool, + pub is_lie_detector_auto_recording: bool, } /// A struct for storing current's bot state information. @@ -397,6 +404,11 @@ pub async fn auto_save_rune(auto_save: bool) { send_request!(AutoSaveRune(auto_save)) } +#[cfg(debug_assertions)] +pub async fn auto_record_lie_detector(auto_record: bool) { + send_request!(AutoRecordLieDetector(auto_record)) +} + #[cfg(debug_assertions)] pub async fn record_video(start: bool) { send_request!(RecordVideo(start)) diff --git a/backend/src/models/localization.rs b/backend/src/models/localization.rs index a6a4904a..942e7d6f 100644 --- a/backend/src/models/localization.rs +++ b/backend/src/models/localization.rs @@ -23,6 +23,10 @@ pub struct Localization { pub hexa_erda_conversion_button_base64: Option, pub hexa_booster_button_base64: Option, pub hexa_max_button_base64: Option, + #[serde(default)] + pub lie_detector_new_base64: Option, + #[serde(default)] + pub lie_detector_old_base64: Option, } impl_identifiable!(Localization); diff --git a/backend/src/player/solve_shape.rs b/backend/src/player/solve_shape.rs index 901da756..029c2331 100644 --- a/backend/src/player/solve_shape.rs +++ b/backend/src/player/solve_shape.rs @@ -10,6 +10,8 @@ use log::debug; use opencv::core::{Point, Rect}; use tokio::sync::mpsc::{self, error::TryRecvError}; +#[cfg(debug_assertions)] +use crate::ecs::RecordingHandle; use crate::{ bridge::MouseKind, detect::Detector, @@ -119,12 +121,29 @@ fn update_waiting(resources: &mut Resources, solving_shape: &mut SolvingShape) { } }; + #[cfg(debug_assertions)] + let handle = if resources.debug.auto_record_lie_detector { + use opencv::core::MatTraitConst; + + let size = resources.detector().mat().size().unwrap(); + let handle = resources.debug.new_recording(size); + + Some(handle) + } else { + None + }; + let tl = title.tl() + Point::new(0, 20); let br = tl + Point::new(755, 505); let region = Rect::from_points(tl, br); - solving_shape.solving = Some(Rc::new(RefCell::new(start_solving_task(region)))); - debug!(target:"backend/player","lie detector transparent shape region: {region:?}"); + let solving = start_solving_task( + region, + #[cfg(debug_assertions)] + handle, + ); + solving_shape.solving = Some(Rc::new(RefCell::new(solving))); solving_shape.state = State::Solving(Timeout::default()); + debug!(target:"backend/player","lie detector transparent shape region: {region:?}"); } fn update_solving(resources: &mut Resources, solving_shape: &mut SolvingShape) { @@ -163,9 +182,17 @@ fn update_solving(resources: &mut Resources, solving_shape: &mut SolvingShape) { } } -fn start_solving_task(region: Rect) -> Solving { +fn start_solving_task( + region: Rect, + #[cfg(debug_assertions)] handle: Option, +) -> Solving { let (cursor_tx, cursor_rx) = mpsc::channel(1); - let (detector_tx, mut detector_rx) = mpsc::channel::>(3); + let (detector_tx, mut detector_rx) = mpsc::channel::>(2); + + #[cfg(debug_assertions)] + let (record_tx, mut record_rx) = mpsc::unbounded_channel::>(); + #[cfg(debug_assertions)] + let recording = handle.is_some(); let task = Task::spawn_blocking(move || { let mut solver = TransparentShapeSolver::default(); @@ -178,12 +205,37 @@ fn start_solving_task(region: Rect) -> Solving { TryRecvError::Disconnected => break, }, }; + if let Some(cursor) = solver.solve(&*detector, region) { let _ = cursor_tx.try_send(cursor); } + + #[cfg(debug_assertions)] + if recording { + let _ = record_tx.send(detector.clone()); + } } }); + #[cfg(debug_assertions)] + if recording { + Task::spawn_blocking(move || { + let mut handle = handle.unwrap(); + + loop { + let detector = match record_rx.try_recv() { + Ok(detector) => detector, + Err(err) => match err { + TryRecvError::Empty => continue, + TryRecvError::Disconnected => break, + }, + }; + + handle.write(detector.as_ref()) + } + }); + } + Solving { task, cursor_rx, diff --git a/backend/src/services/debug.rs b/backend/src/services/debug.rs index 8862145e..0e3a8610 100644 --- a/backend/src/services/debug.rs +++ b/backend/src/services/debug.rs @@ -63,7 +63,8 @@ impl DebugService { if self.state.is_empty() { let _ = self.state.send(DebugState { is_recording: self.writer.is_some(), - is_rune_auto_saving: resources.debug.auto_save_rune(), + is_rune_auto_saving: resources.debug.auto_save_rune, + is_lie_detector_auto_recording: resources.debug.auto_record_lie_detector, }); } } @@ -72,10 +73,6 @@ impl DebugService { self.state.subscribe() } - pub fn set_auto_save_rune(&self, resources: &mut Resources, auto_save: bool) { - resources.debug.set_auto_save_rune(auto_save); - } - pub fn record_video(&mut self, resources: &mut Resources, start: bool) { if !start { self.writer = None; diff --git a/backend/src/services/localization.rs b/backend/src/services/localization.rs index 300284aa..f07989bc 100644 --- a/backend/src/services/localization.rs +++ b/backend/src/services/localization.rs @@ -5,10 +5,11 @@ use crate::{ detect::{ CASH_SHOP_TEMPLATE, CHANGE_CHANNEL_TEMPLATE, FAMILIAR_LEVEL_BUTTON_TEMPLATE, FAMILIAR_SAVE_BUTTON_TEMPLATE, HEXA_BOOSTER_BUTTON_TEMPLATE, HEXA_CONVERT_BUTTON_TEMPLATE, - HEXA_ERDA_CONVERSION_BUTTON_TEMPLATE, HEXA_MAX_BUTTON_TEMPLATE, POPUP_CANCEL_NEW_TEMPLATE, - POPUP_CANCEL_OLD_TEMPLATE, POPUP_CONFIRM_TEMPLATE, POPUP_END_CHAT_TEMPLATE, - POPUP_NEXT_TEMPLATE, POPUP_OK_NEW_TEMPLATE, POPUP_OK_OLD_TEMPLATE, POPUP_YES_TEMPLATE, - TIMER_TEMPLATE, to_base64_from_mat, + HEXA_ERDA_CONVERSION_BUTTON_TEMPLATE, HEXA_MAX_BUTTON_TEMPLATE, + LIE_DETECTOR_TRANSPARENT_SHAPE_TEMPLATE, LIE_DETECTOR_VIOLETTA_TEMPLATE, + POPUP_CANCEL_NEW_TEMPLATE, POPUP_CANCEL_OLD_TEMPLATE, POPUP_CONFIRM_TEMPLATE, + POPUP_END_CHAT_TEMPLATE, POPUP_NEXT_TEMPLATE, POPUP_OK_NEW_TEMPLATE, POPUP_OK_OLD_TEMPLATE, + POPUP_YES_TEMPLATE, TIMER_TEMPLATE, to_base64_from_mat, }, ecs::Resources, utils::{self, DatasetDir}, @@ -57,6 +58,8 @@ impl LocalizationService for DefaultLocalizationService { DetectionTemplate::HexaBoosterButton => &HEXA_BOOSTER_BUTTON_TEMPLATE, DetectionTemplate::HexaMaxButton => &HEXA_MAX_BUTTON_TEMPLATE, DetectionTemplate::HexaConvertButton => &HEXA_CONVERT_BUTTON_TEMPLATE, + DetectionTemplate::LieDetectorNew => &LIE_DETECTOR_TRANSPARENT_SHAPE_TEMPLATE, + DetectionTemplate::LieDetectorOld => &LIE_DETECTOR_VIOLETTA_TEMPLATE, }; to_base64_from_mat(template).expect("convert successfully") diff --git a/backend/src/services/mediator.rs b/backend/src/services/mediator.rs index da870758..f198f514 100644 --- a/backend/src/services/mediator.rs +++ b/backend/src/services/mediator.rs @@ -215,10 +215,15 @@ fn handle_ui_request( Request::DebugStateReceiver => Response::DebugStateReceiver(subscribe_debug_state(context)), #[cfg(debug_assertions)] Request::AutoSaveRune(auto_save) => { - update_auto_save_rune(context, auto_save); + context.resources.debug.auto_save_rune = auto_save; Response::AutoSaveRune } #[cfg(debug_assertions)] + Request::AutoRecordLieDetector(auto_record) => { + context.resources.debug.auto_record_lie_detector = auto_record; + Response::AutoRecordLieDetector + } + #[cfg(debug_assertions)] Request::RecordVideo(start) => { record_video(context, start); Response::RecordVideo @@ -349,13 +354,6 @@ fn subscribe_debug_state(context: &mut EventContext<'_>) -> broadcast::Receiver< context.debug_service.subscribe_state() } -#[cfg(debug_assertions)] -fn update_auto_save_rune(context: &mut EventContext<'_>, auto_save: bool) { - context - .debug_service - .set_auto_save_rune(context.resources, auto_save); -} - #[cfg(debug_assertions)] fn record_video(context: &mut EventContext<'_>, start: bool) { context.debug_service.record_video(context.resources, start); diff --git a/ui/src/debug.rs b/ui/src/debug.rs index 26cd348a..d19af8df 100644 --- a/ui/src/debug.rs +++ b/ui/src/debug.rs @@ -1,6 +1,6 @@ use backend::{ - DebugState, TransparentShapeDifficulty, auto_save_rune, debug_state_receiver, record_video, - test_spin_rune, test_transparent_shape, test_violetta, + DebugState, TransparentShapeDifficulty, auto_record_lie_detector, auto_save_rune, + debug_state_receiver, record_video, test_spin_rune, test_transparent_shape, test_violetta, }; use dioxus::prelude::*; use tokio::sync::broadcast::error::RecvError; @@ -88,6 +88,19 @@ pub fn DebugScreen() -> Element { "Start auto saving rune" } } + Button { + style: ButtonStyle::Secondary, + on_click: move |_| async move { + let recording = state.peek().is_lie_detector_auto_recording; + auto_record_lie_detector(!recording).await; + }, + + if state().is_lie_detector_auto_recording { + "Stop auto record lie detector" + } else { + "Start auto record lie detector" + } + } } } } diff --git a/ui/src/localization.rs b/ui/src/localization.rs index 0656d1dc..20b8c30f 100644 --- a/ui/src/localization.rs +++ b/ui/src/localization.rs @@ -142,7 +142,7 @@ fn SectionInfo() -> Element { Data { description: "Convert button." } } tr { - Data { description: "Others", rowspan: 3 } + Data { description: "Others", rowspan: 4 } Data { description: "Detect whether change channel menu is opened." } Data { description: "Change channel text." } } @@ -154,6 +154,10 @@ fn SectionInfo() -> Element { Data { description: "Detect whether Generic/HEXA booster is in use." } Data { description: "Timer text." } } + tr { + Data { description: "Detect lie detector event." } + Data { description: "Lie detector title." } + } } } div { class: "grid grid-cols-2 gap-3 mt-3", @@ -416,6 +420,28 @@ fn SectionOthers() -> Element { }, value: localization().timer_base64, } + LocalizationTemplateInput { + label: "Lie detector (new)", + template: DetectionTemplate::LieDetectorNew, + on_value: move |image: Option>| async move { + save_localization(Localization { + lie_detector_new_base64: to_base64(image, false).await, + ..localization() + }); + }, + value: localization().lie_detector_new_base64, + } + LocalizationTemplateInput { + label: "Lie detector (old)", + template: DetectionTemplate::LieDetectorOld, + on_value: move |image: Option>| async move { + save_localization(Localization { + lie_detector_old_base64: to_base64(image, false).await, + ..localization() + }); + }, + value: localization().lie_detector_old_base64, + } } } }