From b2cda7b7f71288d1408a79bf25909fb97e7bfe47 Mon Sep 17 00:00:00 2001
From: Evan Pratten <ewpratten@gmail.com>
Date: Sat, 2 Oct 2021 17:31:17 -0400
Subject: [PATCH] Add discord presence updating

---
 game/Cargo.toml                     |  1 +
 game/src/context.rs                 |  7 ++-
 game/src/lib.rs                     | 40 ++++++++++++++--
 game/src/scenes/fsm_error_screen.rs | 17 ++++++-
 game/src/scenes/ingame_scene/mod.rs | 16 +++++--
 game/src/scenes/loading_screen.rs   | 14 +++++-
 game/src/scenes/main_menu_screen.rs | 30 +++++++++---
 game/src/scenes/pause_screen.rs     | 74 ++++++++++++++++++-----------
 8 files changed, 151 insertions(+), 48 deletions(-)

diff --git a/game/Cargo.toml b/game/Cargo.toml
index 27126cb..d20822c 100644
--- a/game/Cargo.toml
+++ b/game/Cargo.toml
@@ -32,6 +32,7 @@ cfg-if = "1.0"
 num-derive = "0.3"
 num = "0.4"
 tiled = { version  ="0.9.5", default-features = false }
+async-trait = "0.1.51"
 
 [dev-dependencies]
 puffin_viewer = "0.6"
diff --git a/game/src/context.rs b/game/src/context.rs
index 27fe726..a3e0ac3 100644
--- a/game/src/context.rs
+++ b/game/src/context.rs
@@ -1,4 +1,6 @@
-use std::cell::RefCell;
+use std::{cell::RefCell, sync::mpsc::Sender};
+
+use discord_sdk::activity::ActivityBuilder;
 
 use crate::{GameConfig, utilities::non_ref_raylib::HackedRaylibHandle};
 
@@ -6,7 +8,8 @@ use crate::{GameConfig, utilities::non_ref_raylib::HackedRaylibHandle};
 #[derive(Debug)]
 pub struct GameContext {
     pub renderer: RefCell<HackedRaylibHandle>,
-    pub config: GameConfig
+    pub config: GameConfig,
+    pub discord_rpc_send: Sender<Option<ActivityBuilder>>
 }
 
 // impl GameContext {
diff --git a/game/src/lib.rs b/game/src/lib.rs
index ca21477..d1ab32c 100644
--- a/game/src/lib.rs
+++ b/game/src/lib.rs
@@ -1,6 +1,7 @@
 #![feature(derive_default_enum)]
 #![feature(custom_inner_attributes)]
 #![feature(stmt_expr_attributes)]
+#![feature(async_await)]
 #![feature(c_variadic)]
 #![deny(unsafe_code)]
 #![warn(
@@ -69,7 +70,7 @@
 )]
 #![clippy::msrv = "1.57.0"]
 
-use std::cell::RefCell;
+use std::{cell::RefCell, sync::mpsc::TryRecvError};
 
 use discord_sdk::activity::ActivityBuilder;
 use raylib::prelude::*;
@@ -97,6 +98,8 @@ extern crate serde;
 extern crate approx;
 #[macro_use]
 extern crate num_derive;
+#[macro_use]
+extern crate async_trait;
 
 mod context;
 mod discord_rpc;
@@ -133,11 +136,14 @@ pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box<dyn std:
     };
     maybe_set_discord_presence(
         &discord_rpc,
-        ActivityBuilder::default().details("Testing..."),
+        ActivityBuilder::default().details("Game starting"),
     )
     .await
     .unwrap();
 
+    // Build an MPSC for the game to send rich presence data to discord
+    let (send_discord_rpc, recv_discord_rpc) = std::sync::mpsc::channel();
+
     let context;
     let raylib_thread;
     {
@@ -161,6 +167,7 @@ pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box<dyn std:
         context = Box::new(GameContext {
             renderer: RefCell::new(rl.into()),
             config: game_config.clone(),
+            discord_rpc_send: send_discord_rpc,
         });
     }
 
@@ -198,7 +205,6 @@ pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box<dyn std:
         &raylib_thread,
     )?;
 
-
     while !context.renderer.borrow().window_should_close() {
         // Profile the main game loop
         puffin::profile_scope!("main_loop");
@@ -212,13 +218,21 @@ pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box<dyn std:
         // If in dev mode, allow a debug key
         #[cfg(debug_assertions)]
         {
-            if context.renderer.borrow().is_key_pressed(KeyboardKey::KEY_F3) {
+            if context
+                .renderer
+                .borrow()
+                .is_key_pressed(KeyboardKey::KEY_F3)
+            {
                 game_config.debug_view = !game_config.debug_view;
             }
         }
 
         // Handle fullscreen shortcut
-        if context.renderer.borrow().is_key_pressed(KeyboardKey::KEY_F11) {
+        if context
+            .renderer
+            .borrow()
+            .is_key_pressed(KeyboardKey::KEY_F11)
+        {
             context.renderer.borrow_mut().toggle_fullscreen();
         }
 
@@ -273,6 +287,22 @@ pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box<dyn std:
         unsafe {
             raylib::ffi::EndDrawing();
         }
+
+        // Try to update discord
+        match recv_discord_rpc.try_recv() {
+            Ok(activity) => {
+                if let Some(activity) = activity {
+                    if let Err(e) = maybe_set_discord_presence(&discord_rpc, activity).await {
+                        error!("Failed to update discord presence: {:?}", e);
+                    }
+                }
+            }
+            Err(TryRecvError::Empty) => {}
+            Err(TryRecvError::Disconnected) => {
+                error!("Discord RPC channel disconnected");
+                continue;
+            }
+        }
     }
     Ok(())
 }
diff --git a/game/src/scenes/fsm_error_screen.rs b/game/src/scenes/fsm_error_screen.rs
index 7a57148..d1fee04 100644
--- a/game/src/scenes/fsm_error_screen.rs
+++ b/game/src/scenes/fsm_error_screen.rs
@@ -1,6 +1,7 @@
 use dirty_fsm::{Action, ActionFlag};
+use discord_sdk::activity::{ActivityBuilder, Assets};
 use raylib::{color::Color, prelude::RaylibDraw};
-use tracing::{debug, trace};
+use tracing::{debug, error, trace};
 
 use crate::{
     context::GameContext,
@@ -26,8 +27,20 @@ impl Action<Scenes, ScreenError, GameContext> for FsmErrorScreen {
         Ok(())
     }
 
-    fn on_first_run(&mut self, _context: &GameContext) -> Result<(), ScreenError> {
+    fn on_first_run(&mut self, context: &GameContext) -> Result<(), ScreenError> {
         debug!("Running FsmErrorScreen for the first time");
+
+        // Update discord
+        if let Err(e) = context.discord_rpc_send.send(Some(
+            ActivityBuilder::default()
+                .details("IT FUCKING DIED")
+                .assets(
+                    Assets::default().large("game-logo-small", Some(context.config.name.clone())),
+                ),
+        )) {
+            error!("Failed to update discord: {}", e);
+        }
+
         Ok(())
     }
 
diff --git a/game/src/scenes/ingame_scene/mod.rs b/game/src/scenes/ingame_scene/mod.rs
index 1509484..bfe505e 100644
--- a/game/src/scenes/ingame_scene/mod.rs
+++ b/game/src/scenes/ingame_scene/mod.rs
@@ -1,4 +1,5 @@
 use dirty_fsm::{Action, ActionFlag};
+use discord_sdk::activity::{ActivityBuilder, Assets};
 use raylib::prelude::*;
 
 use crate::{
@@ -13,7 +14,7 @@ use crate::{
 use self::level::Level;
 
 use super::{Scenes, ScreenError};
-use tracing::{debug, trace};
+use tracing::{debug, error, trace};
 
 mod hud;
 pub mod level;
@@ -57,7 +58,7 @@ impl Action<Scenes, ScreenError, GameContext> for InGameScreen {
         Ok(())
     }
 
-    fn on_first_run(&mut self, _context: &GameContext) -> Result<(), ScreenError> {
+    fn on_first_run(&mut self, context: &GameContext) -> Result<(), ScreenError> {
         debug!("Running InGameScreen for the first time");
 
         // Set the player to running
@@ -68,6 +69,15 @@ impl Action<Scenes, ScreenError, GameContext> for InGameScreen {
             -cur_level.platform_tex.height as f32,
         );
 
+        // Update discord
+        if let Err(e) = context.discord_rpc_send.send(Some(
+            ActivityBuilder::default().details("in game").assets(
+                Assets::default().large("game-logo-small", Some(context.config.name.clone())),
+            ),
+        )) {
+            error!("Failed to update discord: {}", e);
+        }
+
         Ok(())
     }
 
@@ -105,8 +115,6 @@ impl Action<Scenes, ScreenError, GameContext> for InGameScreen {
         } else {
             Ok(ActionFlag::Continue)
         }
-
-
     }
 
     fn on_finish(&mut self, _interrupted: bool) -> Result<(), ScreenError> {
diff --git a/game/src/scenes/loading_screen.rs b/game/src/scenes/loading_screen.rs
index 0ccc70b..01b31be 100644
--- a/game/src/scenes/loading_screen.rs
+++ b/game/src/scenes/loading_screen.rs
@@ -3,6 +3,7 @@ use std::ops::{Div, Sub};
 use cfg_if::cfg_if;
 use chrono::{DateTime, Utc};
 use dirty_fsm::{Action, ActionFlag};
+use discord_sdk::activity::{ActivityBuilder, Assets};
 use raylib::prelude::*;
 
 use crate::{GameConfig, context::GameContext, utilities::{
@@ -13,7 +14,7 @@ use crate::{GameConfig, context::GameContext, utilities::{
     }};
 
 use super::{Scenes, ScreenError};
-use tracing::{debug, info, trace};
+use tracing::{debug, info, error, trace};
 
 /// Defines how long the loading screen should be displayed.
 const LOADING_SCREEN_DURATION_SECONDS: u8 = 3;
@@ -49,9 +50,18 @@ impl Action<Scenes, ScreenError, GameContext> for LoadingScreen {
         Ok(())
     }
 
-    fn on_first_run(&mut self, _context: &GameContext) -> Result<(), ScreenError> {
+    fn on_first_run(&mut self, context: &GameContext) -> Result<(), ScreenError> {
         debug!("Running LoadingScreen for the first time");
 
+        // Update discord
+        if let Err(e) = context.discord_rpc_send.send(Some(
+            ActivityBuilder::default().details("loading...").assets(
+                Assets::default().large("game-logo-small", Some(context.config.name.clone())),
+            ),
+        )) {
+            error!("Failed to update discord: {}", e);
+        }
+
         // Keep track of when this screen is opened
         self.start_timestamp = Some(Utc::now());
 
diff --git a/game/src/scenes/main_menu_screen.rs b/game/src/scenes/main_menu_screen.rs
index cc0f68b..13c8295 100644
--- a/game/src/scenes/main_menu_screen.rs
+++ b/game/src/scenes/main_menu_screen.rs
@@ -2,19 +2,24 @@ use std::ops::{Div, Sub};
 
 use chrono::{DateTime, Utc};
 use dirty_fsm::{Action, ActionFlag};
+use discord_sdk::activity::{ActivityBuilder, Assets};
 use pkg_version::pkg_version_major;
 use raylib::prelude::*;
 
-use crate::{GameConfig, context::GameContext, utilities::{
+use crate::{
+    context::GameContext,
+    utilities::{
         datastore::{load_texture_from_internal_data, ResourceLoadError},
         game_version::get_version_string,
         math::interpolate_exp,
         non_ref_raylib::HackedRaylibHandle,
         render_layer::ScreenSpaceRender,
-    }};
+    },
+    GameConfig,
+};
 
 use super::{Scenes, ScreenError};
-use tracing::{debug, info, trace};
+use tracing::{debug, error, info, trace};
 
 #[derive(Debug)]
 pub struct MainMenuScreen {}
@@ -32,9 +37,18 @@ impl Action<Scenes, ScreenError, GameContext> for MainMenuScreen {
         Ok(())
     }
 
-    fn on_first_run(&mut self, _context: &GameContext) -> Result<(), ScreenError> {
+    fn on_first_run(&mut self, context: &GameContext) -> Result<(), ScreenError> {
         debug!("Running MainMenuScreen for the first time");
 
+        // Update discord
+        if let Err(e) = context.discord_rpc_send.send(Some(
+            ActivityBuilder::default().details("main menu").assets(
+                Assets::default().large("game-logo-small", Some(context.config.name.clone())),
+            ),
+        )) {
+            error!("Failed to update discord: {}", e);
+        }
+
         Ok(())
     }
 
@@ -47,7 +61,11 @@ impl Action<Scenes, ScreenError, GameContext> for MainMenuScreen {
         self.render_screen_space(&mut context.renderer.borrow_mut(), &context.config);
 
         // TODO: TEMP
-        if context.renderer.borrow_mut().is_key_pressed(KeyboardKey::KEY_SPACE) {
+        if context
+            .renderer
+            .borrow_mut()
+            .is_key_pressed(KeyboardKey::KEY_SPACE)
+        {
             Ok(ActionFlag::SwitchState(Scenes::InGameScene))
         } else {
             Ok(ActionFlag::Continue)
@@ -64,7 +82,7 @@ impl ScreenSpaceRender for MainMenuScreen {
     fn render_screen_space(
         &self,
         raylib: &mut crate::utilities::non_ref_raylib::HackedRaylibHandle,
-        config: &GameConfig
+        config: &GameConfig,
     ) {
         // Render the background
         raylib.clear_background(Color::BLACK);
diff --git a/game/src/scenes/pause_screen.rs b/game/src/scenes/pause_screen.rs
index e960b71..a496c5a 100644
--- a/game/src/scenes/pause_screen.rs
+++ b/game/src/scenes/pause_screen.rs
@@ -2,19 +2,24 @@ use std::ops::{Div, Sub};
 
 use chrono::{DateTime, Utc};
 use dirty_fsm::{Action, ActionFlag};
+use discord_sdk::activity::{ActivityBuilder, Assets};
 use pkg_version::pkg_version_major;
 use raylib::prelude::*;
 
-use crate::{GameConfig, context::GameContext, utilities::{
+use crate::{
+    context::GameContext,
+    utilities::{
         datastore::{load_texture_from_internal_data, ResourceLoadError},
         game_version::get_version_string,
         math::interpolate_exp,
         non_ref_raylib::HackedRaylibHandle,
         render_layer::ScreenSpaceRender,
-    }};
+    },
+    GameConfig,
+};
 
 use super::{Scenes, ScreenError};
-use tracing::{debug, info, trace};
+use tracing::{debug, error, info, trace};
 
 #[derive(Debug)]
 pub struct PauseScreen {}
@@ -32,9 +37,18 @@ impl Action<Scenes, ScreenError, GameContext> for PauseScreen {
         Ok(())
     }
 
-    fn on_first_run(&mut self, _context: &GameContext) -> Result<(), ScreenError> {
+    fn on_first_run(&mut self, context: &GameContext) -> Result<(), ScreenError> {
         debug!("Running PauseScreen for the first time");
 
+        // Update discord
+        if let Err(e) = context.discord_rpc_send.send(Some(
+            ActivityBuilder::default().details("paused").assets(
+                Assets::default().large("game-logo-small", Some(context.config.name.clone())),
+            ),
+        )) {
+            error!("Failed to update discord: {}", e);
+        }
+
         Ok(())
     }
 
@@ -49,26 +63,35 @@ impl Action<Scenes, ScreenError, GameContext> for PauseScreen {
         //Mouse Position
         let mouse_position: Vector2 = context.renderer.borrow_mut().get_mouse_position();
         //Mouse Input
-        let is_left_click = context.renderer.borrow_mut().is_mouse_button_down(MouseButton::MOUSE_LEFT_BUTTON);
-
+        let is_left_click = context
+            .renderer
+            .borrow_mut()
+            .is_mouse_button_down(MouseButton::MOUSE_LEFT_BUTTON);
 
         //"Hitboxes" for the resume and Main menu buttons
-        if is_left_click && Rectangle::new(322.0,321.0,435.0,80.0).check_collision_point_rec(mouse_position) {
+        if is_left_click
+            && Rectangle::new(322.0, 321.0, 435.0, 80.0).check_collision_point_rec(mouse_position)
+        {
             return Ok(ActionFlag::SwitchState(Scenes::InGameScene));
         }
 
-        if is_left_click && Rectangle::new(390.0,464.0,200.0,50.0).check_collision_point_rec(mouse_position) {
+        if is_left_click
+            && Rectangle::new(390.0, 464.0, 200.0, 50.0).check_collision_point_rec(mouse_position)
+        {
             return Ok(ActionFlag::SwitchState(Scenes::MainMenuScreen));
         }
 
-        if context.renderer.borrow_mut().is_key_pressed(KeyboardKey::KEY_ESCAPE) {
+        if context
+            .renderer
+            .borrow_mut()
+            .is_key_pressed(KeyboardKey::KEY_ESCAPE)
+        {
             Ok(ActionFlag::SwitchState(Scenes::InGameScene))
         } else {
             Ok(ActionFlag::Continue)
         }
     }
 
-
     fn on_finish(&mut self, _interrupted: bool) -> Result<(), ScreenError> {
         debug!("Finished PauseScreen");
         Ok(())
@@ -76,11 +99,10 @@ impl Action<Scenes, ScreenError, GameContext> for PauseScreen {
 }
 
 impl ScreenSpaceRender for PauseScreen {
-
     fn render_screen_space(
         &self,
         raylib: &mut crate::utilities::non_ref_raylib::HackedRaylibHandle,
-        config: &GameConfig
+        config: &GameConfig,
     ) {
         let screen_size = raylib.get_screen_size();
 
@@ -98,85 +120,83 @@ impl ScreenSpaceRender for PauseScreen {
             (screen_size.x as i32 / 2) - 223,
             (screen_size.y as i32 / 2) - 40,
             120,
-            Color::RED
+            Color::RED,
         );
         raylib.draw_text(
             "Paused",
             (screen_size.x as i32 / 2) - 217,
             (screen_size.y as i32 / 2) - 40,
             120,
-            Color::BLUE
+            Color::BLUE,
         );
         raylib.draw_text(
             "Paused",
             (screen_size.x as i32 / 2) - 220,
             (screen_size.y as i32 / 2) - 40,
             120,
-            Color::WHITE
+            Color::WHITE,
         );
         raylib.draw_text(
             "Click To Resume",
             (screen_size.x as i32 / 2) - 80,
             (screen_size.y as i32 / 2) + 60,
             20,
-            Color::RED
+            Color::RED,
         );
         raylib.draw_text(
             "Click To Resume",
             (screen_size.x as i32 / 2) - 80,
             (screen_size.y as i32 / 2) + 60,
             20,
-            Color::BLUE
+            Color::BLUE,
         );
         raylib.draw_text(
             "Click To Resume",
             (screen_size.x as i32 / 2) - 80,
             (screen_size.y as i32 / 2) + 60,
             20,
-            Color::WHITE
+            Color::WHITE,
         );
         raylib.draw_text(
             "Main Menu",
             (screen_size.x as i32 / 2) - 123,
             (screen_size.y as i32 / 2) + 100,
             50,
-            Color::RED
+            Color::RED,
         );
         raylib.draw_text(
             "Main Menu",
             (screen_size.x as i32 / 2) - 117,
             (screen_size.y as i32 / 2) + 100,
             50,
-            Color::BLUE
+            Color::BLUE,
         );
         raylib.draw_text(
             "Main Menu",
             (screen_size.x as i32 / 2) - 120,
             (screen_size.y as i32 / 2) + 100,
             50,
-            Color::WHITE
+            Color::WHITE,
         );
 
-        if Rectangle::new(390.0,464.0,200.0,50.0).check_collision_point_rec(mouse_position) {
+        if Rectangle::new(390.0, 464.0, 200.0, 50.0).check_collision_point_rec(mouse_position) {
             raylib.draw_text(
                 "Main Menu",
                 (screen_size.x as i32 / 2) - 120,
                 (screen_size.y as i32 / 2) + 100,
                 50,
-                Color::YELLOW
+                Color::YELLOW,
             );
         }
 
-        if Rectangle::new(322.0,321.0,435.0,80.0).check_collision_point_rec(mouse_position) {
+        if Rectangle::new(322.0, 321.0, 435.0, 80.0).check_collision_point_rec(mouse_position) {
             raylib.draw_text(
                 "Paused",
                 (screen_size.x as i32 / 2) - 220,
                 (screen_size.y as i32 / 2) - 40,
                 120,
-                Color::DARKBLUE
+                Color::DARKBLUE,
             );
         }
-
-
     }
 }