~starkingdoms/starkingdoms

eaf9c7ead6f4a56ff3e18c70e192c9d56d39e981 — core 24 days ago 37f85fa
feat: feature refactor
M Cargo.lock => Cargo.lock +65 -9
@@ 2152,6 2152,16 @@ dependencies = [
]

[[package]]
name = "built"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
dependencies = [
 "chrono",
 "git2",
]

[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3974,6 3984,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"

[[package]]
name = "git2"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110"
dependencies = [
 "bitflags 2.10.0",
 "libc",
 "libgit2-sys",
 "log",
 "url",
]

[[package]]
name = "gl_generator"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4814,6 4837,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"

[[package]]
name = "libgit2-sys"
version = "0.18.2+1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222"
dependencies = [
 "cc",
 "libc",
 "libz-sys",
 "pkg-config",
]

[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4841,6 4876,18 @@ dependencies = [
]

[[package]]
name = "libz-sys"
version = "1.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7"
dependencies = [
 "cc",
 "libc",
 "pkg-config",
 "vcpkg",
]

[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 7278,11 7325,12 @@ dependencies = [
 "bevy_common_assets",
 "bevy_egui",
 "bevy_replicon",
 "clap",
 "built",
 "console_error_panic_hook",
 "ctrlc",
 "getrandom 0.3.4",
 "ordered-float 5.1.0",
 "pico-args",
 "rand 0.9.2",
 "ron 0.12.0",
 "serde",


@@ 7290,7 7338,7 @@ dependencies = [
 "tracing-wasm",
 "url",
 "wasm-bindgen",
 "wgpu 25.0.2",
 "wgpu 26.0.1",
]

[[package]]


@@ 8204,6 8252,12 @@ dependencies = [
]

[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"

[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 8584,12 8638,14 @@ dependencies = [
 "js-sys",
 "log",
 "naga 26.0.0",
 "parking_lot",
 "portable-atomic",
 "profiling",
 "raw-window-handle",
 "smallvec",
 "static_assertions",
 "wasm-bindgen",
 "wasm-bindgen-futures",
 "web-sys",
 "wgpu-core 26.0.1",
 "wgpu-hal 26.0.6",


@@ 8621,8 8677,7 @@ dependencies = [
 "smallvec",
 "thiserror 2.0.17",
 "wgpu-core-deps-apple 25.0.0",
 "wgpu-core-deps-emscripten",
 "wgpu-core-deps-wasm 25.0.0",
 "wgpu-core-deps-emscripten 25.0.0",
 "wgpu-core-deps-windows-linux-android 25.0.0",
 "wgpu-hal 25.0.2",
 "wgpu-types 25.0.0",


@@ 8653,7 8708,8 @@ dependencies = [
 "smallvec",
 "thiserror 2.0.17",
 "wgpu-core-deps-apple 26.0.0",
 "wgpu-core-deps-wasm 26.0.0",
 "wgpu-core-deps-emscripten 26.0.0",
 "wgpu-core-deps-wasm",
 "wgpu-core-deps-windows-linux-android 26.0.0",
 "wgpu-hal 26.0.6",
 "wgpu-types 26.0.0",


@@ 8687,12 8743,12 @@ dependencies = [
]

[[package]]
name = "wgpu-core-deps-wasm"
version = "25.0.0"
name = "wgpu-core-deps-emscripten"
version = "26.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eca8809ad123f6c7f2c5e01a2c7117c4fdfd02f70bd422ee2533f69dfa98756c"
checksum = "d7670e390f416006f746b4600fdd9136455e3627f5bd763abf9a65daa216dd2d"
dependencies = [
 "wgpu-hal 25.0.2",
 "wgpu-hal 26.0.6",
]

[[package]]

M crates/unified/Cargo.toml => crates/unified/Cargo.toml +18 -18
@@ 4,8 4,6 @@ description = "A game about floating through space"
edition = "2024"
version = "0.1.0"

#[lib]
#crate-type = ["cdylib", "rlib"]

[dependencies]
bevy = { version = "0.17", default-features = false, features = [


@@ 22,11 20,10 @@ bevy = { version = "0.17", default-features = false, features = [
    "bevy_color",
    "bevy_input_focus",
    "bevy_log",
    "x11",
    "wayland",
    "multi_threaded",
    "bevy_dev_tools",
    "bevy_sprite_picking_backend",
    "bevy_mesh_picking_backend",
    "default_font",
    "png",
    "bevy_gizmos",


@@ 34,9 31,8 @@ bevy = { version = "0.17", default-features = false, features = [
    "bevy_anti_alias",
    "bevy_sprite_render",
    "bevy_ui_render",
    "webgl2"
    "zstd_rust"
] }
#bevy_rapier2d = { features = ["serde-serialize", "simd-stable"], git = "https://github.com/Deniskore/bevy_rapier", branch = "bevy-0.17" }

avian2d = { version = "0.4", default-features = false, features = [
    "2d",


@@ 54,7 50,6 @@ bevy_common_assets = { version = "0.14", features = ["toml"] }

bevy_replicon = "0.36"

clap = { version = "4", features = ["derive", "cargo"] }
url = "2"

tracing-subscriber = "0.3"


@@ 69,16 64,20 @@ aeronet_replicon = { version = "0.17", features = ["client"] }
aeronet_websocket = { version = "0.17", features = ["client"] }
aeronet_transport = "0.17"

bevy_egui = "0.38"

ordered-float = { version = "5", features = ["serde"] }
ron = "0.12"

ctrlc = { version = "3.5", optional = true }

wgpu = "*"

pico-args = "0.5"

bevy_egui = { version = "0.38", optional = true }

[build-dependencies]
built = { version = "0.8", features = ["git2", "chrono"] }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
ctrlc = { version = "3.5", optional = true }

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "0.2" }


@@ 86,15 85,16 @@ tracing-wasm = "0.2"
console_error_panic_hook = "0.1"

[features]
default = ["native", "particle_editor"]
native = [
    "bevy/file_watcher",
    "bevy/hotpatching",
    "bevy/dynamic_linking",
    "aeronet_websocket/server",
    "aeronet_replicon/server",
    "ctrlc",
    "wgpu/webgl"
    "bevy/x11",
    "bevy/wayland",
    "ctrlc"
]
wasm = ["getrandom/wasm_js"]
particle_editor = []
wasm = ["getrandom/wasm_js", "bevy/webgl2"]

particle_editor = ["bevy_egui"]
server = ["aeronet_websocket/server", "aeronet_replicon/server"]
client = []
\ No newline at end of file

A crates/unified/build.rs => crates/unified/build.rs +3 -0
@@ 0,0 1,3 @@
fn main() {
    built::write_built_file().expect("Failed to acquire build-time information");
}
\ No newline at end of file

A crates/unified/src/build_meta.rs => crates/unified/src/build_meta.rs +30 -0
@@ 0,0 1,30 @@
// Use of a mod or pub mod is not actually necessary.
pub mod built_info {
    // The file has been placed there by the build script.
    include!(concat!(env!("OUT_DIR"), "/built.rs"));
}

pub fn version_and_features_line() -> String {
    // Version, commit, and features line
    // ex: starkingdoms v0.1.0 37f85fab-dirty +native +server +client +particle_editor
    let commit = format!("{}{}", built_info::GIT_COMMIT_HASH_SHORT.unwrap_or("<unknown>"), if let Some(d) = built_info::GIT_DIRTY && d { "-dirty" } else { "" }); // TODO
    let mut line = format!("starkingdoms v{} {commit}", env!("CARGO_PKG_VERSION"));

    if cfg!(feature = "native") {
        line += " +native";
    }
    if cfg!(feature = "wasm") {
        line += " +wasm";
    }
    if cfg!(feature = "server") {
        line += " +server";
    }
    if cfg!(feature = "client") {
        line += " +client";
    }
    if cfg!(feature = "particle_editor") {
        line += " +particle_editor";
    }

    line
}
\ No newline at end of file

A crates/unified/src/cli.rs => crates/unified/src/cli.rs +128 -0
@@ 0,0 1,128 @@
use crate::build_meta::built_info::{BUILT_TIME_UTC, HOST, RUSTC_VERSION, TARGET};
use crate::build_meta::version_and_features_line;

#[cfg(not(any(feature = "client", feature = "server", feature = "particle_editor")))]
compile_error!("You need to enable one or more of client, server, particle_editor features");
#[cfg(not(any(feature = "native", feature = "wasm")))]
compile_error!("You need to enable one of native, wasm features");
#[cfg(all(feature = "native", feature = "wasm"))]
compile_error!("You cannot enable both native and wasm features");

pub enum StkArgs {
    #[cfg(feature = "client")]
    Client {
        server: String,
    },
    #[cfg(feature = "server")]
    Server {
        bind_to: std::net::SocketAddr,
        max_clients: std::num::NonZeroUsize,
        tick_rate: f32,
        #[cfg(feature = "client")]
        with_client: bool
    },
    #[cfg(feature = "particle_editor")]
    ParticleEditor
}

#[cfg(not(target_arch = "wasm32"))]
pub fn parse_args() -> StkArgs {
    let mut pargs = pico_args::Arguments::from_env();

    if pargs.contains(["-h", "--help"]) {
        print_help();
        std::process::exit(0);
    }

    if pargs.contains(["-v", "--version"]) {
        print_version();
        std::process::exit(0);
    }

    let Some(subcommand) = pargs.subcommand().unwrap() else {
        eprintln!("a subcommand is required");
        print_help();
        std::process::exit(1);
    };

    let args = match subcommand.as_str() {
        #[cfg(feature = "client")]
        "client" => {
            StkArgs::Client {
                server: pargs.value_from_str(["-s", "--server"]).unwrap(),
            }
        },
        #[cfg(feature = "server")]
        "server" => {
            StkArgs::Server {
                bind_to: pargs.value_from_str(["-b", "--bind-to"]).unwrap(),
                max_clients: pargs.value_from_str(["-c", "--max-clients"]).unwrap(),
                tick_rate: pargs.value_from_str(["-t", "--tick-rate"]).unwrap(),
                #[cfg(feature = "client")]
                with_client: pargs.contains("--with-client"),
            }
        },
        #[cfg(feature = "particle_editor")]
        "particle_editor" => {
            StkArgs::ParticleEditor
        },
        unknown => {
            eprintln!("unknown subcommand: {unknown} (is that feature enabled?)");
            eprintln!("-h, --help for help");
            std::process::exit(1);
        }
    };

    args
}

fn print_help() {
    println!("{}\n", version_and_features_line());

    println!("\
USAGE:
    starkingdoms [FLAGS] <subcommand> [<args>...]

FLAGS:
    -h, --help        Prints help information
    -v, --version     Prints version information

SUBCOMMANDS:
    ");

    if cfg!(feature = "client") {
        println!("    client            Run the client (see CLIENT for options)");
    }
    if cfg!(feature = "server") {
        println!("    server            Run the server (see SERVER for options)");
    }
    if cfg!(feature = "particle_editor") {
        println!("particle_editor   Run the particle editor");
    }

    println!("\n");

    if cfg!(feature = "client") {
        println!("\
CLIENT:
    -s, --server <URL>      WebSocket URL to connect to
        ");
    }
    if cfg!(feature = "server") {
        print!("\
SERVER:
    -b, --bind-to <IP:PORT> Socket address to bind to
    -c, --max-clients <N>   Maximum number of clients to accept
    -r, --tick-rate   <N>   Tick rate\n");
        if cfg!(feature = "client") {
            println!("    --with-client           Start a client connected to this server");
        } else {
            println!();
        }
    }
}

fn print_version() {
    println!("{}", version_and_features_line());
    println!("built on {} by {} on {} for {}", BUILT_TIME_UTC, RUSTC_VERSION, HOST, TARGET);
}
\ No newline at end of file

M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +2 -2
@@ 15,6 15,7 @@ use crate::prelude::*;
use bevy::window::PrimaryWindow;
use planet::incoming_planets::incoming_planets_plugin;
use crate::client::ship::attachment::client_attachment_plugin;
use crate::ecs::Me;

pub mod colors;
pub mod key_input;


@@ 64,8 65,7 @@ impl Plugin for ClientPlugin {
    }
}

#[derive(Component)]
pub struct Me;


fn find_me(
    mut commands: Commands,

M crates/unified/src/client/parts.rs => crates/unified/src/client/parts.rs +1 -1
@@ 1,7 1,7 @@
use std::f32::consts::PI;

use crate::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf, SnapOfJoint};
use crate::client::Me;
use crate::ecs::Me;
use crate::client::colors::GREEN;
use crate::ecs::{CursorWorldCoordinates, DragRequestEvent, Part};
use bevy::color::palettes::css::{ORANGE, PURPLE, RED, YELLOW};

M crates/unified/src/client/planet/indicators.rs => crates/unified/src/client/planet/indicators.rs +1 -1
@@ 1,4 1,4 @@
use crate::client::Me;
use crate::ecs::Me;
use crate::config::planet::Planet;
use crate::ecs::MainCamera;
use crate::prelude::*;

M crates/unified/src/client/starfield.rs => crates/unified/src/client/starfield.rs +1 -1
@@ 17,7 17,7 @@ use bevy::{
};

use crate::{
    client::Me,
    ecs::Me,
    ecs::{MainCamera, StarfieldBack, StarfieldFront, StarfieldMid},
};


M crates/unified/src/client/zoom.rs => crates/unified/src/client/zoom.rs +1 -1
@@ 4,8 4,8 @@ use bevy::{
};

use crate::{
    ecs::Me,
    client::{
        Me,
        starfield::{BACK_STARFIELD_SIZE, FRONT_STARFIELD_SIZE, MID_STARFIELD_SIZE},
    },
    ecs::{MainCamera, StarfieldBack, StarfieldFront, StarfieldMid},

M crates/unified/src/ecs.rs => crates/unified/src/ecs.rs +3 -0
@@ 85,3 85,6 @@ pub struct PlayerStorage {
    pub power_capacity: f32,
    pub power: f32,
}

#[derive(Component)]
pub struct Me;
\ No newline at end of file

M crates/unified/src/main.rs => crates/unified/src/main.rs +54 -92
@@ 20,16 20,18 @@
)]

pub mod attachment;
#[cfg(feature = "client")]
pub mod client;
#[cfg(feature = "client")]
pub mod client_plugins;
pub mod config;
pub mod ecs;
#[cfg(all(not(target_arch = "wasm32"), feature = "particle_editor"))]
#[cfg(feature = "particle_editor")]
pub mod particle_editor;
pub mod particles;
#[cfg(all(not(target_arch = "wasm32"), feature = "native"))]
#[cfg(feature = "server")]
pub mod server;
#[cfg(all(not(target_arch = "wasm32"), feature = "native"))]
#[cfg(feature = "server")]
pub mod server_plugins;
pub mod shared_plugins;



@@ 38,129 40,88 @@ pub mod prelude;

#[cfg(target_arch = "wasm32")]
pub mod wasm_entrypoint;
mod cli;
mod build_meta;

use std::str::FromStr;
#[cfg(target_arch = "wasm32")]
pub use wasm_entrypoint::*;

use bevy::log::{tracing_subscriber, LogPlugin};
use crate::cli::StkArgs;
use crate::prelude::*;
use clap::Parser;
#[cfg(feature = "client")]
use crate::client_plugins::ClientPluginGroup;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "server")]
use crate::server_plugins::ServerPluginGroup;
use std::net::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 {

#[allow(unused_mut)]
fn run(cli: StkArgs) -> 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 -+-+-+-+-+-+-");
            }
        #[cfg(feature = "client")]
        StkArgs::Client { server } => {
            app.add_plugins(
                DefaultPlugins.build()
                    .disable::<LogPlugin>()
                    .disable::<UiPlugin>()
                    .disable::<bevy::log::LogPlugin>()
                    .disable::<bevy::ui::UiPlugin>()
            );
            app.add_plugins(ClientPluginGroup { server: Some(server) });
            app.add_plugins(SharedPluginGroup);
            app.add_plugins(shared_plugins::SharedPluginGroup);
        }
        #[cfg(not(target_arch = "wasm32"))]
        Cli::Server {
            bind,
        #[cfg(feature = "server")]
        StkArgs::Server {
            bind_to,
            tick_rate,
            max_clients,
            hotpatching_enabled,
            #[cfg(feature = "client")]
            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);
            }


            let mut with_client_all = false;
            #[cfg(feature = "client")]
            if with_client {
                with_client_all = true;
                app.add_plugins(
                    DefaultPlugins.build()
                        .disable::<LogPlugin>()
                        .disable::<UiPlugin>()
                        .disable::<bevy::log::LogPlugin>()
                        .disable::<bevy::ui::UiPlugin>()
                );
                app.add_plugins(|app: &mut App| {
                    app.add_systems(Startup, crate::server::player::join::ls_magically_invent_player);
                    app.add_systems(Startup, server::player::join::ls_magically_invent_player);
                });
            } else {
            }
            if !with_client_all {
               app
                   .add_plugins(AssetPlugin::default())
                   .add_plugins(StatesPlugin)
                   .add_plugins(bevy::state::app::StatesPlugin)
                    .add_plugins(TaskPoolPlugin::default())
                    .add_plugins(FrameCountPlugin)
                    .add_plugins(TimePlugin)
                    .add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f32(
                    .add_plugins(bevy::diagnostic::FrameCountPlugin)
                    .add_plugins(bevy::time::TimePlugin)
                    .add_plugins(bevy::app::ScheduleRunnerPlugin::run_loop(std::time::Duration::from_secs_f32(
                        1.0 / tick_rate,
                    )));
            }

            app.add_plugins(SharedPluginGroup);
            app.add_plugins(shared_plugins::SharedPluginGroup);

            let mut pg = ServerPluginGroup {
                bind,
                bind: bind_to,
                tick_rate,
                max_clients,
                max_clients: max_clients.into(),
            }.build();

            #[cfg(feature = "client")]
            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 {} => {
        #[cfg(feature = "particle_editor")]
        StkArgs::ParticleEditor {} => {
            app.add_plugins(crate::particle_editor::particle_editor_plugin);
        }
    }


@@ 170,26 131,27 @@ fn run(cli: Cli) -> AppExit {

#[cfg(feature = "wasm")]
fn main() {
    // noop
    // noop on webassembly
}

#[cfg(feature = "native")]
fn main() -> AppExit {
    let cli = Cli::parse();
    use tracing_subscriber::util::SubscriberInitExt;
    use bevy::log::{tracing_subscriber};

    let cli = crate::cli::parse_args();

    tracing_subscriber::fmt()
        .with_env_filter(
            EnvFilter::from_default_env().add_directive(Directive::from_str("naga=error").unwrap()),
            tracing_subscriber::EnvFilter::from_default_env().add_directive(tracing_subscriber::filter::Directive::from_str("naga=error").unwrap()),
        )
        .finish()
        .init();

    #[cfg(feature = "native")] {
        ctrlc::set_handler(|| {
            info!("caught ^C, ciao!");
            exit(0);
        }).unwrap();
    }
    ctrlc::set_handler(|| {
        info!("caught ^C, ciao!");
        std::process::exit(0);
    }).unwrap();

    run(cli)
}

M crates/unified/src/server/player.rs => crates/unified/src/server/player.rs +1 -1
@@ 233,7 233,7 @@ fn dragging(
    joints: Query<(&Joint, &JointOf, &Transform, Option<&Peer>, Entity)>,
    q_joints: Query<&Joints>,
    clients: Query<&ConnectedNetworkEntity>,
    q_ls_me: Query<Entity, With<crate::client::Me>>,
    q_ls_me: Query<Entity, With<crate::ecs::Me>>,
    world_config: Res<WorldConfigResource>,
    mut commands: Commands,
) {

M crates/unified/src/server/player/join.rs => crates/unified/src/server/player/join.rs +1 -1
@@ 100,7 100,7 @@ pub fn ls_magically_invent_player(
            network_entity: fake_network_entity,
        },
        Replicated,
        crate::client::Me
        crate::ecs::Me
    )).id();
    debug!(?fake_network_entity, ?local_player, "listenserver: magically invented a player");
}
\ No newline at end of file

M crates/xtask/src/main.rs => crates/xtask/src/main.rs +4 -0
@@ 150,6 150,10 @@ fn main() {
                    "native",
                    "--features",
                    "bevy/dynamic_linking",
                    "--features",
                    "server",
                    "--features",
                    "client",
                    "--args",
                    &args.collect::<Vec<_>>().join(" ")
                ])