#![warn(clippy::pedantic)] // Be annoying, and disable specific irritating lints if needed #![deny( clippy::allow_attributes_without_reason, clippy::assertions_on_result_states )] #![warn(clippy::if_then_some_else_none)] #![allow(clippy::type_complexity, reason = "Bevy makes this a nightmare")] #![allow(clippy::needless_pass_by_value, reason = "Bevy makes this a nightmare")] #![allow( clippy::cast_precision_loss, clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "We cast ints to floats a lot" )] #![allow(clippy::missing_panics_doc, reason = "Gamedev! We panic a lot")] #![allow(clippy::too_many_arguments, reason = "Le Bevy:tm:")] #![allow( clippy::too_many_lines, reason = "With the three of us, this is impossible" )] pub mod attachment; pub mod client; pub mod client_plugins; pub mod config; pub mod ecs; #[cfg(all(not(target_arch = "wasm32"), feature = "particle_editor"))] pub mod particle_editor; pub mod particles; #[cfg(all(not(target_arch = "wasm32"), feature = "native"))] pub mod server; #[cfg(all(not(target_arch = "wasm32"), feature = "native"))] pub mod server_plugins; pub mod shared_plugins; pub mod physics; pub mod prelude; #[cfg(target_arch = "wasm32")] pub mod wasm_entrypoint; #[cfg(target_arch = "wasm32")] pub use wasm_entrypoint::*; use bevy::log::{tracing_subscriber, LogPlugin}; use crate::prelude::*; use clap::Parser; use crate::client_plugins::ClientPluginGroup; #[cfg(not(target_arch = "wasm32"))] use crate::server_plugins::ServerPluginGroup; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::process::exit; use std::str::FromStr; use std::time::Duration; use bevy::app::ScheduleRunnerPlugin; use bevy::diagnostic::FrameCountPlugin; use bevy::state::app::StatesPlugin; use bevy::time::TimePlugin; use bevy::ui::UiPlugin; use tracing_subscriber::EnvFilter; use tracing_subscriber::filter::Directive; use tracing_subscriber::util::SubscriberInitExt; use crate::shared_plugins::SharedPluginGroup; #[derive(Parser, Debug, Clone)] #[command(version, about)] enum Cli { Client { #[arg(short, long)] server: String, #[arg(long, action)] hotpatching_enabled: bool, }, #[cfg(not(target_arch = "wasm32"))] Server { #[arg(short = 'b', long)] bind: SocketAddr, #[arg(short = 'r', long)] tick_rate: f32, #[arg(short = 'C', long)] max_clients: usize, #[arg(long, action)] with_client: bool, #[arg(long, action)] hotpatching_enabled: bool }, #[cfg(all(not(target_arch = "wasm32"), feature = "particle_editor"))] ParticleEditor {}, } fn run(cli: Cli) -> AppExit { let mut app = App::new(); match cli { Cli::Client { server, hotpatching_enabled } => { if hotpatching_enabled { warn!("-+-+-+-+-+-+- Starting with hotpatching enabled -+-+-+-+-+-+-"); warn!("This can result in segfaults and inconsistent behavior! If there is weirdness, try disabling it."); warn!("-+-+-+-+-+-+- Starting with hotpatching enabled -+-+-+-+-+-+-"); } app.add_plugins( DefaultPlugins.build() .disable::() .disable::() ); app.add_plugins(ClientPluginGroup { server: Some(server) }); app.add_plugins(SharedPluginGroup); } #[cfg(not(target_arch = "wasm32"))] Cli::Server { bind, tick_rate, max_clients, hotpatching_enabled, with_client } => { if hotpatching_enabled { warn!("-+-+-+-+-+-+- Starting with hotpatching enabled -+-+-+-+-+-+-"); warn!("This can result in segfaults and inconsistent behavior! If there is weirdness, try disabling it."); warn!("-+-+-+-+-+-+- Starting with hotpatching enabled -+-+-+-+-+-+-"); } if cfg!(target_family = "wasm") { eprintln!("the server cannot run on webassembly"); exit(1); } if with_client { app.add_plugins( DefaultPlugins.build() .disable::() .disable::() ); app.add_plugins(|app: &mut App| { app.add_systems(Startup, crate::server::player::join::ls_magically_invent_player); }); } else { app .add_plugins(AssetPlugin::default()) .add_plugins(StatesPlugin) .add_plugins(TaskPoolPlugin::default()) .add_plugins(FrameCountPlugin) .add_plugins(TimePlugin) .add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f32( 1.0 / tick_rate, ))); } app.add_plugins(SharedPluginGroup); let mut pg = ServerPluginGroup { bind, tick_rate, max_clients, }.build(); if with_client { pg = pg.add_group(ClientPluginGroup { server: None }); } app.add_plugins(pg); } #[cfg(all(not(target_arch = "wasm32"), feature = "particle_editor"))] Cli::ParticleEditor {} => { app.add_plugins(crate::particle_editor::particle_editor_plugin); } } app.run() } fn main() -> AppExit { let cli = Cli::parse(); tracing_subscriber::fmt() .with_env_filter( EnvFilter::from_default_env().add_directive(Directive::from_str("naga=error").unwrap()), ) .finish() .init(); ctrlc::set_handler(|| { info!("caught ^C, ciao!"); exit(0); }).unwrap(); match cli { Cli::Client { .. } => { run(cli) }, Cli::ParticleEditor { .. } => { run(cli) }, Cli::Server { with_client, bind, hotpatching_enabled, .. } => { run(cli) /* if !with_client { run(cli) } else { warn!("-----------------------------------------"); warn!("RUNNING IN EXPERIMENTAL LISTENSERVER MODE"); warn!("-----------------------------------------"); warn!("This mode is HIGHLY EXPERIMENTAL, relies on janky threading, and may or may not work at all."); warn!("Use at your own risk. If weird things happen, try running separately."); warn!("-----------------------------------------"); warn!("RUNNING IN EXPERIMENTAL LISTENSERVER MODE"); warn!("-----------------------------------------"); let scli_clone = cli.clone(); let server_thread = std::thread::Builder::new() .name("server".to_string()) .spawn(move || { info!("starting server thread..."); run(scli_clone) }) .unwrap(); let clt_exit; info!("starting client thread..."); let is_multicast = bind.ip().is_unspecified(); let target_ip = if is_multicast { if bind.ip().is_ipv4() { IpAddr::V4(Ipv4Addr::LOCALHOST) } else { IpAddr::V6(Ipv6Addr::LOCALHOST) } } else { bind.ip() }; let target_port = bind.port(); let target_ip_str = match target_ip { IpAddr::V4(a) => a.to_string(), IpAddr::V6(a) => format!("[{a}]"), }; let target_url = format!("ws://{target_ip_str}:{target_port}"); info!("starting the client with autocalculated target server url {target_url}"); let cli2 = Cli::Client { server: target_url, hotpatching_enabled }; clt_exit = run(cli2); if let AppExit::Error(c) = clt_exit { error!("client exited with error {c}"); } info!("-------- CLIENT HAS EXITED --------"); info!(" ^C to stop the server"); let srv_exit = server_thread.join().unwrap(); match srv_exit { AppExit::Error(c) => AppExit::Error(c), _ => match clt_exit { AppExit::Error(c) => AppExit::Error(c), _ => AppExit::Success } }*/ } } }