~starkingdoms/starkingdoms

4fb7a538004c7b3fb5880cf3e71dbd782e877910 — core 5 months ago 69730b3
gravity
M Cargo.lock => Cargo.lock +0 -1
@@ 7857,7 7857,6 @@ dependencies = [
 "bevy_replicon",
 "bevy_replicon_renet2",
 "clap",
 "crossbeam-channel",
 "log",
 "serde",
 "tracing-subscriber",

M crates/unified/Cargo.toml => crates/unified/Cargo.toml +0 -1
@@ 17,5 17,4 @@ clap = { version = "4", features = ["derive", "cargo"] }
tracing-subscriber = "0.3"
log = { version = "*", features = ["max_level_debug", "release_max_level_warn"] }

crossbeam-channel = "0.5"
serde = { version = "1", features = ["derive"] }
\ No newline at end of file

M crates/unified/assets/config/world.wc.toml => crates/unified/assets/config/world.wc.toml +6 -1
@@ 1,2 1,7 @@
[world]
gravity = 0.015
\ No newline at end of file
gravity = 0.015

[part]
default_height = 25
default_width = 25
default_mass = 50
\ No newline at end of file

A crates/unified/src/client/incoming_parts.rs => crates/unified/src/client/incoming_parts.rs +35 -0
@@ 0,0 1,35 @@
use bevy::prelude::*;
use bevy_rapier2d::prelude::{AdditionalMassProperties, ReadMassProperties, RigidBody};
use crate::config::planet::Planet;
use crate::ecs::Part;

pub fn incoming_parts_plugin(app: &mut App) {
    app.add_systems(Update, (handle_incoming_parts, handle_updated_parts));
}

fn handle_incoming_parts(mut commands: Commands, mut new_parts: Query<(Entity, &Part), Added<Part>>, asset_server: Res<AssetServer>) {
    for (new_entity, new_part) in new_parts.iter() {
        let mut sprite = Sprite::from_image(asset_server.load(&new_part.sprite));
        sprite.custom_size = Some(Vec2::new(new_part.width, new_part.height));

        commands.entity(new_entity)
            .insert(sprite)
            .insert(AdditionalMassProperties::Mass(new_part.mass))
            .insert(ReadMassProperties::default())
            .insert(RigidBody::Dynamic);
        info!(?new_part, "prepared new part");
    }
}
fn handle_updated_parts(mut commands: Commands, mut updated_parts: Query<(Entity, &Part), Changed<Part>>, asset_server: Res<AssetServer>) {
    for (updated_entity, updated_part) in updated_parts.iter() {
        let mut sprite = Sprite::from_image(asset_server.load(&updated_part.sprite));
        sprite.custom_size = Some(Vec2::new(updated_part.width, updated_part.height));

        commands.entity(updated_entity)
            .remove::<Sprite>()
            .remove::<AdditionalMassProperties>()
            .insert(sprite)
            .insert(AdditionalMassProperties::Mass(updated_part.mass));
        info!(?updated_part, "updated part");
    }
}
\ No newline at end of file

M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +4 -34
@@ 1,4 1,5 @@
mod incoming_planets;
mod incoming_parts;

use std::net::{SocketAddr, UdpSocket};
use std::time::SystemTime;


@@ 9,6 10,7 @@ use bevy_replicon::prelude::RepliconChannels;
use bevy_replicon_renet2::netcode::{ClientAuthentication, NetcodeClientTransport, NativeSocket};
use bevy_replicon_renet2::renet2::{ConnectionConfig, RenetClient};
use bevy_replicon_renet2::RenetChannelsExt;
use crate::client::incoming_parts::incoming_parts_plugin;
use crate::client::incoming_planets::incoming_planets_plugin;
use crate::ecs::{Ball, CursorWorldCoordinates, Ground, MainCamera, SendBallHere};



@@ 43,11 45,9 @@ impl Plugin for ClientPlugin {
                info!(?client_id, "connected!");
            })
            .add_systems(Startup, setup_graphics)
            //.add_systems(Update, add_ball_sprite)
            //.add_systems(Update, add_ground_sprite)
            .add_systems(Update, update_cursor_position)
            .add_systems(Update, teleport_cube_around)
            .add_plugins(incoming_planets_plugin);
            .add_plugins(incoming_planets_plugin)
            .add_plugins(incoming_parts_plugin);
    }
}



@@ 56,27 56,6 @@ fn setup_graphics(mut commands: Commands) {
        .insert(MainCamera);
}

fn add_ball_sprite(mut commands: Commands, q: Query<Entity, Added<Ball>>, asset_server: Res<AssetServer>) {
    for item in &q {
        let mut sprite = Sprite::from_image(asset_server.load("textures/earth.png"));
        sprite.custom_size = Some(Vec2::new(100.0, 100.0));

        commands
            .entity(item)
            .insert(sprite);
    }
}
fn add_ground_sprite(mut commands: Commands, q: Query<Entity, Added<Ground>>, asset_server: Res<AssetServer>) {
    for item in &q {
        let mut sprite = Sprite::from_image(asset_server.load("textures/hearty.png"));
        sprite.custom_size = Some(Vec2::new(1000.0, 100.0));

        commands
            .entity(item)
            .insert(sprite);
    }
}

fn update_cursor_position(
    q_windows: Query<&Window, With<PrimaryWindow>>,
    q_camera: Query<(&Camera, &GlobalTransform), With<MainCamera>>,


@@ 92,13 71,4 @@ fn update_cursor_position(
    } else {
        coords.0 = None;
    }
}
fn teleport_cube_around(
    cursor_world_coordinates: Res<CursorWorldCoordinates>,
    button_input: Res<ButtonInput<MouseButton>>,
    mut events: EventWriter<SendBallHere>
) {
    if let Some(position) = cursor_world_coordinates.0 && button_input.pressed(MouseButton::Left) {
        events.write(SendBallHere(position));
    }
}
\ No newline at end of file

M crates/unified/src/config/world.rs => crates/unified/src/config/world.rs +15 -2
@@ 2,7 2,20 @@ use bevy::asset::Asset;
use bevy::prelude::TypePath;
use serde::Deserialize;

#[derive(Deserialize, Asset, TypePath)]
#[derive(Deserialize, Asset, TypePath, Clone)]
pub struct GlobalWorldConfig {
    pub world: WorldConfig,
    pub part: PartConfig,
}

#[derive(Deserialize, Asset, TypePath, Clone)]
pub struct WorldConfig {
    pub gravity: f64
    pub gravity: f32
}

#[derive(Deserialize, Asset, TypePath, Clone)]
pub struct PartConfig {
    pub default_width: f32,
    pub default_height: f32,
    pub default_mass: f32
}
\ No newline at end of file

M crates/unified/src/ecs.rs => crates/unified/src/ecs.rs +23 -2
@@ 1,5 1,10 @@
use bevy::math::Vec2;
use bevy::prelude::{Component, Event, Resource};
use bevy::prelude::{Bundle, Component, Event, Resource, Transform};
use bevy_rapier2d::dynamics::AdditionalMassProperties;
use bevy_replicon::prelude::Replicated;
use bevy_rapier2d::dynamics::RigidBody;
use bevy_rapier2d::geometry::Collider;
use bevy_rapier2d::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Component, Serialize, Deserialize)]


@@ 12,4 17,20 @@ pub struct MainCamera;
pub struct CursorWorldCoordinates(pub Option<Vec2>);

#[derive(Debug, Default, Deserialize, Event, Serialize)]
pub struct SendBallHere(pub Vec2);
\ No newline at end of file
pub struct SendBallHere(pub Vec2);

#[derive(Component, Serialize, Deserialize, Debug)]
#[require(ReadMassProperties, RigidBody::Dynamic, ExternalForce, ExternalImpulse)]
pub struct Part {
    pub sprite: String,
    pub width: f32,
    pub height: f32,
    pub mass: f32
}
#[derive(Bundle)]
pub struct PartBundle {
    pub part: Part,
    pub transform: Transform,
    pub collider: Collider,
    pub additional_mass_properties: AdditionalMassProperties,
}
\ No newline at end of file

A crates/unified/src/server/gravity.rs => crates/unified/src/server/gravity.rs +46 -0
@@ 0,0 1,46 @@
use bevy::math::FloatPow;
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use crate::config::planet::Planet;
use crate::ecs::Part;
use crate::server::world_config::WorldConfigResource;

pub fn newtonian_gravity_plugin(mut app: &mut App) {
    app.add_systems(Update, update_gravity);
}

fn update_gravity(
    mut part_query: Query<
        (
            &Transform,
            &ReadMassProperties,
            &mut ExternalForce,
            &mut ExternalImpulse
        ),
        With<Part>
    >,
    planet_query: Query<(&Transform, &ReadMassProperties), With<Planet>>,
    world_config: Res<WorldConfigResource>,
) {
    let Some(world_config) = &world_config.config else { return; };

    for (part_transform, part_mass, mut forces, mut impulses) in &mut part_query {
        forces.force = Vec2::ZERO;
        impulses.impulse = Vec2::ZERO;
        forces.torque = 0.0;

        let part_mass = part_mass.mass;
        let part_translation = part_transform.translation;

        for (planet_transform, planet_mass) in &planet_query {
            let planet_mass = planet_mass.mass;
            let planet_translation = planet_transform.translation;

            let distance = planet_translation.distance(part_translation);

            let force = world_config.world.gravity * ((part_mass * planet_mass) / distance.squared());
            let direction = (planet_translation - part_translation).normalize() * force;
            forces.force += direction.xy();
        }
    }
}
\ No newline at end of file

M crates/unified/src/server/mod.rs => crates/unified/src/server/mod.rs +11 -31
@@ 1,4 1,7 @@
mod planets;
pub mod planets;
pub mod player;
mod world_config;
mod gravity;

use std::net::{SocketAddr, UdpSocket};
use std::time::{SystemTime, UNIX_EPOCH};


@@ 9,7 12,10 @@ use bevy_replicon_renet2::netcode::{NativeSocket, NetcodeServerTransport, Server
use bevy_replicon_renet2::renet2::{ConnectionConfig, RenetServer};
use bevy_replicon_renet2::RenetChannelsExt;
use crate::ecs::{Ball, Ground, SendBallHere};
use crate::server::gravity::newtonian_gravity_plugin;
use crate::server::planets::planets_plugin;
use crate::server::player::player_management_plugin;
use crate::server::world_config::world_config_plugin;

pub struct ServerPlugin {
    pub bind: SocketAddr,


@@ 44,35 50,9 @@ impl Plugin for ServerPlugin {

                info!("websocket server listening");
            })
            .add_systems(Startup, setup_physics)
            .add_systems(PreUpdate, receive_send_ball_here)
            .add_plugins(planets_plugin);
    }
}

fn setup_physics(mut commands: Commands) {
    commands.spawn(Collider::cuboid(500.0, 50.0))
        .insert(Transform::from_xyz(0.0, -100.0, 0.0))
        .insert(Restitution::coefficient(1.0))
        .insert(Ground)
        .insert(Replicated);

    commands.spawn(RigidBody::Dynamic)
        .insert(Collider::ball(50.0))
        .insert(Restitution::coefficient(1.0))
        .insert(Transform::from_xyz(0.0, 400.0, 0.0))
        .insert(Velocity::default())
        .insert(Ball)
        .insert(Replicated);
}

fn receive_send_ball_here(mut events: EventReader<FromClient<SendBallHere>>, mut ball: Query<(&mut Transform, &mut Velocity), With<Ball>>) {
    for FromClient { client_entity, event } in events.read() {
        for (mut position, mut velocity) in &mut ball {
            position.translation.x = event.0.x;
            position.translation.y = event.0.y;
            velocity.linvel = Vec2::ZERO;
            velocity.angvel = 0.0;
        }
            .add_plugins(planets_plugin)
            .add_plugins(world_config_plugin)
            .add_plugins(newtonian_gravity_plugin)
            .add_plugins(player_management_plugin);
    }
}
\ No newline at end of file

A crates/unified/src/server/player.rs => crates/unified/src/server/player.rs +30 -0
@@ 0,0 1,30 @@
use bevy::prelude::*;
use bevy_rapier2d::prelude::{AdditionalMassProperties, Collider, ReadMassProperties, RigidBody};
use bevy_replicon::prelude::{ConnectedClient, Replicated};
use crate::ecs::{Part, PartBundle};
use crate::server::world_config::WorldConfigResource;

pub fn player_management_plugin(mut app: &mut App) {
    app.add_systems(Update, handle_new_players);
}

fn handle_new_players(mut commands: Commands, q_new_clients: Query<Entity, Added<ConnectedClient>>, world_config: Res<WorldConfigResource>) {
    let Some(wc) = &world_config.config else { return; };
    for joined_player in &q_new_clients {
        commands.entity(joined_player)
            .insert(PartBundle {
                part: Part {
                    sprite: "textures/hearty.png".to_string(),
                    width: wc.part.default_width,
                    height: wc.part.default_height,
                    mass: wc.part.default_mass
                },
                transform: Transform::from_xyz(500.0, 0.0, 0.0), // todo,
                collider: Collider::cuboid(wc.part.default_width / 2.0, wc.part.default_height / 2.0),
                additional_mass_properties: AdditionalMassProperties::Mass(wc.part.default_mass)
            })
            .insert(ReadMassProperties::default())
            .insert(RigidBody::Dynamic)
            .insert(Replicated);
    }
}
\ No newline at end of file

A crates/unified/src/server/world_config.rs => crates/unified/src/server/world_config.rs +56 -0
@@ 0,0 1,56 @@
use bevy::asset::Handle;
use bevy::prelude::*;
use bevy_rapier2d::dynamics::AdditionalMassProperties;
use bevy_rapier2d::prelude::Collider;
use bevy_replicon::prelude::Replicated;
use crate::config::planet::{Planet, PlanetBundle, PlanetConfigCollection};
use crate::config::world::{GlobalWorldConfig, WorldConfig};

pub fn world_config_plugin(app: &mut App) {
    app
        .init_resource::<WorldConfigResource>()
        .add_systems(Startup, start_loading_planets)
        .add_systems(Update, update_planets);
}

#[derive(Resource, Default)]
pub struct WorldConfigResource {
    handle: Option<Handle<GlobalWorldConfig>>,
    pub config: Option<GlobalWorldConfig>
}

fn start_loading_planets(assets: Res<AssetServer>, mut planets: ResMut<WorldConfigResource>) {
    planets.handle = Some(assets.load("config/world.wc.toml"));
}


pub fn update_planets(
    mut commands: Commands,
    mut ev_config: EventReader<AssetEvent<GlobalWorldConfig>>,
    mut assets: ResMut<Assets<GlobalWorldConfig>>,
    mut resource: ResMut<WorldConfigResource>,
) {
    let Some(handle) = resource.handle.as_ref() else { return; };

    let waiting_for_asset_id = handle.id();

    for ev in ev_config.read() {
        match ev {
            AssetEvent::Added { id } => {
                if *id == waiting_for_asset_id {
                    info!("world config loaded");
                    let world_config = assets.get(*id).unwrap();
                    resource.config = Some(world_config.clone());
                }
            },
            AssetEvent::Modified { id } => {
                if *id == waiting_for_asset_id {
                    info!("world config modified - reloading");
                    let world_config = assets.get(*id).unwrap();
                    resource.config = Some(world_config.clone());
                }
            },
            _ => {}
        }
    }
}
\ No newline at end of file

M crates/unified/src/server_plugins.rs => crates/unified/src/server_plugins.rs +2 -2
@@ 8,7 8,7 @@ use bevy_common_assets::toml::TomlAssetPlugin;
use bevy_replicon::RepliconPlugins;
use bevy_replicon_renet2::RepliconRenetServerPlugin;
use crate::config::planet::{Planet, PlanetConfigCollection};
use crate::config::world::WorldConfig;
use crate::config::world::{GlobalWorldConfig, WorldConfig};

pub struct ServerPluginGroup {
    pub bind: SocketAddr,


@@ 32,7 32,7 @@ impl PluginGroup for ServerPluginGroup {
            )
            /* Assets */
            .add(AssetPlugin::default())
            .add(TomlAssetPlugin::<WorldConfig>::new(&["wc.toml"]))
            .add(TomlAssetPlugin::<GlobalWorldConfig>::new(&["wc.toml"]))
            .add(TomlAssetPlugin::<PlanetConfigCollection>::new(&["pc.toml"]))
            .add(crate::server::ServerPlugin {
                bind: self.bind,

M crates/unified/src/shared_plugins.rs => crates/unified/src/shared_plugins.rs +17 -4
@@ 1,10 1,9 @@
use bevy::app::{App, PluginGroup, PluginGroupBuilder};
use bevy::math::vec2;
use bevy::prelude::{Sprite, Transform};
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use bevy_replicon::prelude::{AppRuleExt, Channel, ClientEventAppExt};
use crate::config::planet::Planet;
use crate::ecs::{Ball, Ground, SendBallHere};
use crate::ecs::{Ball, Ground, Part, SendBallHere};

pub struct SharedPluginGroup;



@@ 15,6 14,7 @@ impl PluginGroup for SharedPluginGroup {
                RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(100.0)
            )
            .add(register_everything)
            .add(physics_setup_plugin)
    }
}



@@ 25,5 25,18 @@ pub fn register_everything(app: &mut App) {
        .replicate::<Ball>()
        .replicate::<Ground>()
        .replicate::<Collider>()
        .replicate::<Planet>();
        .replicate::<Planet>()
        .replicate::<Part>();
}

fn physics_setup_plugin(mut app: &mut App) {
    app.add_systems(Startup, setup_physics);
}


fn setup_physics(
    mut rapier_config: Query<&mut RapierConfiguration>
) {
    let mut cfg = rapier_config.single_mut().unwrap();
    cfg.gravity = Vec2::ZERO;
}
\ No newline at end of file