handle fuck off request

This commit is contained in:
griffi-gh 2024-02-21 04:21:47 +01:00
parent 8f5f8b07fb
commit d63966e618
4 changed files with 378 additions and 337 deletions

View file

@ -1,198 +1,201 @@
use shipyard::{Unique, AllStoragesView, UniqueView, UniqueViewMut, Workload, IntoWorkload, EntitiesViewMut, Component, ViewMut, SystemModificator, View, IntoIter, WorkloadModificator}; use shipyard::{Unique, AllStoragesView, UniqueView, UniqueViewMut, Workload, IntoWorkload, EntitiesViewMut, Component, ViewMut, SystemModificator, View, IntoIter, WorkloadModificator};
use winit::event_loop::ControlFlow; use winit::event_loop::ControlFlow;
use std::net::SocketAddr; use std::net::SocketAddr;
use uflow::{ use uflow::{
client::{Client, Config as ClientConfig, Event as ClientEvent}, client::{Client, Config as ClientConfig, Event as ClientEvent},
EndpointConfig EndpointConfig
}; };
use kubi_shared::networking::{ use kubi_shared::networking::{
messages::ServerToClientMessage, messages::ServerToClientMessage,
state::ClientJoinState, state::ClientJoinState,
client::ClientIdMap, client::ClientIdMap,
}; };
use crate::{ use crate::{
events::EventComponent, events::EventComponent,
control_flow::RequestExit, control_flow::RequestExit,
world::tasks::ChunkTaskManager, world::tasks::ChunkTaskManager,
state::is_ingame_or_loading, state::is_ingame_or_loading,
fixed_timestamp::FixedTimestamp fixed_timestamp::FixedTimestamp
}; };
mod handshake; mod handshake;
mod world; mod world;
mod player; mod player;
use handshake::{ pub use handshake::ConnectionRejectionReason;
set_client_join_state_to_connected, use handshake::{
say_hello, set_client_join_state_to_connected,
check_server_hello_response say_hello,
}; check_server_hello_response,
use world::{ check_server_fuck_off_response,
inject_network_responses_into_manager_queue, };
send_block_place_events, use world::{
recv_block_place_events, inject_network_responses_into_manager_queue,
}; send_block_place_events,
use player::{ recv_block_place_events,
init_client_map, };
send_player_movement_events, use player::{
receive_player_movement_events, init_client_map,
receive_player_connect_events send_player_movement_events,
}; receive_player_movement_events,
receive_player_connect_events
const NET_TICKRATE: u16 = 33; };
#[derive(Unique, Clone, Copy, PartialEq, Eq)] const NET_TICKRATE: u16 = 33;
pub enum GameType {
Singleplayer, #[derive(Unique, Clone, Copy, PartialEq, Eq)]
Muliplayer pub enum GameType {
} Singleplayer,
Muliplayer
#[derive(Unique, Clone, Copy, PartialEq, Eq)] }
pub struct ServerAddress(pub SocketAddr);
#[derive(Unique, Clone, Copy, PartialEq, Eq)]
#[derive(Unique)] pub struct ServerAddress(pub SocketAddr);
pub struct UdpClient(pub Client);
#[derive(Unique)]
#[derive(Component)] pub struct UdpClient(pub Client);
pub struct NetworkEvent(pub ClientEvent);
#[derive(Component)]
impl NetworkEvent { pub struct NetworkEvent(pub ClientEvent);
///Checks if postcard-encoded message has a type
pub fn is_message_of_type<const T: u8>(&self) -> bool { impl NetworkEvent {
let ClientEvent::Receive(data) = &self.0 else { return false }; ///Checks if postcard-encoded message has a type
if data.len() == 0 { return false } pub fn is_message_of_type<const T: u8>(&self) -> bool {
data[0] == T let ClientEvent::Receive(data) = &self.0 else { return false };
} if data.len() == 0 { return false }
} data[0] == T
}
#[derive(Component)] }
pub struct NetworkMessageEvent(pub ServerToClientMessage);
#[derive(Component)]
fn connect_client( pub struct NetworkMessageEvent(pub ServerToClientMessage);
storages: AllStoragesView
) { fn connect_client(
log::info!("Creating client"); storages: AllStoragesView
let address = storages.borrow::<UniqueView<ServerAddress>>().unwrap(); ) {
let client = Client::connect(address.0, ClientConfig { log::info!("Creating client");
endpoint_config: EndpointConfig { let address = storages.borrow::<UniqueView<ServerAddress>>().unwrap();
active_timeout_ms: 10000, let client = Client::connect(address.0, ClientConfig {
keepalive: true, endpoint_config: EndpointConfig {
keepalive_interval_ms: 5000, active_timeout_ms: 10000,
..Default::default() keepalive: true,
}, keepalive_interval_ms: 5000,
}).expect("Client connection failed"); ..Default::default()
storages.add_unique(UdpClient(client)); },
storages.add_unique(ClientJoinState::Disconnected); }).expect("Client connection failed");
} storages.add_unique(UdpClient(client));
storages.add_unique(ClientJoinState::Disconnected);
fn poll_client( }
mut client: UniqueViewMut<UdpClient>,
mut entities: EntitiesViewMut, fn poll_client(
mut events: ViewMut<EventComponent>, mut client: UniqueViewMut<UdpClient>,
mut network_events: ViewMut<NetworkEvent>, mut entities: EntitiesViewMut,
) { mut events: ViewMut<EventComponent>,
entities.bulk_add_entity(( mut network_events: ViewMut<NetworkEvent>,
&mut events, ) {
&mut network_events, entities.bulk_add_entity((
), client.0.step().map(|event| { &mut events,
(EventComponent, NetworkEvent(event)) &mut network_events,
})); ), client.0.step().map(|event| {
} (EventComponent, NetworkEvent(event))
}));
fn flush_client( }
mut client: UniqueViewMut<UdpClient>,
) { fn flush_client(
client.0.flush(); mut client: UniqueViewMut<UdpClient>,
} ) {
client.0.flush();
fn handle_disconnect( }
network_events: View<NetworkEvent>,
mut join_state: UniqueViewMut<ClientJoinState> fn handle_disconnect(
) { network_events: View<NetworkEvent>,
for event in network_events.iter() { mut join_state: UniqueViewMut<ClientJoinState>
if matches!(event.0, ClientEvent::Disconnect) { ) {
log::warn!("Disconnected from server"); for event in network_events.iter() {
*join_state = ClientJoinState::Disconnected; if matches!(event.0, ClientEvent::Disconnect) {
return; log::warn!("Disconnected from server");
} *join_state = ClientJoinState::Disconnected;
} return;
} }
}
pub fn update_networking() -> Workload { }
(
init_client_map.run_if_missing_unique::<ClientIdMap>(), pub fn update_networking() -> Workload {
connect_client.run_if_missing_unique::<UdpClient>(), (
poll_client.into_workload().make_fixed(NET_TICKRATE, 0), init_client_map.run_if_missing_unique::<ClientIdMap>(),
( connect_client.run_if_missing_unique::<UdpClient>(),
set_client_join_state_to_connected, poll_client.into_workload().make_fixed(NET_TICKRATE, 0),
say_hello, (
).into_sequential_workload().run_if(if_just_connected), set_client_join_state_to_connected,
( say_hello,
check_server_hello_response, ).into_sequential_workload().run_if(if_just_connected),
handle_disconnect, (
).into_sequential_workload().run_if(is_join_state::<{ClientJoinState::Connected as u8}>), check_server_hello_response,
( check_server_fuck_off_response,
( handle_disconnect,
receive_player_connect_events, ).into_sequential_workload().run_if(is_join_state::<{ClientJoinState::Connected as u8}>),
).into_workload(), (
( (
recv_block_place_events, receive_player_connect_events,
receive_player_movement_events, ).into_workload(),
).into_workload() (
).into_sequential_workload().run_if(is_join_state::<{ClientJoinState::Joined as u8}>).run_if(is_ingame_or_loading), recv_block_place_events,
inject_network_responses_into_manager_queue.run_if(is_ingame_or_loading).skip_if_missing_unique::<ChunkTaskManager>(), receive_player_movement_events,
).into_sequential_workload() ).into_workload()
} ).into_sequential_workload().run_if(is_join_state::<{ClientJoinState::Joined as u8}>).run_if(is_ingame_or_loading),
inject_network_responses_into_manager_queue.run_if(is_ingame_or_loading).skip_if_missing_unique::<ChunkTaskManager>(),
pub fn update_networking_late() -> Workload { ).into_sequential_workload()
( }
(
send_block_place_events, pub fn update_networking_late() -> Workload {
send_player_movement_events, (
).into_workload().run_if(is_join_state::<{ClientJoinState::Joined as u8}>), (
flush_client.into_workload().make_fixed(NET_TICKRATE, 1) send_block_place_events,
).into_sequential_workload() send_player_movement_events,
} ).into_workload().run_if(is_join_state::<{ClientJoinState::Joined as u8}>),
flush_client.into_workload().make_fixed(NET_TICKRATE, 1)
pub fn disconnect_on_exit( ).into_sequential_workload()
exit: UniqueView<RequestExit>, }
mut client: UniqueViewMut<UdpClient>,
) { pub fn disconnect_on_exit(
//TODO check if this works exit: UniqueView<RequestExit>,
if exit.0 { mut client: UniqueViewMut<UdpClient>,
if client.0.is_active() { ) {
client.0.flush(); //TODO check if this works
client.0.disconnect(); if exit.0 {
while client.0.is_active() { client.0.step().for_each(|_|()); } if client.0.is_active() {
log::info!("Client disconnected"); client.0.flush();
} else { client.0.disconnect();
log::info!("Client inactive") while client.0.is_active() { client.0.step().for_each(|_|()); }
} log::info!("Client disconnected");
} } else {
} log::info!("Client inactive")
}
// conditions }
}
fn if_just_connected(
network_events: View<NetworkEvent>, // conditions
) -> bool {
network_events.iter().any(|event| matches!(&event.0, ClientEvent::Connect)) fn if_just_connected(
} network_events: View<NetworkEvent>,
) -> bool {
fn is_join_state<const STATE: u8>( network_events.iter().any(|event| matches!(&event.0, ClientEvent::Connect))
join_state: UniqueView<ClientJoinState> }
) -> bool {
(*join_state as u8) == STATE fn is_join_state<const STATE: u8>(
} join_state: UniqueView<ClientJoinState>
) -> bool {
pub fn is_multiplayer( (*join_state as u8) == STATE
game_type: UniqueView<GameType> }
) -> bool {
*game_type == GameType::Muliplayer pub fn is_multiplayer(
} game_type: UniqueView<GameType>
) -> bool {
pub fn is_singleplayer( *game_type == GameType::Muliplayer
game_type: UniqueView<GameType> }
) -> bool {
*game_type == GameType::Singleplayer pub fn is_singleplayer(
} game_type: UniqueView<GameType>
) -> bool {
*game_type == GameType::Singleplayer
}

View file

@ -1,76 +1,111 @@
use shipyard::{UniqueViewMut, View, IntoIter, AllStoragesViewMut}; use shipyard::{AllStoragesView, AllStoragesViewMut, IntoIter, Unique, UniqueView, UniqueViewMut, View};
use uflow::{client::Event as ClientEvent, SendMode}; use uflow::{client::Event as ClientEvent, SendMode};
use kubi_shared::networking::{ use kubi_shared::networking::{
messages::{ClientToServerMessage, ServerToClientMessage, ServerToClientMessageType}, messages::{ClientToServerMessage, ServerToClientMessage, ServerToClientMessageType},
state::ClientJoinState, state::ClientJoinState,
channels::Channel, channels::Channel,
}; };
use crate::player::{spawn_local_player_multiplayer, spawn_remote_player_multiplayer}; use crate::player::{spawn_local_player_multiplayer, spawn_remote_player_multiplayer};
use super::{UdpClient, NetworkEvent}; use super::{UdpClient, NetworkEvent};
pub fn set_client_join_state_to_connected( #[derive(Unique)]
mut join_state: UniqueViewMut<ClientJoinState> pub struct ConnectionRejectionReason {
) { pub reason: String,
log::info!("Setting ClientJoinState"); }
*join_state = ClientJoinState::Connected;
} pub fn set_client_join_state_to_connected(
mut join_state: UniqueViewMut<ClientJoinState>
pub fn say_hello( ) {
mut client: UniqueViewMut<UdpClient>, log::info!("Setting ClientJoinState");
) { *join_state = ClientJoinState::Connected;
let username = "XxX-FishFucker-69420-XxX".into(); }
let password = None;
log::info!("Authenticating"); pub fn say_hello(
client.0.send( mut client: UniqueViewMut<UdpClient>,
postcard::to_allocvec( ) {
&ClientToServerMessage::ClientHello { username, password } let username = "XxX-FishFucker-69420-XxX".into();
).unwrap().into_boxed_slice(), let password = None;
Channel::Auth as usize, log::info!("Authenticating");
SendMode::Reliable client.0.send(
); postcard::to_allocvec(
} &ClientToServerMessage::ClientHello { username, password }
).unwrap().into_boxed_slice(),
pub fn check_server_hello_response( Channel::Auth as usize,
mut storages: AllStoragesViewMut, SendMode::Reliable
) { );
//Check if we got the message and extract the init data from it }
let Some(init) = storages.borrow::<View<NetworkEvent>>().unwrap().iter().find_map(|event| {
let ClientEvent::Receive(data) = &event.0 else { pub fn check_server_hello_response(
return None mut storages: AllStoragesViewMut,
}; ) {
if !event.is_message_of_type::<{ServerToClientMessageType::ServerHello as u8}>() { //Check if we got the message and extract the init data from it
return None let Some(init) = storages.borrow::<View<NetworkEvent>>().unwrap().iter().find_map(|event| {
} let ClientEvent::Receive(data) = &event.0 else {
let Ok(parsed_message) = postcard::from_bytes(data) else { return None
log::error!("Malformed message"); };
return None if !event.is_message_of_type::<{ServerToClientMessageType::ServerHello as u8}>() {
}; return None
let ServerToClientMessage::ServerHello { init } = parsed_message else { }
unreachable!() let Ok(parsed_message) = postcard::from_bytes(data) else {
}; log::error!("Malformed message");
Some(init) return None
}) else { return }; };
let ServerToClientMessage::ServerHello { init } = parsed_message else {
// struct ClientInitData { unreachable!()
// client_id: ClientId, };
// username: String, Some(init)
// position: Vec3, }) else { return };
// velocity: Vec3,
// direction: Quat, // struct ClientInitData {
// health: Health, // client_id: ClientId,
// } // username: String,
// position: Vec3,
//Add components to main player // velocity: Vec3,
spawn_local_player_multiplayer(&mut storages, init.user); // direction: Quat,
// health: Health,
//Init players // }
for init_data in init.users {
spawn_remote_player_multiplayer(&mut storages, init_data); //Add components to main player
} spawn_local_player_multiplayer(&mut storages, init.user);
// Set state to connected //Init players
let mut join_state = storages.borrow::<UniqueViewMut<ClientJoinState>>().unwrap(); for init_data in init.users {
*join_state = ClientJoinState::Joined; spawn_remote_player_multiplayer(&mut storages, init_data);
}
log::info!("Joined the server!");
} // Set state to connected
let mut join_state = storages.borrow::<UniqueViewMut<ClientJoinState>>().unwrap();
*join_state = ClientJoinState::Joined;
log::info!("Joined the server!");
}
pub fn check_server_fuck_off_response(
storages: AllStoragesView,
) {
//Check if we got the message and extract the init data from it
let Some(reason) = storages.borrow::<View<NetworkEvent>>().unwrap().iter().find_map(|event| {
let ClientEvent::Receive(data) = &event.0 else {
return None
};
if !event.is_message_of_type::<{ServerToClientMessageType::ServerFuckOff as u8}>() {
return None
}
let Ok(parsed_message) = postcard::from_bytes(data) else {
log::error!("Malformed message");
return None
};
let ServerToClientMessage::ServerFuckOff { reason } = parsed_message else {
unreachable!()
};
Some(reason)
}) else { return };
let mut client = storages.borrow::<UniqueViewMut<UdpClient>>().unwrap();
client.0.disconnect_now();
let mut join_state = storages.borrow::<UniqueViewMut<ClientJoinState>>().unwrap();
*join_state = ClientJoinState::Disconnected;
storages.add_unique(ConnectionRejectionReason { reason });
}

View file

@ -1,58 +1,58 @@
use shipyard::{Unique, UniqueView, UniqueViewMut, AllStoragesView}; use shipyard::{Unique, UniqueView, UniqueViewMut, AllStoragesView};
use std::mem::take; use std::mem::take;
#[derive(Unique, PartialEq, Eq, Default, Clone, Copy)] #[derive(Unique, PartialEq, Eq, Default, Clone, Copy)]
pub enum GameState { pub enum GameState {
#[default] #[default]
Initial, Initial,
Connecting, Connecting,
LoadingWorld, LoadingWorld,
InGame InGame
} }
#[derive(Unique, PartialEq, Eq, Default, Clone, Copy)] #[derive(Unique, PartialEq, Eq, Default, Clone, Copy)]
pub struct NextState(pub Option<GameState>); pub struct NextState(pub Option<GameState>);
pub fn init_state( pub fn init_state(
all_storages: AllStoragesView, all_storages: AllStoragesView,
) { ) {
all_storages.add_unique(GameState::default()); all_storages.add_unique(GameState::default());
all_storages.add_unique(NextState::default()); all_storages.add_unique(NextState::default());
} }
pub fn update_state( pub fn update_state(
mut state: UniqueViewMut<GameState>, mut state: UniqueViewMut<GameState>,
mut next: UniqueViewMut<NextState>, mut next: UniqueViewMut<NextState>,
) { ) {
*state = take(&mut next.0).unwrap_or(*state); *state = take(&mut next.0).unwrap_or(*state);
} }
pub fn is_changing_state( pub fn is_changing_state(
state: UniqueView<NextState> state: UniqueView<NextState>
) -> bool { ) -> bool {
state.0.is_some() state.0.is_some()
} }
pub fn is_connecting( pub fn is_connecting(
state: UniqueView<GameState> state: UniqueView<GameState>
) -> bool { ) -> bool {
*state == GameState::Connecting *state == GameState::Connecting
} }
pub fn is_ingame( pub fn is_ingame(
state: UniqueView<GameState> state: UniqueView<GameState>
) -> bool { ) -> bool {
*state == GameState::InGame *state == GameState::InGame
} }
pub fn is_loading( pub fn is_loading(
state: UniqueView<GameState> state: UniqueView<GameState>
) -> bool { ) -> bool {
matches!(*state, GameState::LoadingWorld) matches!(*state, GameState::LoadingWorld)
} }
pub fn is_ingame_or_loading( pub fn is_ingame_or_loading(
state: UniqueView<GameState> state: UniqueView<GameState>
) -> bool { ) -> bool {
matches!(*state, GameState::InGame | GameState::LoadingWorld) matches!(*state, GameState::InGame | GameState::LoadingWorld)
} }

View file

@ -4,7 +4,7 @@ use shipyard::{IntoWorkload, NonSendSync, UniqueView, UniqueViewMut, Workload};
use crate::{ use crate::{
hui_integration::UiState, hui_integration::UiState,
loading_screen::loading_screen_base, loading_screen::loading_screen_base,
networking::ServerAddress, networking::{ConnectionRejectionReason, ServerAddress},
prefabs::UiFontPrefab, prefabs::UiFontPrefab,
rendering::WindowSize, rendering::WindowSize,
state::{GameState, NextState} state::{GameState, NextState}
@ -12,6 +12,8 @@ use crate::{
fn render_connecting_ui( fn render_connecting_ui(
addr: UniqueView<ServerAddress>, addr: UniqueView<ServerAddress>,
rejection: Option<UniqueView<ConnectionRejectionReason>>,
join_state: UniqueView<ClientJoinState>,
mut ui: NonSendSync<UniqueViewMut<UiState>>, mut ui: NonSendSync<UniqueViewMut<UiState>>,
font: UniqueView<UiFontPrefab>, font: UniqueView<UiFontPrefab>,
size: UniqueView<WindowSize>, size: UniqueView<WindowSize>,
@ -19,10 +21,11 @@ fn render_connecting_ui(
ui.hui.add( ui.hui.add(
loading_screen_base(vec![ loading_screen_base(vec![
Box::new(Text { Box::new(Text {
text: format!( text: match (rejection, *join_state) {
"Connecting to {}...", (Some(err), _) => format!("Connection rejected by {}\n\n{}", addr.0, err.reason).into(),
addr.0, (_, ClientJoinState::Disconnected) => format!("Lost connection to {}", addr.0).into(),
).into(), _ => format!("Connecting to {}...", addr.0).into(),
},
font: font.0, font: font.0,
text_size: 16, text_size: 16,
..Default::default() ..Default::default()