/* Hurry Curry! - a game about cooking Copyright 2024 metamuffin This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License only. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ #![feature(map_many_mut, path_add_extension)] use anyhow::{anyhow, Result}; use clap::{Parser, Subcommand}; use config::Config; use game::Game; use hurrycurry_client_lib::network::sync::Network; use hurrycurry_protocol::glam::Vec2; use menu::main::MainMenu; use profiler::ProfilerOverlay; use render::Renderer; use sdl2::{event::Event, keyboard::KeyboardState, mouse::MouseState, pixels::Color}; use std::time::{Duration, Instant}; pub mod config; pub mod game; pub mod helper; pub mod menu; pub mod profiler; pub mod render; pub mod tilemap; pub mod ui; #[derive(Debug, Parser)] pub struct Args { #[clap(subcommand)] action: Option, } #[derive(Debug, Subcommand, Default)] pub enum Action { #[default] Menu, Join { #[arg(default_value = "ws://127.0.0.1/")] server_address: String, }, } pub enum State { Ingame(Box), MainMenu(MainMenu), Quit, } fn main() -> Result<()> { env_logger::init_from_env("LOG"); let args = Args::parse(); let mut config = Config::load()?; rustls::crypto::ring::default_provider() .install_default() .expect("failed to initialize crypto things"); let sdl_context = sdl2::init().map_err(|e| anyhow!("sdl2 init failed: {e}"))?; let video_subsystem = sdl_context .video() .map_err(|e| anyhow!("sdl2 video subsystem init failed: {e}"))?; let window = video_subsystem .window("Pixel Curry!", 1280, 720) .position_centered() .resizable() .build() .map_err(|e| anyhow!("sdl2 window creation failed: {e}"))?; let mut canvas = window .into_canvas() .accelerated() .present_vsync() .build() .map_err(|e| anyhow!("sdl2 canvas creation failed: {e}"))?; let texture_creator = canvas.texture_creator(); let mut renderer = Renderer::init(&texture_creator); let mut state = match args.action.unwrap_or_default() { Action::Menu => State::MainMenu(MainMenu::new(renderer.atlas_layout())), Action::Join { server_address } => State::Ingame(Box::new(Game::new( Network::connect(&server_address)?, &config, renderer.atlas_layout(), ))), }; let mut events = sdl_context .event_pump() .map_err(|e| anyhow!("sdl2 event pump: {e}"))?; let mut last_tick = Instant::now(); let mut profiler = ProfilerOverlay::new(); 'mainloop: loop { let (width, height) = canvas .output_size() .map_err(|_| anyhow!("cannot get canvas size"))?; renderer.size = Vec2::new(width as f32, height as f32); let keyboard = KeyboardState::new(&events); let mouse = MouseState::new(&events); let actual_dt = last_tick.elapsed(); last_tick += actual_dt; let dt = actual_dt.min(Duration::from_secs_f32(1. / 30.)); let next = match &mut state { State::Ingame(x) => x.tick(dt.as_secs_f32(), &keyboard, renderer.atlas_layout()), State::MainMenu(x) => { x.tick(dt.as_secs_f32(), &keyboard, &mouse, renderer.atlas_layout()) } State::Quit => break Ok(()), }; if let Some(next) = next { state = *next; } renderer.set_ui_view(4.); match &mut state { State::Ingame(x) => x.draw(&mut renderer), State::MainMenu(x) => x.draw(&mut renderer, &mut config), State::Quit => (), } profiler.update(&mut renderer); canvas.set_draw_color(Color::BLACK); canvas.clear(); renderer.submit(&mut canvas); canvas.present(); for event in events.poll_iter() { match event { Event::Quit { .. } => break 'mainloop Ok(()), Event::KeyUp { keycode: Some(keycode), .. } => match &mut state { State::Ingame(_) => (), State::MainMenu(menu) => menu.keyboard_event(keycode, false), _ => (), }, Event::KeyDown { keycode: Some(keycode), .. } => match &mut state { State::Ingame(_) => (), State::MainMenu(menu) => menu.keyboard_event(keycode, true), _ => (), }, Event::TextInput { text, .. } => match &mut state { State::MainMenu(menu) => menu.ui_state.text_input(text), _ => (), }, _ => {} } } } }