/* 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 . */ use anyhow::anyhow; use clap::{Parser, Subcommand}; use game::Game; use hurrycurry_protocol::glam::{UVec2, Vec2}; use menu::Menu; use network::Network; use render::SpriteRenderer; use sdl2::{ event::Event, keyboard::{KeyboardState, Keycode}, pixels::Color, }; use std::{ str::FromStr, time::{Duration, Instant}, }; pub mod game; pub mod helper; pub mod menu; pub mod network; pub mod render; pub mod tilemap; #[derive(Debug, Parser)] pub struct Args { #[arg(short = 'r', long, default_value = "320x240")] logical_resolution: Resolution, #[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, }, } #[derive(Debug, Clone)] struct Resolution(UVec2); impl FromStr for Resolution { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let (x, y) = s.split_once("x").ok_or(anyhow!("sep missing"))?; Ok(Resolution(UVec2::new(x.parse()?, y.parse()?))) } } enum State { Ingame(Game), Menu(Menu), } fn main() { env_logger::init_from_env("LOG"); let args = Args::parse(); rustls::crypto::ring::default_provider() .install_default() .unwrap(); let sdl_context = sdl2::init().unwrap(); let ttf_context = sdl2::ttf::init().unwrap(); let video_subsystem = sdl_context.video().unwrap(); let window = video_subsystem .window("Hurry Curry! Light Client", 1280, 720) .position_centered() .resizable() .build() .map_err(|e| e.to_string()) .unwrap(); let mut canvas = window .into_canvas() .accelerated() .build() .map_err(|e| e.to_string()) .unwrap(); let texture_creator = canvas.texture_creator(); let mut renderer = SpriteRenderer::init(&texture_creator); let mut state = match args.action.unwrap_or_default() { Action::Menu => State::Menu(Menu::new()), Action::Join { server_address } => State::Ingame(Game::new( Network::connect(&server_address).unwrap(), &renderer.atlas_layout(), )), }; // let font = ttf_context // .load_font("/usr/share/fonts/noto/NotoSansMono-Regular.ttf", 24) // .unwrap(); // let text = font.render("Hello world").blended(Color::WHITE).unwrap(); // texture_creator.create_texture_from_surface(text).unwrap(); let mut events = sdl_context.event_pump().unwrap(); let mut last_tick = Instant::now(); canvas .set_logical_size(args.logical_resolution.0.x, args.logical_resolution.0.y) .unwrap(); 'mainloop: loop { let (width, height) = canvas.logical_size(); renderer.size = Vec2::new(width as f32, height as f32); let keyboard = KeyboardState::new(&events); let dt = last_tick.elapsed().min(Duration::from_secs_f32(1. / 30.)); match &mut state { State::Ingame(x) => x.tick(dt.as_secs_f32(), &keyboard, renderer.atlas_layout()), State::Menu(x) => x.tick(dt.as_secs_f32(), &keyboard, renderer.atlas_layout()), } last_tick += dt; match &mut state { State::Ingame(x) => x.draw(&mut renderer), State::Menu(x) => x.draw(&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 { .. } | Event::KeyDown { keycode: Option::Some(Keycode::Escape), .. } => break 'mainloop, _ => {} } } } }