init
This commit is contained in:
commit
f978b8af17
21 changed files with 4015 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
3314
Cargo.lock
generated
Normal file
3314
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
25
Cargo.toml
Normal file
25
Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "d5"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bevy = { version = "0.14.2", default-features = false, features = [
|
||||
"multi_threaded",
|
||||
] }
|
||||
bevy_eventwork = "0.9.0"
|
||||
itermore = { version = "0.7.1", features = ["array_chunks"] }
|
||||
rand = "0.8.5"
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
sysinfo = { version = "0.32.0", default-features = false, features = [
|
||||
"system",
|
||||
] }
|
||||
termion = "4.0.3"
|
||||
|
||||
[[bin]]
|
||||
name = "server"
|
||||
path = "src/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "client"
|
||||
path = "src/client.rs"
|
2
rustfmt.toml
Normal file
2
rustfmt.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
hard_tabs = true
|
||||
max_width = 120
|
19
src/client.rs
Normal file
19
src/client.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use bevy::{app::ScheduleRunnerPlugin, prelude::*};
|
||||
use lib_client::{display::DisplayPlugin, input::InputPlugin, net::NetPlugin};
|
||||
|
||||
mod common;
|
||||
mod lib_client;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
// Core.
|
||||
.add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_millis(10)))
|
||||
.add_plugins(NetPlugin)
|
||||
.add_plugins(DisplayPlugin)
|
||||
.add_plugins(InputPlugin)
|
||||
.run();
|
||||
}
|
31
src/common.rs
Normal file
31
src/common.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use bevy::math::IVec2;
|
||||
use bevy_eventwork::NetworkMessage;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod server_msg {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct MapUpdates(pub Vec<(IVec2, (char, char))>);
|
||||
netify!(MapUpdates);
|
||||
}
|
||||
|
||||
pub mod client_msg {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Move(pub IVec2);
|
||||
netify!(Move);
|
||||
}
|
||||
|
||||
macro_rules! netify {
|
||||
($name:ident) => {
|
||||
impl NetworkMessage for $name {
|
||||
const NAME: &'static str = stringify!($name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use netify;
|
||||
|
||||
pub const RENDER_DISTANCE: i32 = 12;
|
48
src/lib_client/display.rs
Normal file
48
src/lib_client/display.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_eventwork::{tcp::TcpProvider, AppNetworkMessage, NetworkData};
|
||||
|
||||
use termion::{clear, cursor::Goto};
|
||||
|
||||
use crate::common::{self, server_msg::MapUpdates};
|
||||
|
||||
pub struct DisplayPlugin;
|
||||
|
||||
impl Plugin for DisplayPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.listen_for_message::<MapUpdates, TcpProvider>()
|
||||
.add_systems(Update, handle_map_updates);
|
||||
}
|
||||
}
|
||||
|
||||
struct Display;
|
||||
|
||||
impl Display {
|
||||
fn draw(&self, pos: IVec2, (l, r): (char, char)) {
|
||||
let pos = pos + IVec2::new(RENDER_DISTANCE, RENDER_DISTANCE);
|
||||
let x = (pos.x * 2) + 1;
|
||||
let y = ((2 * RENDER_DISTANCE) - pos.y) + 1;
|
||||
print!("{}{}{}", Goto(x as _, y as _), l, r);
|
||||
}
|
||||
}
|
||||
|
||||
const RENDER_DISTANCE: i32 = common::RENDER_DISTANCE;
|
||||
const RENDER_DISTANCE_SQ: i32 = RENDER_DISTANCE * RENDER_DISTANCE;
|
||||
|
||||
fn handle_map_updates(mut updates: EventReader<NetworkData<MapUpdates>>) {
|
||||
for update in updates.read() {
|
||||
print!("{}", clear::All);
|
||||
for x in -RENDER_DISTANCE..RENDER_DISTANCE {
|
||||
for y in -RENDER_DISTANCE..RENDER_DISTANCE {
|
||||
let pos = IVec2::new(x, y);
|
||||
if pos.distance_squared(IVec2::ZERO) <= RENDER_DISTANCE_SQ {
|
||||
Display.draw(pos, ('.', ' '));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (pos, chars) in &update.0 {
|
||||
Display.draw(*pos, *chars);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
77
src/lib_client/input.rs
Normal file
77
src/lib_client/input.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use bevy_eventwork::{tcp::TcpProvider, ConnectionId, Network};
|
||||
use std::{
|
||||
io::{stdin, stdout, Stdout},
|
||||
process,
|
||||
sync::{mpsc, Arc, Mutex},
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
use termion::{
|
||||
cursor::{self, HideCursor},
|
||||
event::Key,
|
||||
input::TermRead,
|
||||
raw::{IntoRawMode, RawTerminal},
|
||||
};
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::common::client_msg::Move;
|
||||
|
||||
use super::net::Server;
|
||||
|
||||
pub struct InputPlugin;
|
||||
|
||||
impl Plugin for InputPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let (rec, _thread) = spawn_reader();
|
||||
let rec = Arc::new(Mutex::new(rec));
|
||||
app.insert_resource(InputReceiver(rec))
|
||||
.add_event::<KeyEvent>()
|
||||
.add_systems(Update, try_read_keys)
|
||||
.add_systems(Update, on_move);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct InputReceiver(Arc<Mutex<mpsc::Receiver<Key>>>);
|
||||
|
||||
#[derive(Debug, Event)]
|
||||
pub struct KeyEvent(Key);
|
||||
|
||||
fn spawn_reader() -> (mpsc::Receiver<Key>, JoinHandle<()>) {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let thread = thread::spawn(move || {
|
||||
let stdout = stdout().into_raw_mode().unwrap();
|
||||
println!("{}", cursor::Hide);
|
||||
let mut keys = stdin().keys();
|
||||
while let Some(Ok(key)) = keys.next() {
|
||||
if key == Key::Esc {
|
||||
println!("exitting.");
|
||||
println!("{}", cursor::Restore);
|
||||
drop(stdout);
|
||||
process::exit(0);
|
||||
}
|
||||
tx.send(key).ok();
|
||||
}
|
||||
});
|
||||
(rx, thread)
|
||||
}
|
||||
|
||||
fn try_read_keys(receiver: Res<InputReceiver>, mut writer: EventWriter<KeyEvent>) {
|
||||
let receiver = receiver.0.lock().expect("Poisoned threads doomed the state.");
|
||||
writer.send_batch(receiver.try_iter().map(KeyEvent));
|
||||
}
|
||||
|
||||
fn on_move(mut keys: EventReader<KeyEvent>, server: Res<Server>, net: Res<Network<TcpProvider>>) {
|
||||
if let Some(server) = server.0 {
|
||||
for key in keys.read() {
|
||||
let dir = match key.0 {
|
||||
Key::Char('z') => IVec2::Y,
|
||||
Key::Char('s') => -IVec2::Y,
|
||||
Key::Char('q') => -IVec2::X,
|
||||
Key::Char('d') => IVec2::X,
|
||||
_ => continue,
|
||||
};
|
||||
net.send_message(server, Move(dir)).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
3
src/lib_client/mod.rs
Normal file
3
src/lib_client/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod display;
|
||||
pub mod input;
|
||||
pub mod net;
|
50
src/lib_client/net.rs
Normal file
50
src/lib_client/net.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use bevy::{
|
||||
prelude::*,
|
||||
tasks::{TaskPool, TaskPoolBuilder},
|
||||
};
|
||||
use bevy_eventwork::{
|
||||
tcp::{NetworkSettings, TcpProvider},
|
||||
ConnectionId, EventworkPlugin, EventworkRuntime, Network, NetworkEvent,
|
||||
};
|
||||
use termion::clear;
|
||||
|
||||
pub struct NetPlugin;
|
||||
|
||||
impl Plugin for NetPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(EventworkPlugin::<TcpProvider, TaskPool>::default())
|
||||
.insert_resource(EventworkRuntime(TaskPoolBuilder::new().num_threads(2).build()))
|
||||
.insert_resource(NetworkSettings::default())
|
||||
.add_systems(Update, handle_net_event)
|
||||
.init_resource::<Server>()
|
||||
.add_systems(Startup, do_connect);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Resource, Default)]
|
||||
pub struct Server(pub Option<ConnectionId>);
|
||||
|
||||
fn handle_net_event(mut events: EventReader<NetworkEvent>, mut server: ResMut<Server>) {
|
||||
for event in events.read() {
|
||||
match event {
|
||||
NetworkEvent::Connected(connection_id) => {
|
||||
println!("Connected '{connection_id:?}'.");
|
||||
server.0 = Some(*connection_id);
|
||||
}
|
||||
NetworkEvent::Disconnected(connection_id) => {
|
||||
print!("{}", clear::All);
|
||||
println!("Disconnected '{connection_id:?}'.");
|
||||
panic!();
|
||||
}
|
||||
NetworkEvent::Error(network_error) => println!("Error '{network_error}'."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_connect(
|
||||
net: Res<Network<TcpProvider>>,
|
||||
settings: Res<NetworkSettings>,
|
||||
task_pool: Res<EventworkRuntime<TaskPool>>,
|
||||
) {
|
||||
net.connect(([0, 0, 0, 0], 9000).into(), &task_pool.0, &settings);
|
||||
}
|
24
src/lib_server/counter.rs
Normal file
24
src/lib_server/counter.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
pub struct CounterPlugin;
|
||||
|
||||
impl Plugin for CounterPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, sys_setup_counter)
|
||||
.add_systems(Update, sys_count);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
struct Counter(usize);
|
||||
|
||||
fn sys_count(mut query: Query<&mut Counter>) {
|
||||
for mut c in &mut query {
|
||||
println!("{c:?}");
|
||||
c.0 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn sys_setup_counter(mut commands: Commands) {
|
||||
commands.spawn(Counter(0));
|
||||
}
|
67
src/lib_server/display.rs
Normal file
67
src/lib_server/display.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use crate::common::{self, server_msg::MapUpdates};
|
||||
use bevy::{prelude::*, utils::hashbrown::HashSet};
|
||||
use bevy_eventwork::{tcp::TcpProvider, ConnectionId, Network};
|
||||
|
||||
use super::{
|
||||
physics::{update_physics_collisions, Pos},
|
||||
player::Player,
|
||||
};
|
||||
|
||||
pub struct DisplayPlugin;
|
||||
|
||||
impl Plugin for DisplayPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<RenderUpdate>()
|
||||
// .add_systems(Update, rerender_players)
|
||||
.add_systems(Update, on_updates.after(update_physics_collisions));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Display(pub (char, char));
|
||||
|
||||
#[derive(Debug, Event)]
|
||||
pub struct RenderUpdate(pub IVec2);
|
||||
|
||||
const RENDER_DISTANCE: i32 = common::RENDER_DISTANCE;
|
||||
const RENDER_DISTANCE_SQ: i32 = RENDER_DISTANCE * RENDER_DISTANCE;
|
||||
|
||||
fn on_updates(
|
||||
mut updates: EventReader<RenderUpdate>,
|
||||
players: Query<(&Player, &Pos)>,
|
||||
displayables: Query<(&Pos, &Display)>,
|
||||
net: Res<Network<TcpProvider>>,
|
||||
) {
|
||||
let mut to_render = HashSet::new();
|
||||
for RenderUpdate(event_pos) in updates.read() {
|
||||
for (Player(connection_id), player_pos) in &players {
|
||||
if player_pos.pos().distance_squared(*event_pos) <= RENDER_DISTANCE_SQ {
|
||||
to_render.insert((*connection_id, player_pos.pos()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (id, pos) in to_render {
|
||||
render(&net, id, pos, &displayables);
|
||||
}
|
||||
}
|
||||
|
||||
fn render(
|
||||
net: &Res<Network<TcpProvider>>,
|
||||
id: ConnectionId,
|
||||
player_pos: IVec2,
|
||||
displayables: &Query<(&Pos, &Display)>,
|
||||
) {
|
||||
let updates = displayables
|
||||
.iter()
|
||||
.filter(|(pos, _)| pos.pos().distance_squared(player_pos) <= RENDER_DISTANCE_SQ)
|
||||
.map(|(pos, Display(display))| (pos.pos() - player_pos, *display))
|
||||
.collect();
|
||||
net.send_message(id, MapUpdates(updates)).ok();
|
||||
}
|
||||
|
||||
fn rerender_players(players: Query<&Pos, With<Player>>, mut writer: EventWriter<RenderUpdate>) {
|
||||
for Pos(pos) in &players {
|
||||
writer.send(RenderUpdate(pos.as_ivec2()));
|
||||
}
|
||||
}
|
54
src/lib_server/map.rs
Normal file
54
src/lib_server/map.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use std::ops::Not;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use itermore::IterArrayChunks;
|
||||
|
||||
use super::{
|
||||
display::Display,
|
||||
physics::{Mass, Pos},
|
||||
};
|
||||
|
||||
fn spawn_block(cmd: &mut Commands, pos: IVec2, display: (char, char), mass: usize) {
|
||||
cmd.spawn((Pos(pos.as_vec2()), Mass(mass), Display(display)));
|
||||
}
|
||||
|
||||
fn str_to_struct(text: &str) -> Vec<(IVec2, (char, char), usize)> {
|
||||
text.lines()
|
||||
.enumerate()
|
||||
.flat_map(|(y, line)| {
|
||||
line.chars().array_chunks().enumerate().filter_map(move |(x, [l, r])| {
|
||||
(l == ' ' && r == ' ')
|
||||
.not()
|
||||
.then_some((IVec2::new(x as _, y as _), (l, r), mass_of((l, r))))
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn mass_of(tiles: (char, char)) -> usize {
|
||||
match tiles {
|
||||
('#', '#') => 100,
|
||||
('X', 'X') => 10,
|
||||
('[', ']') => 5,
|
||||
('(', ')') => 1,
|
||||
_ => 20,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_debug_map(mut cmd: Commands) {
|
||||
let raw = "
|
||||
()
|
||||
|
||||
########## ()
|
||||
()## ##
|
||||
## []
|
||||
## ##
|
||||
##########()
|
||||
|
||||
|
||||
";
|
||||
|
||||
for (pos, display, mass) in str_to_struct(raw) {
|
||||
spawn_block(&mut cmd, pos, display, mass);
|
||||
}
|
||||
}
|
27
src/lib_server/metrics.rs
Normal file
27
src/lib_server/metrics.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use bevy::prelude::*;
|
||||
use sysinfo::{CpuRefreshKind, RefreshKind, System};
|
||||
use termion::cursor;
|
||||
|
||||
use super::player::Player;
|
||||
|
||||
pub struct MetricsPlugin;
|
||||
|
||||
#[derive(Debug, Resource)]
|
||||
pub struct SysRes(System);
|
||||
|
||||
impl Plugin for MetricsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let sys = System::new_with_specifics(RefreshKind::new().with_cpu(CpuRefreshKind::new().with_cpu_usage()));
|
||||
app.add_systems(Update, show_metrics).insert_resource(SysRes(sys));
|
||||
}
|
||||
}
|
||||
|
||||
fn show_metrics(time: Res<Time>, mut sys: ResMut<SysRes>, players: Query<(), With<Player>>) {
|
||||
sys.0.refresh_all();
|
||||
let cpu = sys.0.global_cpu_usage();
|
||||
let player_count = players.iter().count();
|
||||
let time_delta = time.delta_seconds();
|
||||
|
||||
let up = cursor::Up(1);
|
||||
println!(" | players {player_count:2} | tick {time_delta:2.3} | cpu {cpu:2.1}{up} ");
|
||||
}
|
7
src/lib_server/mod.rs
Normal file
7
src/lib_server/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
pub mod counter;
|
||||
pub mod display;
|
||||
pub mod map;
|
||||
pub mod metrics;
|
||||
pub mod net;
|
||||
pub mod physics;
|
||||
pub mod player;
|
55
src/lib_server/net.rs
Normal file
55
src/lib_server/net.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use bevy::{
|
||||
prelude::*,
|
||||
tasks::{TaskPool, TaskPoolBuilder},
|
||||
};
|
||||
use bevy_eventwork::{
|
||||
tcp::{NetworkSettings, TcpProvider},
|
||||
EventworkPlugin, EventworkRuntime, Network, NetworkEvent,
|
||||
};
|
||||
|
||||
use crate::lib_server::player::{remove_player, spawn_player};
|
||||
|
||||
use super::{display::RenderUpdate, player::Player};
|
||||
|
||||
pub struct NetPlugin;
|
||||
|
||||
impl Plugin for NetPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(EventworkPlugin::<TcpProvider, TaskPool>::default())
|
||||
.insert_resource(EventworkRuntime(TaskPoolBuilder::new().num_threads(2).build()))
|
||||
.insert_resource(NetworkSettings::default())
|
||||
.add_systems(Update, handle_net_event)
|
||||
.add_systems(Startup, do_listen);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_net_event(
|
||||
mut cmd: Commands,
|
||||
mut events: EventReader<NetworkEvent>,
|
||||
mut render_update: EventWriter<RenderUpdate>,
|
||||
players: Query<(Entity, &Player)>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
match event {
|
||||
NetworkEvent::Connected(conn_id) => {
|
||||
println!("New connection, {conn_id:?}.");
|
||||
spawn_player(&mut cmd, *conn_id, IVec2::ZERO);
|
||||
render_update.send(RenderUpdate(IVec2::ZERO));
|
||||
}
|
||||
NetworkEvent::Disconnected(conn_id) => {
|
||||
println!("Lost connection, {conn_id:?}.");
|
||||
remove_player(&mut cmd, *conn_id, &players);
|
||||
}
|
||||
NetworkEvent::Error(network_error) => println!("Network failure '{network_error}'."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_listen(
|
||||
mut net: ResMut<Network<TcpProvider>>,
|
||||
settings: Res<NetworkSettings>,
|
||||
task_pool: Res<EventworkRuntime<TaskPool>>,
|
||||
) {
|
||||
net.listen(([0, 0, 0, 0], 9000).into(), &task_pool.0, &settings)
|
||||
.unwrap();
|
||||
}
|
97
src/lib_server/physics.rs
Normal file
97
src/lib_server/physics.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use super::{display::RenderUpdate, player};
|
||||
|
||||
pub struct PhysicsPlugin;
|
||||
|
||||
impl Plugin for PhysicsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
update_physics_forces.after(player::handle_move),
|
||||
update_physics_center.after(update_physics_forces),
|
||||
update_physics_collisions.after(update_physics_center),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Component, Clone)]
|
||||
pub struct Pos(pub Vec2);
|
||||
|
||||
impl Pos {
|
||||
pub fn fpos(&self) -> Vec2 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn pos_center(&self) -> Vec2 {
|
||||
self.pos().as_vec2()
|
||||
}
|
||||
|
||||
pub fn pos(&self) -> IVec2 {
|
||||
self.0.round().as_ivec2()
|
||||
}
|
||||
|
||||
pub fn dir_to_center(&self) -> Vec2 {
|
||||
self.pos_center() - self.fpos()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Mass(pub usize);
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Forces(Vec2);
|
||||
|
||||
impl Forces {
|
||||
pub fn zero() -> Self {
|
||||
Self::new(Vec2::ZERO)
|
||||
}
|
||||
pub fn new(forces: Vec2) -> Self {
|
||||
Self(forces)
|
||||
}
|
||||
pub fn add(&mut self, force: Vec2) {
|
||||
self.0 += force;
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_MOVEMENT_PER_TICK: f32 = 0.3;
|
||||
|
||||
fn update_physics_forces(mut blocks: Query<(&mut Pos, &mut Forces)>, mut updater: EventWriter<RenderUpdate>) {
|
||||
for (mut pos, mut forces) in &mut blocks {
|
||||
let old_pos = pos.clone();
|
||||
|
||||
let applied = forces.0;
|
||||
let applied = applied.clamp_length_max(MAX_MOVEMENT_PER_TICK);
|
||||
pos.0 += applied;
|
||||
forces.0 -= applied;
|
||||
|
||||
if old_pos.pos() != pos.pos() {
|
||||
updater.send(RenderUpdate(old_pos.pos()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_physics_collisions(mut blocks: Query<(&mut Pos, &Mass)>) {
|
||||
let mut combinations = blocks.iter_combinations_mut();
|
||||
while let Some([(mut a_pos, Mass(a_mass)), (mut b_pos, Mass(b_mass))]) = combinations.fetch_next() {
|
||||
if a_pos.pos() == b_pos.pos() {
|
||||
let a_to_b = (b_pos.fpos() - a_pos.fpos()).normalize_or(Vec2::Y);
|
||||
let total_mass = (a_mass + b_mass) as f32;
|
||||
let a_mass_frac = (*a_mass) as f32 / total_mass;
|
||||
let b_mass_frac = (*b_mass) as f32 / total_mass;
|
||||
let a_movement = (-a_to_b) * b_mass_frac;
|
||||
let b_movement = (a_to_b) * a_mass_frac;
|
||||
a_pos.0 += a_movement;
|
||||
b_pos.0 += b_movement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_physics_center(mut blocks: Query<(&Pos, &mut Forces)>) {
|
||||
for (pos, mut forces) in &mut blocks {
|
||||
let dir = pos.pos_center() - pos.0;
|
||||
forces.add(dir * 0.75);
|
||||
}
|
||||
}
|
74
src/lib_server/player.rs
Normal file
74
src/lib_server/player.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use crate::common::client_msg::Move;
|
||||
use bevy::prelude::*;
|
||||
use bevy_eventwork::{tcp::TcpProvider, AppNetworkMessage, ConnectionId, NetworkData};
|
||||
|
||||
use super::{
|
||||
display::{Display, RenderUpdate},
|
||||
physics::{Forces, Mass, Pos},
|
||||
};
|
||||
|
||||
pub struct PlayerPlugin;
|
||||
|
||||
impl Plugin for PlayerPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.listen_for_message::<Move, TcpProvider>()
|
||||
.add_event::<RenderUpdate>()
|
||||
.add_systems(Update, handle_move);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Player(pub ConnectionId);
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Dir(pub IVec2);
|
||||
|
||||
fn player_display_for_dir(dir: IVec2) -> (char, char) {
|
||||
match dir {
|
||||
IVec2::Y => ('&', '/'),
|
||||
IVec2::NEG_X => ('_', '&'),
|
||||
IVec2::X => ('&', '_'),
|
||||
_ => ('&', '\\'),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_player(cmd: &mut Commands, conn_id: ConnectionId, pos: IVec2) {
|
||||
cmd.spawn((
|
||||
Player(conn_id),
|
||||
Pos(pos.as_vec2()),
|
||||
Display(player_display_for_dir(IVec2::X)),
|
||||
Dir(IVec2::X),
|
||||
Forces::zero(),
|
||||
Mass(10),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn remove_player(cmd: &mut Commands, conn_id: ConnectionId, players: &Query<(Entity, &Player)>) {
|
||||
for (entity, player) in players {
|
||||
if player.0 == conn_id {
|
||||
cmd.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_move(
|
||||
mut messages: EventReader<NetworkData<Move>>,
|
||||
mut players: Query<(&Player, &mut Forces, &Pos, &mut Dir, &mut Display)>,
|
||||
) {
|
||||
for message in messages.read() {
|
||||
let conn_id = message.source();
|
||||
for (Player(id), mut forces, pos, mut dir, mut display) in &mut players {
|
||||
if conn_id == id {
|
||||
let dir_movement = message.0.as_vec2().normalize_or_zero();
|
||||
let added_force = dir_movement + pos.dir_to_center();
|
||||
forces.add(added_force);
|
||||
|
||||
let idir = dir_movement.as_ivec2();
|
||||
if !(idir == IVec2::ZERO) {
|
||||
dir.0 = idir;
|
||||
display.0 = player_display_for_dir(idir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
src/server.rs
Normal file
30
src/server.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
#![allow(dead_code)]
|
||||
#![allow(unstable_name_collisions)]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use bevy::{app::ScheduleRunnerPlugin, prelude::*, time::TimePlugin};
|
||||
use lib_server::{
|
||||
display::DisplayPlugin, map, metrics::MetricsPlugin, net::NetPlugin, physics::PhysicsPlugin, player::PlayerPlugin,
|
||||
};
|
||||
|
||||
mod common;
|
||||
mod lib_server;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
// Core.
|
||||
.add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_millis(50)))
|
||||
.add_plugins(TimePlugin)
|
||||
// World.
|
||||
.add_plugins(PhysicsPlugin)
|
||||
.add_plugins(DisplayPlugin)
|
||||
.add_plugins(NetPlugin)
|
||||
// Content.
|
||||
.add_plugins(PlayerPlugin)
|
||||
// Debug.
|
||||
.add_plugins(MetricsPlugin)
|
||||
// Startup.
|
||||
.add_systems(Startup, map::spawn_debug_map)
|
||||
.run();
|
||||
}
|
5
watch_client
Executable file
5
watch_client
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/bash
|
||||
set -e
|
||||
cd "$(dirname "$(realpath "$0")")"
|
||||
|
||||
regar -sc 'sleep 1s; cargo run --release --bin=client' src
|
5
watch_server
Executable file
5
watch_server
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/bash
|
||||
set -e
|
||||
cd "$(dirname "$(realpath "$0")")"
|
||||
|
||||
regar -sc 'cargo run --release --bin=server' src
|
Loading…
Add table
Add a link
Reference in a new issue