library

Online co-op book organization video game
git clone https://git.bradleygannon.com/library.git
Log | Files | Refs

run.rs (7990B)


      1 use std::{
      2     collections::VecDeque,
      3     net::SocketAddr,
      4     sync::mpsc::channel,
      5     thread,
      6     time::{Duration, Instant, SystemTime},
      7 };
      8 
      9 use common::game::{Action, Response, state::GameState};
     10 use macroquad::{
     11     prelude::*,
     12     ui::{
     13         hash, root_ui,
     14         widgets::{Button, InputText, Label},
     15     },
     16 };
     17 use serde::{Deserialize, Serialize};
     18 
     19 use crate::{
     20     ClientState,
     21     input::{
     22         handle_drop_book_input, handle_movement_input, handle_pick_up_book_input,
     23         handle_put_book_on_shelf_input, handle_take_book_off_shelf_input,
     24     },
     25     net::handle_connection,
     26     render::Camera,
     27     state::ViewMode,
     28 };
     29 
     30 const CONFY_APP_NAME: &str = "library-game";
     31 const CONFY_CONFIG_NAME: &str = "config";
     32 
     33 impl Default for ClientState {
     34     fn default() -> Self {
     35         Self {
     36             mode: ViewMode::default(),
     37             camera: Camera::default(),
     38             game: GameState::default(),
     39             old_game: GameState::default(),
     40             interpolated_game: GameState::default(),
     41             local_player_id: None,
     42             sequence_number: 0,
     43             unresponded_actions: VecDeque::new(),
     44             interpolation_basis: SystemTime::now(),
     45             debug_view: cfg!(debug_assertions),
     46             t0: Instant::now(),
     47             t1: Instant::now(),
     48             dt: Duration::ZERO,
     49             tx: None,
     50             rx: None,
     51             config: confy::load(CONFY_APP_NAME, Some(CONFY_CONFIG_NAME)).unwrap_or_default(),
     52             menu_error: String::new(),
     53             first_frame: true,
     54             selected_inventory_book: 0,
     55         }
     56     }
     57 }
     58 
     59 #[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
     60 pub(crate) struct Config {
     61     player_name: String,
     62     server: String,
     63 }
     64 
     65 impl Config {
     66     pub(crate) fn player_name(&self) -> String {
     67         self.player_name.clone()
     68     }
     69 }
     70 
     71 impl ClientState {
     72     pub(crate) fn run_menu_frame(&mut self) {
     73         clear_background(MAGENTA);
     74         const ELEMENT_SIZE: Vec2 = Vec2::new(420., 24.);
     75         const NUM_ELEMENTS: usize = 4;
     76         const MENU_SIZE: Vec2 = Vec2::new(ELEMENT_SIZE.x, ELEMENT_SIZE.y * NUM_ELEMENTS as f32);
     77         const ELEMENT_PITCH: Vec2 = Vec2::new(0., ELEMENT_SIZE.y);
     78         let screen_size = vec2(screen_width(), screen_height());
     79         let menu_top_left = screen_size / 2. - MENU_SIZE / 2.;
     80         let mut ui = root_ui();
     81         let name_input_token = hash!();
     82         InputText::new(name_input_token)
     83             .label("name")
     84             .position(menu_top_left)
     85             .size(ELEMENT_SIZE)
     86             .margin(Vec2::ZERO)
     87             .ui(&mut ui, &mut self.config.player_name);
     88         InputText::new(hash!())
     89             .label("server")
     90             .position(menu_top_left)
     91             .size(ELEMENT_SIZE)
     92             .margin(Vec2::ZERO)
     93             .ui(&mut ui, &mut self.config.server);
     94         let connect_button_clicked = Button::new("connect")
     95             .position(menu_top_left + ELEMENT_PITCH * 2.)
     96             .size(ELEMENT_SIZE)
     97             .ui(&mut ui);
     98         let addr = if connect_button_clicked || is_key_pressed(KeyCode::Enter) {
     99             if let Ok(addr) = self.config.server.parse::<SocketAddr>() {
    100                 Some(addr)
    101             } else if self.config.player_name.is_empty() {
    102                 self.menu_error = "enter your name pls".to_string();
    103                 None
    104             } else {
    105                 self.menu_error = "invalid server :-\\".to_string();
    106                 None
    107             }
    108         } else {
    109             None
    110         };
    111         Label::new(self.menu_error.as_str())
    112             .position(menu_top_left + ELEMENT_PITCH * 3.)
    113             .size(ELEMENT_SIZE)
    114             .ui(&mut ui);
    115         if self.first_frame {
    116             ui.set_input_focus(name_input_token);
    117             self.first_frame = false;
    118         }
    119         if let Some(addr) = addr {
    120             let _ = confy::store(CONFY_APP_NAME, Some(CONFY_CONFIG_NAME), self.config.clone());
    121             let (client_to_server_tx, client_to_server_rx) = channel();
    122             let (server_to_client_tx, server_to_client_rx) = channel();
    123             thread::spawn(move || {
    124                 handle_connection(addr, server_to_client_tx, client_to_server_rx)
    125             });
    126             self.tx = Some(client_to_server_tx);
    127             self.rx = Some(server_to_client_rx);
    128             self.mode = ViewMode::Game;
    129         }
    130     }
    131     pub(crate) fn run_game_frame(&mut self) {
    132         // Update frame timer so we move the player at the right speed
    133         self.tick();
    134 
    135         // Process any new responses from the server
    136         let mut response_count: u8 = 0;
    137         let mut latest_sequence_number = 0;
    138         if let Some(ref rx) = self.rx {
    139             for response in rx.try_iter().collect::<Vec<Response>>() {
    140                 response_count = response_count.saturating_add(1);
    141                 // If this is the first response that we're processing during this tick,
    142                 // then we need to copy the current game state so we can interpolate against
    143                 // it below
    144                 if response_count == 1 {
    145                     self.advance_game_state();
    146                 }
    147                 self.handle_response(&response);
    148                 latest_sequence_number = latest_sequence_number.max(response.sequence_number());
    149             }
    150         }
    151 
    152         // If there were any server responses, then do server reconciliation and client
    153         // prediction
    154         if response_count > 0 {
    155             self.reconcile(latest_sequence_number);
    156             self.predict();
    157         }
    158 
    159         if let Some(id) = self.local_player_id
    160             && let Some(local_player) = self.game.get_player(&id).cloned()
    161         {
    162             // Collect and send actions based on user input this frame
    163             let mut new_actions = [None, None, None, None, None];
    164 
    165             new_actions[0] = handle_movement_input(&local_player, &self.dt);
    166             new_actions[1] =
    167                 handle_pick_up_book_input(&local_player, &self.camera, self.game.floor_books());
    168             new_actions[2] = handle_drop_book_input(&local_player, self.selected_inventory_book);
    169             new_actions[3] = handle_put_book_on_shelf_input(
    170                 self.game.bookshelves(),
    171                 &self.camera,
    172                 &local_player,
    173                 self.selected_inventory_book,
    174             );
    175             new_actions[4] = handle_take_book_off_shelf_input(
    176                 self.game.bookshelves(),
    177                 &self.camera,
    178                 &local_player,
    179             );
    180 
    181             for new_action in new_actions.iter().filter_map(|na| na.clone()) {
    182                 let action = Action::new(self.sequence_number, id, new_action);
    183                 self.game.apply_action(&action);
    184                 self.send_action(&action);
    185             }
    186 
    187             // Center local player on the screen
    188             self.follow_local_player();
    189 
    190             // Entity interpolation
    191             self.interpolated_game = self.interpolate();
    192 
    193             // Toggle debug view if requested
    194             if is_key_pressed(KeyCode::F3) {
    195                 self.debug_view = !self.debug_view;
    196             }
    197 
    198             // Set selected book
    199             let books_count = local_player.books().len();
    200             if self.selected_inventory_book > books_count.saturating_sub(1) {
    201                 self.selected_inventory_book = books_count.saturating_sub(1);
    202             }
    203             if is_key_pressed(KeyCode::Key1) {
    204                 self.selected_inventory_book = 0;
    205             } else if is_key_pressed(KeyCode::Key2) {
    206                 self.selected_inventory_book = 1;
    207             } else if is_key_pressed(KeyCode::Key3) {
    208                 self.selected_inventory_book = 2;
    209             } else if is_key_pressed(KeyCode::Key4) {
    210                 self.selected_inventory_book = 3;
    211             } else if is_key_pressed(KeyCode::Key5) {
    212                 self.selected_inventory_book = 4;
    213             }
    214 
    215             self.draw_frame();
    216         }
    217     }
    218 }