mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-11 05:48:29 +00:00
remove network stack
This commit is contained in:
parent
9074854ce9
commit
e3e3d4d7a2
2
.idea/runConfigurations/PublishServer_Win.xml
generated
2
.idea/runConfigurations/PublishServer_Win.xml
generated
@ -1,7 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="PublishServer Win" type="ShConfigurationType">
|
||||
<option name="SCRIPT_TEXT" value="" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="false" />
|
||||
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/publish_server.bat" />
|
||||
<option name="SCRIPT_OPTIONS" value="" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["client", "server"]
|
||||
members = ["client"]
|
||||
|
||||
@ -11,16 +11,10 @@ build = "build.rs"
|
||||
[dependencies]
|
||||
bevy = { version = "0.15.1", features = ["jpeg", "trace_tracy", "trace_tracy_memory"] }
|
||||
bevy_egui = "0.33.0"
|
||||
bevy_asset = "0.15.0"
|
||||
bevy_reflect = "0.15.0"
|
||||
bevy_render = "0.15.0"
|
||||
bevy_window = "0.15.0"
|
||||
egui_tiles = "0.12.0"
|
||||
spacetimedb-sdk = "1.0"
|
||||
hex = "0.4"
|
||||
random_word = { version = "0.5.0", features = ["en"] }
|
||||
rand = "0.8.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
big_space = "0.9.1"
|
||||
|
||||
|
||||
|
||||
@ -5,6 +5,6 @@ fn main() {
|
||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||
let target_dir = Path::new(&out_dir).ancestors().nth(3).unwrap(); // gets target/debug or release
|
||||
|
||||
fs::copy("config.toml", target_dir.join("config.toml"))
|
||||
.expect("Failed to copy config.toml to target directory");
|
||||
fs::copy("Config.toml", target_dir.join("Config.toml"))
|
||||
.expect("Failed to copy Config.toml to target directory");
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ pub struct AppPlugin;
|
||||
impl Plugin for AppPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(crate::plugins::ui::ui_plugin::UiPlugin);
|
||||
app.add_plugins(crate::plugins::big_space::big_space_plugin::BigSpaceIntegrationPlugin);
|
||||
app.add_plugins(crate::plugins::environment::environment_plugin::EnvironmentPlugin);
|
||||
//app.add_plugins(crate::plugins::network::network_plugin::NetworkPlugin);
|
||||
app.add_plugins(crate::plugins::input::input_plugin::InputPlugin);
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
pub mod debug_gizmos;
|
||||
pub mod vector_helper;
|
||||
pub mod math;
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
use bevy::math::Vec3;
|
||||
use bevy::prelude::{Quat, Transform};
|
||||
use rand::Rng;
|
||||
use crate::helper::math::RoundTo;
|
||||
use crate::module_bindings::DbTransform;
|
||||
|
||||
pub(crate) fn random_vec3(min: f32, max: f32) -> Vec3 {
|
||||
let mut rng = rand::thread_rng();
|
||||
Vec3::new(
|
||||
rng.gen_range(min..max),
|
||||
rng.gen_range(min..max),
|
||||
rng.gen_range(min..max),
|
||||
)
|
||||
}
|
||||
|
||||
impl From<DbTransform> for Transform {
|
||||
fn from(db: DbTransform) -> Self {
|
||||
Transform {
|
||||
translation: Vec3::new(db.position.x, db.position.y, db.position.z),
|
||||
rotation: //Quat::from_xyzw(0.0, 0.0, 0.0, 0.0),
|
||||
Quat::from_xyzw(
|
||||
db.rotation.x.round_to(3),
|
||||
db.rotation.y.round_to(3),
|
||||
db.rotation.z.round_to(3),
|
||||
db.rotation.w.round_to(3),
|
||||
),
|
||||
scale: Vec3::new(db.scale.x.round_to(3), db.scale.y.round_to(3), db.scale.z.round_to(3)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,23 @@
|
||||
mod app;
|
||||
mod helper;
|
||||
mod plugins;
|
||||
mod module_bindings;
|
||||
mod config;
|
||||
|
||||
use std::fs;
|
||||
use crate::app::AppPlugin;
|
||||
use bevy::gizmos::{AppGizmoBuilder, GizmoPlugin};
|
||||
use bevy::log::info;
|
||||
use bevy::prelude::{default, App, GizmoConfigGroup, PluginGroup, Reflect, Res, Resource};
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::settings::{Backends, RenderCreation, WgpuSettings};
|
||||
use bevy::render::RenderPlugin;
|
||||
use bevy::DefaultPlugins;
|
||||
use bevy::input::gamepad::AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound;
|
||||
use bevy::window::PresentMode;
|
||||
use bevy_egui::EguiPlugin;
|
||||
use bevy_window::{PresentMode, Window, WindowPlugin};
|
||||
use big_space::plugin::BigSpacePlugin;
|
||||
use toml;
|
||||
use crate::config::Config;
|
||||
use crate::plugins::big_space::big_space_plugin::BigSpaceIntegrationPlugin;
|
||||
|
||||
const TITLE: &str = "horror-game";
|
||||
const RESOLUTION: (f32, f32) = (1920f32, 1080f32);
|
||||
@ -34,14 +36,14 @@ fn main() {
|
||||
|
||||
|
||||
let mut app = App::new();
|
||||
|
||||
|
||||
app.insert_resource(config);
|
||||
|
||||
|
||||
register_platform_plugins(&mut app);
|
||||
|
||||
app.add_plugins(AppPlugin);
|
||||
app.add_plugins(EguiPlugin);
|
||||
|
||||
|
||||
|
||||
|
||||
/*app.add_plugins(GizmoPlugin);*/
|
||||
@ -76,7 +78,7 @@ fn register_platform_plugins(app: &mut App) {
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}),
|
||||
}).build().disable::<TransformPlugin>(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
77
client/src/plugins/big_space/big_space_plugin.rs
Normal file
77
client/src/plugins/big_space/big_space_plugin.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use bevy::math::DVec3;
|
||||
use bevy::prelude::*;
|
||||
use big_space::prelude::*;
|
||||
|
||||
/// Plugin enabling high precision coordinates using `big_space`.
|
||||
///
|
||||
/// This sets up [`BigSpacePlugin`] so entities can be placed far from the origin
|
||||
/// without losing precision.
|
||||
// ── plugin that creates the grid ──────────────────────────────────────────────
|
||||
pub struct BigSpaceIntegrationPlugin;
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct RootGrid(pub Entity);
|
||||
|
||||
impl Plugin for BigSpaceIntegrationPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(BigSpacePlugin::<i64>::default());
|
||||
|
||||
app.add_systems(PreStartup, (spawn_root, cache_root.after(spawn_root)));
|
||||
app.add_systems(PostStartup, (fix_invalid_children));
|
||||
|
||||
app.add_systems(PostUpdate,(fix_invalid_children));
|
||||
}
|
||||
}
|
||||
|
||||
// 1) build the Big-Space root
|
||||
fn spawn_root(mut commands: Commands) {
|
||||
commands.spawn_big_space_default::<i64>(|_| {});
|
||||
}
|
||||
|
||||
// 2) cache the root entity for later use
|
||||
fn cache_root(
|
||||
mut commands: Commands,
|
||||
roots: Query<Entity, (With<BigSpace>, Without<Parent>)>, // top-level grid
|
||||
) {
|
||||
if let Ok(entity) = roots.get_single() {
|
||||
|
||||
commands.entity(entity).insert(Visibility::Visible);
|
||||
commands.insert_resource(RootGrid(entity));
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_invalid_children(
|
||||
mut commands: Commands,
|
||||
bad: Query<Entity, (With<FloatingOrigin>, Without<GridCell<i64>>, With<Parent>)>,
|
||||
) {
|
||||
for e in &bad {
|
||||
commands.entity(e).insert(GridCell::<i64>::ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn move_by(
|
||||
mut q: Query<&mut Transform>,
|
||||
delta: Vec3, // metres inside the current cell
|
||||
) {
|
||||
for mut t in &mut q {
|
||||
t.translation += delta; // small numbers only
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn teleport_to<P: GridPrecision>(
|
||||
e: Entity,
|
||||
target: DVec3,
|
||||
grids: Grids<'_, '_, P>,
|
||||
mut q: Query<(&Parent, &mut GridCell<P>, &mut Transform)>,
|
||||
) {
|
||||
let (parent, mut cell, mut tf) = q.get_mut(e).unwrap();
|
||||
let grid = grids.parent_grid(parent.get()).unwrap();
|
||||
|
||||
let (new_cell, local) = grid.translation_to_grid(target);
|
||||
|
||||
*cell = new_cell;
|
||||
tf.translation = local;
|
||||
}
|
||||
2
client/src/plugins/big_space/mod.rs
Normal file
2
client/src/plugins/big_space/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
pub mod big_space_plugin;
|
||||
@ -1,13 +1,15 @@
|
||||
use bevy::app::{App, Plugin, PreStartup, PreUpdate, Startup};
|
||||
use bevy::prelude::IntoSystemConfigs;
|
||||
|
||||
pub struct EnvironmentPlugin;
|
||||
impl Plugin for EnvironmentPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
|
||||
app.add_systems(
|
||||
Startup,
|
||||
(crate::plugins::environment::systems::environment_system::setup, crate::plugins::environment::systems::camera_system::setup ),
|
||||
(crate::plugins::environment::systems::camera_system::setup,crate::plugins::environment::systems::environment_system::setup.after(crate::plugins::environment::systems::camera_system::setup) ),
|
||||
);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,12 +2,10 @@
|
||||
use bevy::input::mouse::{MouseMotion, MouseWheel};
|
||||
use bevy::math::Vec3;
|
||||
use bevy::prelude::*;
|
||||
use bevy_render::camera::{Exposure, PhysicalCameraParameters, Projection};
|
||||
use bevy_window::CursorGrabMode;
|
||||
use bevy::render::camera::{Exposure, PhysicalCameraParameters};
|
||||
use big_space::prelude::{BigSpaceCommands, FloatingOrigin};
|
||||
use rand::Rng;
|
||||
use random_word::Lang;
|
||||
use crate::module_bindings::{set_name, set_position, spawn_entity, DbTransform, DbVector3, DbVector4};
|
||||
use crate::plugins::network::systems::database::DbConnectionResource;
|
||||
use crate::plugins::big_space::big_space_plugin::RootGrid;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct CameraController {
|
||||
@ -27,26 +25,33 @@ impl Default for CameraController {
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn setup(mut commands: Commands,
|
||||
root: Res<RootGrid>,) {
|
||||
|
||||
|
||||
|
||||
pub fn setup(mut commands: Commands,) {
|
||||
|
||||
|
||||
commands.entity(root.0).with_children(|parent| {
|
||||
parent.spawn((
|
||||
|
||||
commands.spawn((
|
||||
Transform::from_xyz(0.0, 0.0, 10.0), // initial f32
|
||||
GlobalTransform::default(),
|
||||
Camera3d::default(),
|
||||
Projection::from(PerspectiveProjection {
|
||||
near: 0.0001,
|
||||
..default()
|
||||
}),
|
||||
CameraController::default(),
|
||||
Exposure::from_physical_camera(PhysicalCameraParameters {
|
||||
aperture_f_stops: 1.0,
|
||||
shutter_speed_s: 1.0 / 125.0,
|
||||
sensitivity_iso: 100.0,
|
||||
sensor_height: 0.01866,
|
||||
}),
|
||||
Name::new("Camera"),
|
||||
Transform::from_xyz(0.0, 0.0, 10.0), // initial position
|
||||
GlobalTransform::default(),
|
||||
Camera3d::default(),
|
||||
Projection::from(PerspectiveProjection {
|
||||
near: 0.0001,
|
||||
..default()
|
||||
}),
|
||||
CameraController::default(),
|
||||
Exposure::from_physical_camera(PhysicalCameraParameters {
|
||||
aperture_f_stops: 1.0,
|
||||
shutter_speed_s: 1.0 / 125.0,
|
||||
sensitivity_iso: 100.0,
|
||||
sensor_height: 0.01866,
|
||||
}),
|
||||
FloatingOrigin,
|
||||
));
|
||||
});
|
||||
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@ -1,29 +1,74 @@
|
||||
|
||||
use bevy::prelude::*;
|
||||
use big_space::prelude::{BigSpace, BigSpaceCommands, GridCell, GridCommands};
|
||||
use crate::plugins::big_space::big_space_plugin::RootGrid;
|
||||
|
||||
|
||||
/// Earth and a FIFA-size football (diameters, metres)
|
||||
const EARTH_DIAM: f32 = 12_742_000.0; // 12 742 km
|
||||
const BALL_DIAM: f32 = 0.22; // 22 cm
|
||||
|
||||
pub(crate) fn setup(
|
||||
mut commands : Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
root: Res<RootGrid>,
|
||||
) {
|
||||
|
||||
// 2) directional light
|
||||
commands.spawn(DirectionalLightBundle {
|
||||
transform: Transform::from_rotation(Quat::from_euler(
|
||||
EulerRot::XYZ,
|
||||
-std::f32::consts::FRAC_PI_4,
|
||||
std::f32::consts::FRAC_PI_4,
|
||||
0.0,
|
||||
)),
|
||||
directional_light: DirectionalLight {
|
||||
shadows_enabled: true,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
// one unit-diameter sphere mesh, reused for every instance
|
||||
let sphere_mesh = meshes.add(Sphere::new(0.5).mesh().ico(32).unwrap());
|
||||
let mat = materials.add(StandardMaterial {
|
||||
base_color: Color::srgb(0.6, 0.7, 0.8),
|
||||
perceptual_roughness: 0.7,
|
||||
..default()
|
||||
});
|
||||
|
||||
// light (unchanged)
|
||||
commands.entity(root.0).with_children(|p| {
|
||||
p.spawn(DirectionalLightBundle {
|
||||
transform: Transform::from_rotation(Quat::from_euler(
|
||||
EulerRot::XYZ,
|
||||
-std::f32::consts::FRAC_PI_4,
|
||||
0.0,
|
||||
0.0,
|
||||
)),
|
||||
directional_light: DirectionalLight {
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
});
|
||||
});
|
||||
|
||||
/*// ---------- spawn spheres from football-size up to Earth-size ----------
|
||||
const N: usize = 10; // how many spheres
|
||||
let log_min = BALL_DIAM.log10();
|
||||
let log_max = EARTH_DIAM.log10();
|
||||
|
||||
let mut offset = 0.0_f32; // keep objects apart
|
||||
commands.entity(root.0).with_children(|parent| {
|
||||
for i in 0..N {
|
||||
// log-spaced diameters
|
||||
let t = i as f32 / (N as f32 - 1.0);
|
||||
let diam = 10f32.powf(log_min + t * (log_max - log_min));
|
||||
let radius = diam * 0.5;
|
||||
let scale_v3 = Vec3::splat(radius); // unit sphere → real size
|
||||
|
||||
// place the sphere so they don’t overlap
|
||||
offset += radius; // move by previous radius
|
||||
let pos = Vec3::new(offset, radius, 0.0); // sit on X axis, resting on Y=0
|
||||
offset += radius + radius * 0.05; // add gap (5 %)
|
||||
|
||||
parent.spawn((
|
||||
// spatial requirements for big_space
|
||||
GridCell::<i64>::ZERO,
|
||||
Transform::from_scale(scale_v3).with_translation(pos),
|
||||
GlobalTransform::default(),
|
||||
// rendering
|
||||
Mesh3d(sphere_mesh.clone()),
|
||||
MeshMaterial3d(mat.clone()),
|
||||
Name::new(format!("Sphere_{i}")),
|
||||
));
|
||||
}
|
||||
});*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
pub mod environment_system;
|
||||
pub mod camera_system;
|
||||
pub mod camera_system;
|
||||
mod planet;
|
||||
71
client/src/plugins/environment/systems/planet.rs
Normal file
71
client/src/plugins/environment/systems/planet.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use bevy::asset::RenderAssetUsages;
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::mesh::*;
|
||||
use big_space::floating_origins::FloatingOrigin;
|
||||
use big_space::prelude::GridCell;
|
||||
use crate::plugins::big_space::big_space_plugin::RootGrid;
|
||||
use crate::plugins::environment::systems::camera_system::CameraController;
|
||||
|
||||
pub struct PlanetMaker {}
|
||||
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
root: Res<RootGrid>
|
||||
) {
|
||||
// Four corner vertices – y is up in Bevy’s 3-D coordinate system
|
||||
let positions = vec![
|
||||
[-0.5, 0.0, -0.5],
|
||||
[ 0.5, 0.0, -0.5],
|
||||
[ 0.5, 0.0, 0.5],
|
||||
[-0.5, 0.0, 0.5],
|
||||
];
|
||||
|
||||
// Single normal for all vertices (pointing up)
|
||||
let normals = vec![[0.0, 1.0, 0.0]; 4];
|
||||
|
||||
// UVs for a full-size texture
|
||||
let uvs = vec![
|
||||
[0.0, 0.0],
|
||||
[1.0, 0.0],
|
||||
[1.0, 1.0],
|
||||
[0.0, 1.0],
|
||||
];
|
||||
|
||||
// Two triangles: (0,1,2) and (0,2,3)
|
||||
let indices = Indices::U32(vec![0, 1, 2, 0, 2, 3]);
|
||||
|
||||
let plane = Mesh::new(
|
||||
PrimitiveTopology::TriangleList,
|
||||
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
|
||||
)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
|
||||
.with_inserted_indices(indices);
|
||||
|
||||
let mesh_handle = meshes.add(plane);
|
||||
let material_handle = materials.add(StandardMaterial::from_color(Color::srgb(0.3, 0.6, 1.0)));
|
||||
|
||||
|
||||
let sphere = meshes.add(
|
||||
SphereMeshBuilder::new(1.0, SphereKind::Ico { subdivisions: 5 })
|
||||
.build()
|
||||
);
|
||||
|
||||
|
||||
|
||||
commands.entity(root.0).with_children(|parent| {
|
||||
|
||||
parent.spawn((
|
||||
Name::new("Planet"),
|
||||
Mesh3d(sphere),
|
||||
MeshMaterial3d(material_handle),
|
||||
GridCell::<i64>::ZERO,
|
||||
Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
|
||||
));
|
||||
});
|
||||
|
||||
}
|
||||
@ -1,12 +1,9 @@
|
||||
use bevy::app::AppExit;
|
||||
use bevy::input::ButtonInput;
|
||||
use bevy::input::mouse::{MouseMotion, MouseWheel};
|
||||
use bevy::prelude::{EventReader, EventWriter, KeyCode, Query, Res, ResMut, Resource, Time, Transform};
|
||||
use bevy::prelude::*;
|
||||
use bevy_egui::{egui, EguiContexts};
|
||||
use bevy_window::Window;
|
||||
use crate::plugins::environment::systems::camera_system::CameraController;
|
||||
use crate::plugins::network::systems::database::DbConnectionResource;
|
||||
|
||||
pub fn console_system(
|
||||
mut ctxs: EguiContexts,
|
||||
mut state: ResMut<ConsoleState>,
|
||||
|
||||
@ -2,111 +2,85 @@ use bevy::app::AppExit;
|
||||
use bevy::input::ButtonInput;
|
||||
use bevy::input::mouse::{MouseMotion, MouseWheel};
|
||||
use bevy::math::{Quat, Vec3};
|
||||
use bevy::prelude::{EventReader, EventWriter, KeyCode, Query, Res, ResMut, Time, Transform};
|
||||
use bevy_window::{CursorGrabMode, Window};
|
||||
use random_word::Lang;
|
||||
use spacetimedb_sdk::DbContext;
|
||||
use crate::module_bindings::{set_name, set_position, spawn_entity, DbTransform, DbVector3, DbVector4, PlayerTableAccess};
|
||||
use bevy::prelude::*;
|
||||
use crate::plugins::environment::systems::camera_system::CameraController;
|
||||
use crate::plugins::network::systems::database::DbConnectionResource;
|
||||
|
||||
|
||||
fn move_by(
|
||||
mut q: Query<&mut Transform, With<CameraController>>,
|
||||
delta: Vec3,
|
||||
) {
|
||||
for mut t in &mut q {
|
||||
t.translation += delta;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Example system to input a camera using double-precision for position.
|
||||
pub fn flight_systems(
|
||||
time: Res<Time>,
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>, /*
|
||||
mouse_button_input: Res<ButtonInput<MouseButton>>,*/
|
||||
mut mouse_motion_events: EventReader<MouseMotion>,
|
||||
mut mouse_wheel_events: EventReader<MouseWheel>,
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
mut mouse_motion: EventReader<MouseMotion>,
|
||||
mut mouse_wheel: EventReader<MouseWheel>,
|
||||
mut windows: Query<&mut Window>,
|
||||
mut query: Query<(&mut Transform, &mut CameraController)>,
|
||||
mut app_exit_events: EventWriter<AppExit>,
|
||||
//mut ctx: ResMut<DbConnectionResource>,
|
||||
// all camera entities carry this tag
|
||||
mut xforms: Query<&mut Transform, With<CameraController>>,
|
||||
mut ctrls: Query<&mut CameraController>,
|
||||
mut exit_ev: EventWriter<AppExit>,
|
||||
) {
|
||||
|
||||
let mut window = windows.single_mut();
|
||||
let (mut transform, mut controller) = query.single_mut();
|
||||
//------------------------------------------------------------
|
||||
// 0) Early-out if no camera
|
||||
//------------------------------------------------------------
|
||||
if xforms.is_empty() { return; }
|
||||
|
||||
// ====================
|
||||
// 1) Handle Mouse Look
|
||||
// ====================
|
||||
if !window.cursor_options.visible {
|
||||
for event in mouse_motion_events.read() {
|
||||
// Adjust yaw/pitch in f32
|
||||
controller.yaw -= event.delta.x * controller.sensitivity;
|
||||
controller.pitch += event.delta.y * controller.sensitivity;
|
||||
controller.pitch = controller.pitch.clamp(-89.9, 89.9);
|
||||
//------------------------------------------------------------
|
||||
// 1) Rotation & speed input (borrow transform/controller)
|
||||
//------------------------------------------------------------
|
||||
let delta_vec3 = {
|
||||
let mut window = windows.single_mut();
|
||||
let mut transform = xforms.single_mut();
|
||||
let mut controller = ctrls.single_mut();
|
||||
|
||||
// Convert degrees to radians (f32)
|
||||
let yaw_radians = controller.yaw.to_radians();
|
||||
let pitch_radians = controller.pitch.to_radians();
|
||||
//------------------ mouse look --------------------------
|
||||
if !window.cursor_options.visible {
|
||||
for ev in mouse_motion.read() {
|
||||
controller.yaw -= ev.delta.x * controller.sensitivity;
|
||||
controller.pitch += ev.delta.y * controller.sensitivity;
|
||||
controller.pitch = controller.pitch.clamp(-89.9, 89.9);
|
||||
|
||||
// Build a double-precision quaternion from those angles
|
||||
let rot_yaw = Quat::from_axis_angle(Vec3::Y, yaw_radians);
|
||||
let rot_pitch = Quat::from_axis_angle(Vec3::X, -pitch_radians);
|
||||
let yaw = controller.yaw.to_radians();
|
||||
let pitch = controller.pitch.to_radians();
|
||||
|
||||
transform.rotation = rot_yaw * rot_pitch;
|
||||
let rot = Quat::from_rotation_y(yaw) * Quat::from_rotation_x(-pitch);
|
||||
transform.rotation = rot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
// 2) Adjust Movement Speed with Mouse Wheel
|
||||
// ====================
|
||||
for event in mouse_wheel_events.read() {
|
||||
let base_factor = 1.1_f32;
|
||||
let factor = base_factor.powf(event.y);
|
||||
controller.speed *= factor;
|
||||
if controller.speed < 0.01 {
|
||||
controller.speed = 0.01;
|
||||
//------------------ mouse wheel speed -------------------
|
||||
for ev in mouse_wheel.read() {
|
||||
controller.speed = (controller.speed * 1.1_f32.powf(ev.y)).max(0.01);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------ keyboard direction -----------------
|
||||
let mut dir = Vec3::ZERO;
|
||||
if keyboard.pressed(KeyCode::KeyW) { dir += *transform.forward(); }
|
||||
if keyboard.pressed(KeyCode::KeyS) { dir -= *transform.forward(); }
|
||||
if keyboard.pressed(KeyCode::KeyA) { dir -= *transform.right(); }
|
||||
if keyboard.pressed(KeyCode::KeyD) { dir += *transform.right(); }
|
||||
if keyboard.pressed(KeyCode::Space) { dir += *transform.up(); }
|
||||
if keyboard.pressed(KeyCode::ShiftLeft) ||
|
||||
keyboard.pressed(KeyCode::ShiftRight) { dir -= *transform.up(); }
|
||||
|
||||
// ====================
|
||||
// 3) Handle Keyboard Movement (WASD, Space, Shift)
|
||||
// ====================
|
||||
let mut direction = Vec3::ZERO;
|
||||
|
||||
// Forward/Back
|
||||
if keyboard_input.pressed(KeyCode::KeyW) {
|
||||
direction += transform.forward().as_vec3();
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyS) {
|
||||
direction -= transform.forward().as_vec3();
|
||||
}
|
||||
|
||||
// Left/Right
|
||||
if keyboard_input.pressed(KeyCode::KeyA) {
|
||||
direction -= transform.right().as_vec3();
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyD) {
|
||||
direction += transform.right().as_vec3();
|
||||
}
|
||||
|
||||
// Up/Down
|
||||
if keyboard_input.pressed(KeyCode::Space) {
|
||||
direction += transform.up().as_vec3();
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::ShiftLeft) || keyboard_input.pressed(KeyCode::ShiftRight) {
|
||||
direction -= transform.up().as_vec3();
|
||||
}
|
||||
|
||||
// Normalize direction if needed
|
||||
if direction.length_squared() > 0.0 {
|
||||
direction = direction.normalize();
|
||||
}
|
||||
|
||||
// Apply movement in double-precision
|
||||
let delta_seconds = time.delta_secs_f64();
|
||||
let distance = controller.speed as f64 * delta_seconds;
|
||||
transform.translation += direction * distance as f32;
|
||||
|
||||
/*ctx.0.reducers.set_position(DbVector3{
|
||||
x: transform.translation.x,
|
||||
y: transform.translation.y,
|
||||
z: transform.translation.z,
|
||||
}).expect("TODO: panic message");
|
||||
*/
|
||||
if dir.length_squared() > 0.0 { dir = dir.normalize(); }
|
||||
|
||||
//------------------ compute delta ----------------------
|
||||
let distance = controller.speed * time.delta_secs_f64() as f32;
|
||||
dir * distance
|
||||
}; // ⬅ scopes end here; mutable borrows are dropped
|
||||
|
||||
//------------------------------------------------------------
|
||||
// 2) Apply translation with the helper
|
||||
//------------------------------------------------------------
|
||||
move_by(xforms, delta_vec3);
|
||||
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
pub mod movement;
|
||||
pub mod flight;
|
||||
pub mod console;
|
||||
pub mod ui;
|
||||
pub mod network;
|
||||
pub mod ui;
|
||||
@ -1,51 +0,0 @@
|
||||
|
||||
use bevy::input::ButtonInput;
|
||||
use bevy::math::{EulerRot, Quat};
|
||||
use bevy::prelude::{KeyCode, Res, ResMut,};
|
||||
use random_word::Lang;
|
||||
use crate::module_bindings::{set_name, spawn_entity, spawn_rigidbody_entity, DbTransform, DbVector3, DbVector4, EntityType};
|
||||
use crate::plugins::network::systems::database::DbConnectionResource;
|
||||
|
||||
pub fn network_system(
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
ctx: ResMut<DbConnectionResource>,
|
||||
) {
|
||||
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::KeyQ) {
|
||||
let word = random_word::get(Lang::En);
|
||||
|
||||
ctx.0.reducers.set_name(word.to_string()).unwrap();
|
||||
}
|
||||
if keyboard_input.just_pressed(KeyCode::KeyE) {
|
||||
let rand_position = crate::helper::vector_helper::random_vec3(-10.0, 10.0);
|
||||
let rand_rotation = crate::helper::vector_helper::random_vec3(0.0, 10.0);
|
||||
let rand_rotation = Quat::from_euler(EulerRot::XYZ,rand_rotation.x,rand_rotation.y,rand_rotation.z).normalize();
|
||||
let rand_scale = crate::helper::vector_helper::random_vec3(0.1, 1.0);
|
||||
ctx.0.reducers.spawn_rigidbody_entity(DbTransform{
|
||||
position: DbVector3{
|
||||
x: rand_position.x,
|
||||
y: rand_position.y,
|
||||
z: rand_position.z,
|
||||
},
|
||||
rotation: DbVector4 {
|
||||
x: rand_rotation.x,
|
||||
y: rand_rotation.y,
|
||||
z: rand_rotation.z,
|
||||
w: rand_rotation.w,
|
||||
},
|
||||
|
||||
|
||||
|
||||
scale: DbVector3 {
|
||||
x: rand_scale.x,
|
||||
y: rand_scale.x,
|
||||
z: rand_scale.x,
|
||||
},
|
||||
},
|
||||
EntityType::Cube,
|
||||
DbVector3{ x:0.0, y:0.0, z:0.0}, 5.0, false).unwrap();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use bevy::app::AppExit;
|
||||
use bevy::input::ButtonInput;
|
||||
use bevy::prelude::{EventWriter, KeyCode, Query, Res,};
|
||||
use bevy_window::{CursorGrabMode, Window};
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::CursorGrabMode;
|
||||
|
||||
pub fn ui_system(
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
pub mod environment;
|
||||
pub mod ui;
|
||||
pub mod network;
|
||||
pub mod input;
|
||||
pub(crate) mod big_space;
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
pub(crate) mod systems;
|
||||
pub mod network_plugin;
|
||||
@ -1,15 +0,0 @@
|
||||
use bevy::app::{App, Plugin, Startup};
|
||||
use bevy::color::palettes::basic::{GREEN, YELLOW};
|
||||
use bevy::color::palettes::css::RED;
|
||||
use bevy::prelude::*;
|
||||
use crate::plugins::environment::systems::environment_system::*;
|
||||
use crate::plugins::network::systems::database::setup_database;
|
||||
use crate::plugins::network::systems::entities::*;
|
||||
|
||||
pub struct NetworkPlugin;
|
||||
impl Plugin for NetworkPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(PreStartup, setup_database);
|
||||
app.add_systems(PostUpdate, sync_entities_system);
|
||||
}
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
use bevy::log::{error, info};
|
||||
use spacetimedb_sdk::Status;
|
||||
use crate::module_bindings::{EventContext, Player, ReducerEventContext, RemoteDbContext};
|
||||
|
||||
/// Our `User::on_insert` callback:
|
||||
/// if the user is online, print a notification.
|
||||
pub fn on_user_inserted(_ctx: &EventContext, user: &Player) {
|
||||
if user.online {
|
||||
info!("User {} connected.", user_name_or_identity(user));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn user_name_or_identity(user: &Player) -> String {
|
||||
user.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| user.identity.to_hex().to_string())
|
||||
}
|
||||
|
||||
/// Our `User::on_update` callback:
|
||||
/// print a notification about name and status changes.
|
||||
pub fn on_user_updated(_ctx: &EventContext, old: &Player, new: &Player) {
|
||||
if old.name != new.name {
|
||||
info!(
|
||||
"User {} renamed to {}.",
|
||||
user_name_or_identity(old),
|
||||
user_name_or_identity(new)
|
||||
);
|
||||
}
|
||||
if old.online && !new.online {
|
||||
info!("User {} disconnected.", user_name_or_identity(new));
|
||||
}
|
||||
if !old.online && new.online {
|
||||
info!("User {} connected.", user_name_or_identity(new));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Our `on_set_name` callback: print a warning if the reducer failed.
|
||||
pub fn on_name_set(ctx: &ReducerEventContext, name: &String) {
|
||||
if let Status::Failed(err) = &ctx.event.status {
|
||||
error!("Failed to change name to {:?}: {}", name, err);
|
||||
}
|
||||
}
|
||||
|
||||
/// Our `on_send_message` callback: print a warning if the reducer failed.
|
||||
pub fn on_message_sent(ctx: &ReducerEventContext, text: &String) {
|
||||
if let Status::Failed(err) = &ctx.event.status {
|
||||
error!("Failed to send message {:?}: {}", text, err);
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
use spacetimedb_sdk::{credentials, Error, Identity};
|
||||
use crate::module_bindings::{DbConnection, ErrorContext};
|
||||
|
||||
|
||||
pub fn creds_store() -> credentials::File {
|
||||
credentials::File::new("token")
|
||||
}
|
||||
|
||||
/// Our `on_connect` callback: save our credentials to a file.
|
||||
pub fn on_connected(_ctx: &DbConnection, _identity: Identity, token: &str) {
|
||||
if let Err(e) = creds_store().save(token) {
|
||||
eprintln!("Failed to save credentials: {:?}", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Our `on_connect_error` callback: print the error, then exit the process.
|
||||
pub fn on_connect_error(_ctx: &ErrorContext, err: Error) {
|
||||
eprintln!("Connection error: {:?}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
/// Our `on_disconnect` callback: print a note, then exit the process.
|
||||
pub fn on_disconnected(_ctx: &ErrorContext, err: Option<Error>) {
|
||||
if let Some(err) = err {
|
||||
eprintln!("Disconnected: {}", err);
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
println!("Disconnected.");
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
use bevy::ecs::system::SystemState;
|
||||
use bevy::prelude::{Commands, DetectChanges, Mut, Res, ResMut, Resource, World};
|
||||
use bevy::utils::info;
|
||||
use spacetimedb_sdk::{credentials, DbContext, Error, Event, Identity, Status, Table, TableWithPrimaryKey};
|
||||
use crate::config::ServerConfig;
|
||||
use crate::module_bindings::*;
|
||||
use crate::plugins::network::systems::callbacks::*;
|
||||
use crate::plugins::network::systems::connection::*;
|
||||
use crate::plugins::network::systems::subscriptions::*;
|
||||
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct DbConnectionResource(pub(crate) DbConnection);
|
||||
|
||||
pub fn setup_database(mut commands: Commands, config: Res<crate::Config>) {
|
||||
// Call your connection function and insert the connection as a resource.
|
||||
let ctx = connect_to_db(config);
|
||||
register_callbacks(&ctx);
|
||||
subscribe_to_tables(&ctx);
|
||||
ctx.run_threaded();
|
||||
commands.insert_resource(DbConnectionResource(ctx));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Register subscriptions for all rows of both tables
|
||||
|
||||
fn connect_to_db(config: Res<crate::Config>) -> DbConnection {
|
||||
|
||||
println!("It's there: {:?}", &config.server);
|
||||
|
||||
DbConnection::builder()
|
||||
.on_connect(on_connected)
|
||||
.on_connect_error(on_connect_error)
|
||||
.on_disconnect( on_disconnected)
|
||||
.with_module_name(&config.server.database)
|
||||
.with_uri(&config.server.host)
|
||||
.build()
|
||||
.expect("Failed to connect")
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Register all the callbacks our app will use to respond to database events.
|
||||
fn register_callbacks(ctx: &DbConnection) {
|
||||
// When a new user joins, print a notification.
|
||||
ctx.db.player().on_insert(on_user_inserted);
|
||||
|
||||
// When a user's status changes, print a notification.
|
||||
ctx.db.player().on_update(on_user_updated);
|
||||
|
||||
// When we fail to set our name, print a warning.
|
||||
ctx.reducers.on_set_name(on_name_set);
|
||||
}
|
||||
|
||||
fn subscribe_to_tables(ctx: &DbConnection) {
|
||||
ctx.subscription_builder()
|
||||
.on_applied(on_sub_applied)
|
||||
.on_error(on_sub_error)
|
||||
.subscribe(["SELECT * FROM player", "SELECT * FROM entity", "SELECT * FROM rigidbody"]);
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
use std::collections::HashSet;
|
||||
use bevy::log::debug;
|
||||
use bevy::math::{NormedVectorSpace, Vec3};
|
||||
use bevy::pbr::{MeshMaterial3d, StandardMaterial};
|
||||
use bevy::prelude::{default, info, Bundle, Commands, Component, Cuboid, DespawnRecursiveExt, DetectChangesMut, Entity, GlobalTransform, Mesh, PbrBundle, Quat, Query, Res, ResMut, Sphere, Transform, TransformBundle};
|
||||
use bevy_asset::Assets;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::mesh::Mesh3d;
|
||||
use spacetimedb_sdk::{DbContext, Table};
|
||||
use crate::helper::math::RoundTo;
|
||||
use crate::module_bindings::{DbTransform, DbVector3, EntityTableAccess, EntityType, PlayerTableAccess};
|
||||
use crate::plugins::network::systems::database::DbConnectionResource;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct EntityDto {
|
||||
|
||||
pub entity_id: u32,
|
||||
|
||||
|
||||
pub transform: DbTransform,
|
||||
}
|
||||
|
||||
impl From<crate::module_bindings::Entity> for EntityDto {
|
||||
fn from(e: crate::module_bindings::Entity) -> Self {
|
||||
EntityDto {
|
||||
entity_id: e.entity_id,
|
||||
transform: e.transform,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// System that syncs DB entities with the Bevy ECS
|
||||
pub fn sync_entities_system(
|
||||
mut commands: Commands,
|
||||
db_resource: Res<DbConnectionResource>,
|
||||
|
||||
// We need the Entity handle for potential despawning,
|
||||
// plus mutable references if we want to update Transform/EntityDto
|
||||
mut query: Query<(Entity, &mut Transform, &mut GlobalTransform, &mut EntityDto)>,
|
||||
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
|
||||
let identity = db_resource.0.identity();
|
||||
let player = db_resource.0.db.player().identity().find(&identity);
|
||||
|
||||
// --- 1) Collect DB entities and build a set of IDs ---
|
||||
let db_entities = db_resource.0.db.entity();
|
||||
let db_ids: HashSet<u32> = db_entities.iter().map(|e| e.entity_id).collect();
|
||||
|
||||
// --- 2) For each DB entity, update or spawn in ECS ---
|
||||
for db_entity in db_entities.iter() {
|
||||
|
||||
if db_entity.entity_id == player.clone().unwrap().entity_id {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to find a matching ECS entity by entity_id
|
||||
if let Some((_, mut transform, mut global, mut dto)) =
|
||||
query.iter_mut().find(|(_, _, _, dto)| dto.entity_id == db_entity.entity_id)
|
||||
{
|
||||
// Update fields
|
||||
|
||||
// build the new local Transform
|
||||
let new_tf = Transform::from(db_entity.transform.clone());
|
||||
|
||||
// overwrite both components
|
||||
*transform = new_tf;
|
||||
*global = GlobalTransform::from(new_tf);
|
||||
|
||||
// keep your DTO in sync
|
||||
dto.transform = db_entity.transform.clone();
|
||||
|
||||
} else {
|
||||
// Not found in ECS, so spawn a new entity
|
||||
let debug_material = materials.add(StandardMaterial {
|
||||
// fill out any fields you want
|
||||
..default()
|
||||
});
|
||||
|
||||
// Pick a mesh based on the entity type
|
||||
let entity_type = match db_entity.entity_type {
|
||||
EntityType::Sphere => Mesh3d(meshes.add(Sphere::default())),
|
||||
EntityType::Cube => Mesh3d(meshes.add(Cuboid::default())),
|
||||
EntityType::Custom => todo!(),
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
let new_tf = Transform::from(db_entity.transform.clone());
|
||||
|
||||
commands.spawn((
|
||||
TransformBundle::from_transform(new_tf), // inserts BOTH Transform and GlobalTransform
|
||||
entity_type,
|
||||
MeshMaterial3d(debug_material),
|
||||
EntityDto::from(db_entity.clone()),
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// --- 3) Despawn any ECS entity that doesn't exist in the DB anymore ---
|
||||
for (entity,_, _, dto) in query.iter_mut() {
|
||||
if !db_ids.contains(&dto.entity_id) {
|
||||
// This ECS entity no longer matches anything in the DB => remove it
|
||||
commands.entity(entity).despawn_recursive();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
pub mod database;
|
||||
mod connection;
|
||||
mod callbacks;
|
||||
mod subscriptions;
|
||||
pub mod entities;
|
||||
@ -1,23 +0,0 @@
|
||||
use bevy::prelude::info;
|
||||
use spacetimedb_sdk::{Error, Table};
|
||||
use crate::module_bindings::{ErrorContext, PlayerTableAccess, SubscriptionEventContext};
|
||||
|
||||
/// Our `on_subscription_applied` callback:
|
||||
/// sort all past messages and print them in timestamp order.
|
||||
pub fn on_sub_applied(ctx: &SubscriptionEventContext) {
|
||||
|
||||
let mut players = ctx.db.player().iter().collect::<Vec<_>>();
|
||||
players.sort_by_key(|p| p.name.clone());
|
||||
for player in players {
|
||||
println!("Player {:?} online", player.name);
|
||||
}
|
||||
println!("Fully connected and all subscriptions applied.");
|
||||
println!("Use /name to set your name, or type a message!");
|
||||
}
|
||||
|
||||
/// Or `on_error` callback:
|
||||
/// print the error, then exit the process.
|
||||
pub fn on_sub_error(_ctx: &ErrorContext, err: Error) {
|
||||
eprintln!("Subscription failed: {}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
@ -1,7 +1,10 @@
|
||||
use crate::plugins::environment::systems::camera_system::CameraController;
|
||||
use bevy::asset::AssetServer;
|
||||
use bevy::math::DVec3;
|
||||
use bevy::prelude::*;
|
||||
|
||||
use big_space::prelude::*;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SpeedDisplay;
|
||||
|
||||
@ -38,25 +41,26 @@ pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
/// - camera f32 position
|
||||
/// - camera global f64 position
|
||||
/// - current chunk coordinate
|
||||
|
||||
pub fn update(
|
||||
// Query the camera controller so we can see its speed
|
||||
query_camera_controller: Query<&CameraController>,
|
||||
// We also query for the camera's f32 `Transform` and the double `DoubleTransform`
|
||||
camera_query: Query<(&Transform, &Camera)>,
|
||||
|
||||
// The UI text entity
|
||||
mut query_text: Query<&mut Text, With<SpeedDisplay>>,
|
||||
grids: Grids<'_, '_, i64>, // helper from big_space
|
||||
// we need the entity id, the cell & the local transform
|
||||
camera_q: Query<(Entity, &GridCell<i64>, &Transform, &CameraController)>,
|
||||
mut ui_q: Query<&mut Text, With<SpeedDisplay>>,
|
||||
) {
|
||||
let camera_controller = query_camera_controller.single();
|
||||
let (transform, _camera) = camera_query.single();
|
||||
let mut text = query_text.single_mut();
|
||||
let Ok((cam_ent, cell, tf, ctrl)) = camera_q.get_single() else { return };
|
||||
|
||||
// Format the string to show speed, positions, and chunk coords
|
||||
text.0 = format!(
|
||||
"\n Speed: {:.3}\n Position(f32): ({:.2},{:.2},{:.2})",
|
||||
camera_controller.speed,
|
||||
transform.translation.x,
|
||||
transform.translation.y,
|
||||
transform.translation.z,
|
||||
);
|
||||
}
|
||||
// grid that the camera lives in
|
||||
let Some(grid) = grids.parent_grid(cam_ent) else { return };
|
||||
|
||||
// absolute position in metres (f64)
|
||||
let pos = grid.grid_position_double(cell,tf);
|
||||
|
||||
if let Ok(mut text) = ui_q.get_single_mut() {
|
||||
text.0 = format!(
|
||||
"\n Speed: {:.3}\n Position(f64): ({:.2}, {:.2}, {:.2})",
|
||||
ctrl.speed,
|
||||
pos.x, pos.y, pos.z,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "spacetime-module"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
spacetimedb = "1.0.1"
|
||||
log = "0.4"
|
||||
@ -1,103 +0,0 @@
|
||||
mod types;
|
||||
|
||||
use std::time::Duration;
|
||||
use spacetimedb::{reducer, ReducerContext, ScheduleAt, Table, TimeDuration, Timestamp};
|
||||
use crate::types::entity::{entity, entity__TableHandle, Entity, EntityType};
|
||||
use crate::types::player::{player, player__TableHandle, Player};
|
||||
use crate::types::rigidbody::physics_step;
|
||||
use crate::types::types::{DBVector4, DbTransform, DbVector3};
|
||||
|
||||
#[spacetimedb::table(name = config, public)]
|
||||
pub struct Config {
|
||||
#[primary_key]
|
||||
pub id: u32,
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn test(ctx: &ReducerContext) -> Result<(), String> {
|
||||
log::debug!("This reducer was called by {}.", ctx.sender);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[reducer(client_connected)]
|
||||
// Called when a client connects to the SpacetimeDB
|
||||
pub fn client_connected(ctx: &ReducerContext) {
|
||||
if let Some(player) = ctx.db.player().identity().find(ctx.sender) {
|
||||
// If this is a returning player, i.e. we already have a `player` with this `Identity`,
|
||||
// set `online: true`, but leave `name` and `identity` unchanged.
|
||||
ctx.db.player().identity().update(Player { online: true, ..player });
|
||||
} else {
|
||||
// If this is a new player, create a `player` row for the `Identity`,
|
||||
// which is online, but hasn't set a name.
|
||||
let entity = ctx.db.entity().try_insert(Entity{
|
||||
entity_id: 0,
|
||||
transform: DbTransform{
|
||||
position: DbVector3{x: 0.0, y: 0.0, z: 10.0},
|
||||
rotation: DBVector4{x: 0.0, y: 0.0, z: 0.0, w: 1.0},
|
||||
scale: DbVector3{x: 1.0, y: 1.0, z: 1.0},
|
||||
},
|
||||
|
||||
entity_type: EntityType::Sphere,
|
||||
}).expect("TODO: panic message");
|
||||
|
||||
ctx.db.player().insert(Player {
|
||||
name: None,
|
||||
identity: ctx.sender,
|
||||
online: true,
|
||||
entity_id: entity.entity_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[reducer(client_disconnected)]
|
||||
// Called when a client disconnects from SpacetimeDB
|
||||
pub fn identity_disconnected(ctx: &ReducerContext) {
|
||||
|
||||
let entity: &entity__TableHandle = ctx.db.entity();
|
||||
|
||||
|
||||
|
||||
|
||||
if let Some(player_iter) = ctx.db.player().identity().find(ctx.sender) {
|
||||
|
||||
|
||||
|
||||
ctx.db.player().identity().update(Player { online: false, ..player_iter });
|
||||
ctx.db.entity().iter().find(|e| e.entity_id == player_iter.entity_id).iter().for_each(|e| {
|
||||
entity.delete(e.clone());
|
||||
})
|
||||
} else {
|
||||
// This branch should be unreachable,
|
||||
// as it doesn't make sense for a client to disconnect without connecting first.
|
||||
log::warn!("Disconnect event for unknown Player with identity {:?}", ctx.sender);
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer(init)]
|
||||
pub fn init(ctx: &ReducerContext) -> Result<(), String> {
|
||||
log::info!("Initializing...");
|
||||
ctx.db.config().try_insert(Config {
|
||||
id: 0,
|
||||
})?;
|
||||
|
||||
|
||||
ctx.db.physics_timer().try_insert(PhysicsTimer {
|
||||
scheduled_id: 0,
|
||||
scheduled_at: ScheduleAt::Interval(Duration::from_millis(50).into()),
|
||||
last_update_ts: ctx.timestamp,
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[spacetimedb::table(name = physics_timer, scheduled(physics_step))]
|
||||
struct PhysicsTimer {
|
||||
#[primary_key]
|
||||
#[auto_inc]
|
||||
scheduled_id: u64,
|
||||
scheduled_at: ScheduleAt,
|
||||
last_update_ts: Timestamp,
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
|
||||
use spacetimedb::{Identity, ReducerContext, SpacetimeType, Table};
|
||||
use crate::types::types::{DbVector3, DbTransform, DBVector4};
|
||||
|
||||
#[spacetimedb::table(name = entity, public)]
|
||||
#[derive(Debug, Clone, )]
|
||||
pub struct Entity {
|
||||
#[auto_inc]
|
||||
#[primary_key]
|
||||
pub entity_id: u32,
|
||||
pub transform: DbTransform,
|
||||
pub entity_type: EntityType,
|
||||
}
|
||||
|
||||
#[derive(SpacetimeType, Clone, Debug)]
|
||||
pub enum EntityType {
|
||||
Cube,
|
||||
Sphere,
|
||||
Custom
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn spawn_entity(ctx: &ReducerContext, transform: DbTransform) -> Result<(), String> {
|
||||
|
||||
ctx.db.entity().try_insert(Entity {
|
||||
entity_id: 0,
|
||||
transform,
|
||||
entity_type: EntityType::Cube,
|
||||
}).expect("TODO: panic message");
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
pub mod types;
|
||||
pub mod player;
|
||||
pub mod entity;
|
||||
pub mod rigidbody;
|
||||
@ -1,56 +0,0 @@
|
||||
use crate::types::entity::*;
|
||||
use std::io::empty;
|
||||
use spacetimedb::{reducer, Identity, ReducerContext, Table};
|
||||
use crate::types::types::{DbTransform, DbVector3};
|
||||
|
||||
#[spacetimedb::table(name = player, public)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Player {
|
||||
#[primary_key]
|
||||
pub identity: Identity,
|
||||
|
||||
#[index(btree)]
|
||||
pub entity_id: u32,
|
||||
|
||||
pub name: Option<String>,
|
||||
pub online: bool,
|
||||
}
|
||||
|
||||
#[reducer]
|
||||
/// Clients invoke this reducer to set their user names.
|
||||
pub fn set_name(ctx: &ReducerContext, name: String) -> Result<(), String> {
|
||||
let name = validate_name(name)?;
|
||||
if let Some(user) = ctx.db.player().identity().find(ctx.sender) {
|
||||
ctx.db.player().identity().update(Player { name: Some(name), ..user });
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Cannot set name for unknown user".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[reducer]
|
||||
/// Clients invoke this reducer to set their user names.
|
||||
pub fn set_position(ctx: &ReducerContext, position: DbVector3) -> Result<(), String> {
|
||||
if let Some(entity) = ctx.db.entity().iter().find(|e| e.entity_id == ctx.db.player().identity().find(ctx.sender).unwrap().entity_id) {
|
||||
ctx.db.entity().entity_id()
|
||||
.update(Entity{
|
||||
transform: DbTransform{
|
||||
position: position,
|
||||
..entity.transform
|
||||
},
|
||||
..entity
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Takes a name and checks if it's acceptable as a user's name.
|
||||
fn validate_name(name: String) -> Result<String, String> {
|
||||
if name.is_empty() {
|
||||
Err("Names must not be empty".to_string())
|
||||
} else {
|
||||
Ok(name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,103 +0,0 @@
|
||||
use std::time::Duration;
|
||||
use spacetimedb::{ReducerContext, Table, TimeDuration, Timestamp};
|
||||
use crate::{physics_timer, PhysicsTimer};
|
||||
use crate::types::entity::{entity, Entity, EntityType};
|
||||
use crate::types::types::{DbTransform, DbVector3};
|
||||
|
||||
#[spacetimedb::table(name = rigidbody, public)]
|
||||
#[derive(Debug, Clone, )]
|
||||
pub struct Rigidbody {
|
||||
#[auto_inc]
|
||||
#[primary_key]
|
||||
pub rigidbody_id: u32,
|
||||
|
||||
#[index(btree)]
|
||||
pub entity_id: u32,
|
||||
|
||||
|
||||
pub velocity: DbVector3,
|
||||
pub force: DbVector3,
|
||||
pub mass: f32,
|
||||
|
||||
pub is_fixed: bool,
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn spawn_rigidbody_entity(
|
||||
ctx: &ReducerContext,
|
||||
transform: DbTransform,
|
||||
entity_type: EntityType,
|
||||
velocity: DbVector3,
|
||||
mass: f32,
|
||||
is_fixed: bool,
|
||||
) -> Result<(), String> {
|
||||
// 1. insert a new Entity row
|
||||
let inserted_entity: Entity = ctx
|
||||
.db
|
||||
.entity()
|
||||
.insert(Entity {
|
||||
entity_id: 0,
|
||||
transform,
|
||||
entity_type,
|
||||
});
|
||||
|
||||
// 2. insert its corresponding Rigidbody row
|
||||
ctx.db
|
||||
.rigidbody()
|
||||
.insert(Rigidbody {
|
||||
rigidbody_id: 0,
|
||||
entity_id: inserted_entity.entity_id,
|
||||
velocity,
|
||||
force: DbVector3::zero(),
|
||||
mass,
|
||||
is_fixed,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
pub fn physics_step(ctx: &ReducerContext, mut timer: PhysicsTimer) -> Result<(), String> {
|
||||
let now = ctx.timestamp;
|
||||
let delta = now
|
||||
.time_duration_since(timer.last_update_ts)
|
||||
.unwrap_or(TimeDuration::from(Duration::from_millis(50)));
|
||||
let dt = delta.to_duration().unwrap().as_secs_f32();
|
||||
|
||||
// update timer state
|
||||
timer.last_update_ts = now;
|
||||
ctx.db.physics_timer().scheduled_id().update(timer);
|
||||
|
||||
// constant gravity
|
||||
let gravity = DbVector3::new(0.0, -9.81, 0.0);
|
||||
|
||||
// process all rigidbodies
|
||||
let bodies: Vec<Rigidbody> = ctx.db.rigidbody().iter().collect();
|
||||
for mut rb in bodies {
|
||||
if rb.is_fixed {
|
||||
continue;
|
||||
}
|
||||
|
||||
// apply gravity to force
|
||||
rb.force.add(&gravity.mul_scalar(rb.mass));
|
||||
|
||||
// integrate velocity
|
||||
let inv_mass = 1.0 / rb.mass;
|
||||
let accel = rb.force.mul_scalar(inv_mass);
|
||||
rb.velocity.add(&accel.mul_scalar(dt));
|
||||
|
||||
// update corresponding entity position
|
||||
if let Some(mut ent) = ctx.db.entity().iter().find(|e| e.entity_id == rb.entity_id){
|
||||
ent.transform.position.add(&rb.velocity.mul_scalar(dt));
|
||||
ctx.db.entity().entity_id().update(ent);
|
||||
}
|
||||
|
||||
// reset force and write back
|
||||
rb.force = DbVector3::zero();
|
||||
ctx.db.rigidbody().rigidbody_id().update(rb);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
use spacetimedb::SpacetimeType;
|
||||
|
||||
#[derive(SpacetimeType, Clone, Debug)]
|
||||
pub struct DbVector3 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
impl DbVector3 {
|
||||
pub(crate) fn new(x: f32, y: f32, z: f32) -> DbVector3 {
|
||||
DbVector3 { x, y, z }
|
||||
}
|
||||
pub(crate) fn zero() -> DbVector3 {
|
||||
DbVector3::new(0.0, 0.0, 0.0)
|
||||
}
|
||||
pub(crate) fn add(&mut self, other: &DbVector3) {
|
||||
self.x += other.x;
|
||||
self.y += other.y;
|
||||
self.z += other.z;
|
||||
}
|
||||
pub(crate) fn mul_scalar(&self, s: f32) -> DbVector3 {
|
||||
DbVector3::new(self.x * s, self.y * s, self.z * s)
|
||||
}
|
||||
}
|
||||
#[derive(SpacetimeType, Clone, Debug)]
|
||||
pub struct DBVector4 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
pub w: f32,
|
||||
}
|
||||
|
||||
|
||||
#[derive(SpacetimeType, Clone, Debug)]
|
||||
pub struct DbTransform {
|
||||
pub position: DbVector3,
|
||||
pub rotation: DBVector4,
|
||||
pub scale: DbVector3,
|
||||
}
|
||||
33
trim.bat
Normal file
33
trim.bat
Normal file
@ -0,0 +1,33 @@
|
||||
@echo off
|
||||
rem combine_all.bat – merge every *.rs and *.toml in this tree
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
rem Output files
|
||||
set "OUT_RS=target/combined.rs"
|
||||
set "OUT_TOML=target/combined.toml"
|
||||
|
||||
if exist "%OUT_RS%" del "%OUT_RS%"
|
||||
if exist "%OUT_TOML%" del "%OUT_TOML%"
|
||||
|
||||
rem -------- merge .rs --------
|
||||
for /f "delims=" %%F in ('
|
||||
dir /b /s /o:n *.rs ^| findstr /v /i "\\target\\"
|
||||
') do (
|
||||
echo /* --- %%~F --- */>>"%OUT_RS%"
|
||||
type "%%F" >>"%OUT_RS%"
|
||||
echo.>>"%OUT_RS%"
|
||||
)
|
||||
|
||||
rem ----- merge .toml -----
|
||||
for /f "delims=" %%F in ('
|
||||
dir /b /s /o:n *.toml ^| findstr /v /i "\\target\\"
|
||||
') do (
|
||||
rem TOML uses # for comments
|
||||
echo # --- %%~F --- >>"%OUT_TOML%"
|
||||
type "%%F" >>"%OUT_TOML%"
|
||||
echo.>>"%OUT_TOML%"
|
||||
)
|
||||
|
||||
echo Merged .rs files into %OUT_RS%
|
||||
echo Merged .toml files into %OUT_TOML%
|
||||
endlocal
|
||||
@ -1,2 +0,0 @@
|
||||
|
||||
watch spacetime logs network-game
|
||||
Loading…
x
Reference in New Issue
Block a user