~starkingdoms/starkingdoms

b072b09c00e6892310c6ee9187f441e95ae89ccf — core 5 months ago a2ff8f1
feat: part & attachment beginnings
M Cargo.lock => Cargo.lock +0 -13
@@ 1234,18 1234,6 @@ dependencies = [
]

[[package]]
name = "bevy_enoki"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "740289c63cb752adbad3823c49c01d479d853a10d76ccfe5ce3d1f3ea8e14ac2"
dependencies = [
 "bevy 0.16.1",
 "rand 0.8.5",
 "ron 0.8.1",
 "serde",
]

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


@@ 7797,7 7785,6 @@ dependencies = [
 "bevy 0.16.1",
 "bevy_common_assets",
 "bevy_egui",
 "bevy_enoki",
 "bevy_rapier2d 0.30.0",
 "bevy_replicon",
 "clap",

M crates/unified/Cargo.toml => crates/unified/Cargo.toml +2 -3
@@ 26,7 26,8 @@ bevy = { version = "0.16", default-features = false, features = [
    "wayland",
    "multi_threaded",
    "bevy_dev_tools",
    "bevy_sprite_picking_backend"
    "bevy_sprite_picking_backend",
    "png"
] }
bevy_rapier2d = { version = "0.30", features = ["serde-serialize", "simd-stable"] }
bevy_common_assets = { version = "0.13", features = ["toml"] }


@@ 53,8 54,6 @@ bevy_egui = "0.35"
ordered-float = { version = "5", features = ["serde"] }
ron = "0.10"

bevy_enoki = "0.4"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]

[target.'cfg(target_arch = "wasm32")'.dependencies]

A crates/unified/assets/config/parts/hearty.part.toml => crates/unified/assets/config/parts/hearty.part.toml +14 -0
@@ 0,0 1,14 @@
[part]
name = "Hearty"
sprite_connected = "textures/hearty.png"
sprite_disconnected = "textures/hearty.png"

[physics]
width = 50
height = 50
mass = 100

# North
[[joints]]
translation = [ 0.0, 50.0, 0.0 ]
rotation = 0.0
\ No newline at end of file

A crates/unified/src/attachment.rs => crates/unified/src/attachment.rs +21 -0
@@ 0,0 1,21 @@
use bevy::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Component, Serialize, Deserialize)]
/// The primary component for a ship structure
pub struct Ship;

#[derive(Component, Serialize, Deserialize)]
#[relationship_target(relationship = PartInShip, linked_spawn)]
pub struct Parts(Vec<Entity>);

#[derive(Component, Serialize, Deserialize)]
#[relationship(relationship_target = Parts)]
pub struct PartInShip(Entity);

#[derive(Component, Serialize, Deserialize)]
pub struct Joint {
    pub transform: Transform
}
#[derive(Component, Serialize, Deserialize)]
pub struct Peer(Entity);
\ No newline at end of file

D crates/unified/src/client/incoming_particles.rs => crates/unified/src/client/incoming_particles.rs +0 -46
@@ 1,46 0,0 @@
use crate::ecs::Particles;
use bevy::prelude::*;
use bevy_enoki::prelude::ParticleSpawnerState;
use bevy_enoki::{ParticleEffectHandle, ParticleSpawner};

pub fn replicated_particles_plugin(app: &mut App) {
    app.add_systems(
        PreUpdate,
        (replicate_new_particles, replicate_updated_particles),
    );
}

fn replicate_new_particles(
    q: Query<(Entity, &Particles), Added<Particles>>,
    assets: Res<AssetServer>,
    mut commands: Commands,
) {
    for (entity, p) in q.iter() {
        commands
            .entity(entity)
            .insert(ParticleSpawner::default())
            .insert(ParticleEffectHandle(assets.load(&p.effect)))
            .insert(ParticleSpawnerState {
                active: p.active,
                ..default()
            });
        info!("replicate_new_particles {:?}", p);
    }
}
fn replicate_updated_particles(
    mut q: Query<
        (
            Entity,
            &mut ParticleEffectHandle,
            &mut ParticleSpawnerState,
            &Particles,
        ),
        Changed<Particles>,
    >,
    assets: Res<AssetServer>,
) {
    for (_entity, mut handle, mut state, p) in &mut q {
        *handle = ParticleEffectHandle(assets.load(&p.effect));
        state.active = p.active;
    }
}

M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +0 -3
@@ 1,5 1,4 @@
mod colors;
mod incoming_particles;
mod parts;
mod key_input;
mod net;


@@ 9,7 8,6 @@ mod planet;
mod zoom;
mod particles;

use crate::client::incoming_particles::replicated_particles_plugin;
use crate::client::parts::parts_plugin;
use planet::incoming_planets::incoming_planets_plugin;
use crate::client::key_input::key_input_plugin;


@@ 55,7 53,6 @@ impl Plugin for ClientPlugin {
            .add_plugins(key_input_plugin)
            .add_plugins(starfield_plugin)
            .add_plugins(ui_plugin)
            .add_plugins(replicated_particles_plugin)
            .add_plugins(zoom_plugin)
            .insert_resource(DebugPickingMode::Disabled);
    }

M crates/unified/src/client_plugins.rs => crates/unified/src/client_plugins.rs +0 -2
@@ 7,7 7,6 @@ use bevy::dev_tools::picking_debug::DebugPickingPlugin;
use bevy::log::LogPlugin;
use bevy::prelude::MeshPickingPlugin;
use bevy::sprite::prelude::SpritePickingPlugin;
use bevy_enoki::EnokiPlugin;
use bevy_rapier2d::prelude::RapierDebugRenderPlugin;
use bevy_replicon::RepliconPlugins;



@@ 21,7 20,6 @@ impl PluginGroup for ClientPluginGroup {
            .add_group(RepliconPlugins)
            .add(WebSocketClientPlugin)
            .add(AeronetRepliconClientPlugin)
            .add(EnokiPlugin)
            .add(MeshPickingPlugin)
            .add(DebugPickingPlugin)
            .add(ClientPlugin {

M crates/unified/src/config/mod.rs => crates/unified/src/config/mod.rs +1 -0
@@ 1,2 1,3 @@
pub mod planet;
pub mod world;
pub mod part;

A crates/unified/src/config/part.rs => crates/unified/src/config/part.rs +28 -0
@@ 0,0 1,28 @@
use bevy::asset::Asset;
use bevy::math::Vec3;
use bevy::prelude::TypePath;
use serde::{Deserialize, Serialize};

#[derive(Deserialize, TypePath, Serialize, Clone, Debug, PartialEq, Asset)]
pub struct PartConfig {
    pub part: PartPartConfig,
    pub physics: PartPhysicsConfig,
    pub joints: Vec<JointConfig>
}
#[derive(Deserialize, TypePath, Serialize, Clone, Debug, PartialEq, Asset)]
pub struct PartPartConfig {
    pub name: String,
    pub sprite_connected: String,
    pub sprite_disconnected: String,
}
#[derive(Deserialize, TypePath, Serialize, Clone, Debug, PartialEq, Asset)]
pub struct PartPhysicsConfig {
    pub width: f32,
    pub height: f32,
    pub mass: f32
}
#[derive(Deserialize, TypePath, Serialize, Clone, Debug, PartialEq, Asset)]
pub struct JointConfig {
    pub translation: Vec3,
    pub rotation: f32
}
\ No newline at end of file

M crates/unified/src/config/world.rs => crates/unified/src/config/world.rs +1 -1
@@ 20,7 20,7 @@ pub struct WorldConfig {
pub struct PartConfig {
    pub default_width: f32,
    pub default_height: f32,
    pub default_mass: f32,
    pub default_mass: f32
}

#[derive(Deserialize, Asset, TypePath, Clone)]

M crates/unified/src/ecs.rs => crates/unified/src/ecs.rs +4 -5
@@ 4,20 4,19 @@ use bevy_rapier2d::dynamics::AdditionalMassProperties;
use bevy_rapier2d::dynamics::RigidBody;
use bevy_rapier2d::geometry::Collider;
use bevy_rapier2d::prelude::*;
use bevy_replicon::prelude::Replicated;
use serde::{Deserialize, Serialize};

#[derive(Component, Serialize, Deserialize)]
pub struct Ball;
#[derive(Component, Serialize, Deserialize)]
pub struct Ground;
#[derive(Component)]
pub struct MainCamera;

#[derive(Component)]
pub struct StarfieldFront;
#[derive(Component)]
pub struct StarfieldMid;
#[derive(Component)]
pub struct StarfieldBack;

#[derive(Resource, Default)]
pub struct CursorWorldCoordinates(pub Option<Vec2>);



@@ 31,7 30,7 @@ pub enum ThrustEvent {
}

#[derive(Component, Serialize, Deserialize, Debug)]
#[require(ReadMassProperties, RigidBody::Dynamic, ExternalForce, ExternalImpulse)]
#[require(ReadMassProperties, RigidBody::Dynamic, ExternalForce, ExternalImpulse, Replicated)]
pub struct Part {
    pub sprite: String,
    pub width: f32,

M crates/unified/src/lib.rs => crates/unified/src/lib.rs +1 -0
@@ 28,3 28,4 @@ pub mod shared_plugins;
#[cfg(all(not(target_arch = "wasm32"), feature = "particle_editor"))]
pub mod particle_editor;
pub mod clientevent;
pub mod attachment;
\ No newline at end of file

M crates/unified/src/server/mod.rs => crates/unified/src/server/mod.rs +4 -1
@@ 4,6 4,7 @@ pub mod player;
mod world_config;
mod earth_parts;
mod part_dragging;
mod part;

use crate::server::gravity::newtonian_gravity_plugin;
use crate::server::planets::planets_plugin;


@@ 18,6 19,7 @@ use bevy::prelude::*;
use bevy_replicon::prelude::Replicated;
use std::net::SocketAddr;
use crate::server::earth_parts::spawn_parts_plugin;
use crate::server::part::part_config_plugin;
use crate::server::part_dragging::part_dragging_plugin;

pub struct ServerPlugin {


@@ 47,7 49,8 @@ impl Plugin for ServerPlugin {
            .add_plugins(newtonian_gravity_plugin)
            .add_plugins(player_management_plugin)
            .add_plugins(part_dragging_plugin)
            .add_plugins(spawn_parts_plugin);
            .add_plugins(spawn_parts_plugin)
            .add_plugins(part_config_plugin);
    }
}
impl ServerPlugin {

A crates/unified/src/server/part.rs => crates/unified/src/server/part.rs +89 -0
@@ 0,0 1,89 @@
use bevy::asset::Handle;
use bevy::prelude::*;
use bevy_rapier2d::prelude::{AdditionalMassProperties, Collider};
use crate::config::part::PartConfig;
use crate::ecs::Part;

pub fn part_config_plugin(app: &mut App) {
    app.add_systems(PreUpdate, handle_spawn_part_requests)
        .add_systems(Update, (update_part_requests, hotreload));
}

#[derive(Component, Debug)]
pub struct SpawnPart(pub String);
#[derive(Component)]
struct LoadingPart(Handle<PartConfig>);
#[derive(Component, Debug)]
struct PartType(AssetId<PartConfig>);

// watch for new SpawnPart components and start loading their config files
fn handle_spawn_part_requests(new_parts: Query<(Entity, &SpawnPart), Added<SpawnPart>>, mut commands: Commands, asset_server: Res<AssetServer>) {
    for (new_part, request) in &new_parts {
        info!(?new_part, ?request, "answering part request");
        commands.entity(new_part)
            .remove::<SpawnPart>()
            .insert(LoadingPart(asset_server.load(request.0.clone())));
    }
}
fn update_part_requests(
    mut ev_config: EventReader<AssetEvent<PartConfig>>,
    loading_parts: Query<(Entity, &LoadingPart)>,
    assets: ResMut<Assets<PartConfig>>,
    mut commands: Commands
) {
    for ev in ev_config.read() {
        match ev {
            AssetEvent::Added { id } => {
                info!(?id, "asset added");
                for (loading_part, req) in &loading_parts {
                    if req.0.id() == *id {
                        let Some(asset) = assets.get(*id) else { continue; };
                        spawn_part(commands.reborrow(), loading_part, asset, id);
                    }
                }
            },
            AssetEvent::Modified { id } => {
                warn!("asset modification missed!");
            }
            _ => {}
        }
    }
}
fn hotreload(
    mut ev_config: EventReader<AssetEvent<PartConfig>>,
    existing_parts: Query<(Entity, &PartType)>,
    assets: ResMut<Assets<PartConfig>>,
    mut commands: Commands
) {
    for ev in ev_config.read() {
        match ev {
            AssetEvent::Modified { id } => {
                info!(?id, "updating part");
                for (existing_part, ptype) in &existing_parts {
                    if ptype.0 == *id {
                        let Some(asset) = assets.get(ptype.0) else { continue; };
                        spawn_part(commands.reborrow(), existing_part, asset, id);
                    }
                }
            }
            AssetEvent::Added { id } => {
                warn!("asset addition missed!");
            }
            _ => {}
        }
    }
}

fn spawn_part(mut commands: Commands, entity: Entity, part: &PartConfig, id: &AssetId<PartConfig>) {
    commands.entity(entity)
        .remove::<LoadingPart>()
        .insert(Part {
            sprite: part.part.sprite_disconnected.clone(),
            width: part.physics.width,
            height: part.physics.height,
            mass: part.physics.mass,
        })
        .insert(Collider::cuboid(part.physics.width / 2.0, part.physics.height / 2.0))
        .insert(AdditionalMassProperties::Mass(part.physics.mass))
        .insert(PartType(*id));
}
\ No newline at end of file

M crates/unified/src/server/player.rs => crates/unified/src/server/player.rs +12 -1
@@ 9,6 9,7 @@ use bevy_rapier2d::prelude::{
    AdditionalMassProperties, Collider, ExternalForce, ExternalImpulse, MassProperties,
};
use bevy_replicon::prelude::{FromClient, Replicated};
use crate::server::part::{SpawnPart};

pub fn player_management_plugin(app: &mut App) {
    app.add_systems(PreUpdate, reset_movement)


@@ 28,6 29,7 @@ fn handle_new_players(
    q_new_clients: Query<Entity, Added<ConnectedGameEntity>>,
    world_config: Res<WorldConfigResource>,
    planets: Query<(&Transform, &Planet)>,
    asset_server: Res<AssetServer>
) {
    let Some(wc) = &world_config.config else {
        return;


@@ 48,6 50,15 @@ fn handle_new_players(

        info!(?new_transform, "set player's position!");

        commands.entity(joined_player)
            .insert(SpawnPart("config/parts/hearty.part.toml".to_string()))
            .insert(new_transform)
            .insert(PlayerThrust::default())
            .insert(Player {
                client: joined_player
            });

        /*
        commands
            .entity(joined_player)
            .insert(PartBundle {


@@ 131,7 142,7 @@ fn handle_new_players(
                    ),
                    Replicated
                ),
            ]);
            ]);*/
    }
}


M crates/unified/src/server_plugins.rs => crates/unified/src/server_plugins.rs +3 -1
@@ 1,5 1,5 @@
use crate::config::planet::PlanetConfigCollection;
use crate::config::world::GlobalWorldConfig;
use crate::config::world::{GlobalWorldConfig};
use aeronet_replicon::server::AeronetRepliconServerPlugin;
use aeronet_websocket::server::WebSocketServerPlugin;
use bevy::app::{PluginGroup, PluginGroupBuilder, ScheduleRunnerPlugin, TaskPoolPlugin};


@@ 10,6 10,7 @@ use bevy_common_assets::toml::TomlAssetPlugin;
use bevy_replicon::RepliconPlugins;
use std::net::SocketAddr;
use std::time::Duration;
use crate::config::part::PartConfig;

pub struct ServerPluginGroup {
    pub bind: SocketAddr,


@@ 32,6 33,7 @@ impl PluginGroup for ServerPluginGroup {
            .add(AssetPlugin::default())
            .add(TomlAssetPlugin::<GlobalWorldConfig>::new(&["wc.toml"]))
            .add(TomlAssetPlugin::<PlanetConfigCollection>::new(&["pc.toml"]))
            .add(TomlAssetPlugin::<PartConfig>::new(&["part.toml"]))
            .add(crate::server::ServerPlugin {
                bind: self.bind,
                max_clients: self.max_clients,

M crates/unified/src/shared_plugins.rs => crates/unified/src/shared_plugins.rs +8 -4
@@ 1,9 1,10 @@
use crate::config::planet::Planet;
use crate::ecs::{Ball, Ground, Part, Particles, Player, ThrustEvent};
use crate::ecs::{Part, Particles, Player, ThrustEvent};
use bevy::app::{App, PluginGroup, PluginGroupBuilder};
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use bevy_replicon::prelude::{AppRuleExt, Channel, ClientEventAppExt};
use crate::attachment::{Joint, PartInShip, Peer, Ship};
use crate::clientevent::{PartDragControlEvent, PartDragEvent};

pub struct SharedPluginGroup;


@@ 23,15 24,18 @@ pub fn register_everything(app: &mut App) {
        .add_mapped_client_event::<PartDragEvent>(Channel::Unreliable)
        .add_mapped_client_event::<PartDragControlEvent>(Channel::Ordered)
        .replicate::<Transform>()
        .replicate::<Ball>()
        .replicate::<Ground>()
        .replicate::<Collider>()
        .replicate::<RigidBody>()
        .replicate::<Planet>()
        .replicate::<Part>()
        .replicate::<Player>()
        .replicate::<Particles>()
        .replicate::<ChildOf>();
        .replicate::<ChildOf>()

        .replicate::<Ship>()
        .replicate::<PartInShip>()
        .replicate::<Joint>()
        .replicate::<Peer>();
}

fn physics_setup_plugin(app: &mut App) {