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 winit::event_loop::ControlFlow;
use std::net::SocketAddr;
use uflow::{
client::{Client, Config as ClientConfig, Event as ClientEvent},
EndpointConfig
};
use kubi_shared::networking::{
messages::ServerToClientMessage,
state::ClientJoinState,
client::ClientIdMap,
};
use crate::{
events::EventComponent,
control_flow::RequestExit,
world::tasks::ChunkTaskManager,
state::is_ingame_or_loading,
fixed_timestamp::FixedTimestamp
};
mod handshake;
mod world;
mod player;
use handshake::{
set_client_join_state_to_connected,
say_hello,
check_server_hello_response
};
use world::{
inject_network_responses_into_manager_queue,
send_block_place_events,
recv_block_place_events,
};
use player::{
init_client_map,
send_player_movement_events,
receive_player_movement_events,
receive_player_connect_events
};
const NET_TICKRATE: u16 = 33;
#[derive(Unique, Clone, Copy, PartialEq, Eq)]
pub enum GameType {
Singleplayer,
Muliplayer
}
#[derive(Unique, Clone, Copy, PartialEq, Eq)]
pub struct ServerAddress(pub SocketAddr);
#[derive(Unique)]
pub struct UdpClient(pub Client);
#[derive(Component)]
pub struct NetworkEvent(pub ClientEvent);
impl NetworkEvent {
///Checks if postcard-encoded message has a type
pub fn is_message_of_type<const T: u8>(&self) -> bool {
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);
fn connect_client(
storages: AllStoragesView
) {
log::info!("Creating client");
let address = storages.borrow::<UniqueView<ServerAddress>>().unwrap();
let client = Client::connect(address.0, ClientConfig {
endpoint_config: EndpointConfig {
active_timeout_ms: 10000,
keepalive: true,
keepalive_interval_ms: 5000,
..Default::default()
},
}).expect("Client connection failed");
storages.add_unique(UdpClient(client));
storages.add_unique(ClientJoinState::Disconnected);
}
fn poll_client(
mut client: UniqueViewMut<UdpClient>,
mut entities: EntitiesViewMut,
mut events: ViewMut<EventComponent>,
mut network_events: ViewMut<NetworkEvent>,
) {
entities.bulk_add_entity((
&mut events,
&mut network_events,
), client.0.step().map(|event| {
(EventComponent, NetworkEvent(event))
}));
}
fn flush_client(
mut client: UniqueViewMut<UdpClient>,
) {
client.0.flush();
}
fn handle_disconnect(
network_events: View<NetworkEvent>,
mut join_state: UniqueViewMut<ClientJoinState>
) {
for event in network_events.iter() {
if matches!(event.0, ClientEvent::Disconnect) {
log::warn!("Disconnected from server");
*join_state = ClientJoinState::Disconnected;
return;
}
}
}
pub fn update_networking() -> Workload {
(
init_client_map.run_if_missing_unique::<ClientIdMap>(),
connect_client.run_if_missing_unique::<UdpClient>(),
poll_client.into_workload().make_fixed(NET_TICKRATE, 0),
(
set_client_join_state_to_connected,
say_hello,
).into_sequential_workload().run_if(if_just_connected),
(
check_server_hello_response,
handle_disconnect,
).into_sequential_workload().run_if(is_join_state::<{ClientJoinState::Connected as u8}>),
(
(
receive_player_connect_events,
).into_workload(),
(
recv_block_place_events,
receive_player_movement_events,
).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>(),
).into_sequential_workload()
}
pub fn update_networking_late() -> Workload {
(
(
send_block_place_events,
send_player_movement_events,
).into_workload().run_if(is_join_state::<{ClientJoinState::Joined as u8}>),
flush_client.into_workload().make_fixed(NET_TICKRATE, 1)
).into_sequential_workload()
}
pub fn disconnect_on_exit(
exit: UniqueView<RequestExit>,
mut client: UniqueViewMut<UdpClient>,
) {
//TODO check if this works
if exit.0 {
if client.0.is_active() {
client.0.flush();
client.0.disconnect();
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>,
) -> bool {
network_events.iter().any(|event| matches!(&event.0, ClientEvent::Connect))
}
fn is_join_state<const STATE: u8>(
join_state: UniqueView<ClientJoinState>
) -> bool {
(*join_state as u8) == STATE
}
pub fn is_multiplayer(
game_type: UniqueView<GameType>
) -> bool {
*game_type == GameType::Muliplayer
}
pub fn is_singleplayer(
game_type: UniqueView<GameType>
) -> bool {
*game_type == GameType::Singleplayer
}
use shipyard::{Unique, AllStoragesView, UniqueView, UniqueViewMut, Workload, IntoWorkload, EntitiesViewMut, Component, ViewMut, SystemModificator, View, IntoIter, WorkloadModificator};
use winit::event_loop::ControlFlow;
use std::net::SocketAddr;
use uflow::{
client::{Client, Config as ClientConfig, Event as ClientEvent},
EndpointConfig
};
use kubi_shared::networking::{
messages::ServerToClientMessage,
state::ClientJoinState,
client::ClientIdMap,
};
use crate::{
events::EventComponent,
control_flow::RequestExit,
world::tasks::ChunkTaskManager,
state::is_ingame_or_loading,
fixed_timestamp::FixedTimestamp
};
mod handshake;
mod world;
mod player;
pub use handshake::ConnectionRejectionReason;
use handshake::{
set_client_join_state_to_connected,
say_hello,
check_server_hello_response,
check_server_fuck_off_response,
};
use world::{
inject_network_responses_into_manager_queue,
send_block_place_events,
recv_block_place_events,
};
use player::{
init_client_map,
send_player_movement_events,
receive_player_movement_events,
receive_player_connect_events
};
const NET_TICKRATE: u16 = 33;
#[derive(Unique, Clone, Copy, PartialEq, Eq)]
pub enum GameType {
Singleplayer,
Muliplayer
}
#[derive(Unique, Clone, Copy, PartialEq, Eq)]
pub struct ServerAddress(pub SocketAddr);
#[derive(Unique)]
pub struct UdpClient(pub Client);
#[derive(Component)]
pub struct NetworkEvent(pub ClientEvent);
impl NetworkEvent {
///Checks if postcard-encoded message has a type
pub fn is_message_of_type<const T: u8>(&self) -> bool {
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);
fn connect_client(
storages: AllStoragesView
) {
log::info!("Creating client");
let address = storages.borrow::<UniqueView<ServerAddress>>().unwrap();
let client = Client::connect(address.0, ClientConfig {
endpoint_config: EndpointConfig {
active_timeout_ms: 10000,
keepalive: true,
keepalive_interval_ms: 5000,
..Default::default()
},
}).expect("Client connection failed");
storages.add_unique(UdpClient(client));
storages.add_unique(ClientJoinState::Disconnected);
}
fn poll_client(
mut client: UniqueViewMut<UdpClient>,
mut entities: EntitiesViewMut,
mut events: ViewMut<EventComponent>,
mut network_events: ViewMut<NetworkEvent>,
) {
entities.bulk_add_entity((
&mut events,
&mut network_events,
), client.0.step().map(|event| {
(EventComponent, NetworkEvent(event))
}));
}
fn flush_client(
mut client: UniqueViewMut<UdpClient>,
) {
client.0.flush();
}
fn handle_disconnect(
network_events: View<NetworkEvent>,
mut join_state: UniqueViewMut<ClientJoinState>
) {
for event in network_events.iter() {
if matches!(event.0, ClientEvent::Disconnect) {
log::warn!("Disconnected from server");
*join_state = ClientJoinState::Disconnected;
return;
}
}
}
pub fn update_networking() -> Workload {
(
init_client_map.run_if_missing_unique::<ClientIdMap>(),
connect_client.run_if_missing_unique::<UdpClient>(),
poll_client.into_workload().make_fixed(NET_TICKRATE, 0),
(
set_client_join_state_to_connected,
say_hello,
).into_sequential_workload().run_if(if_just_connected),
(
check_server_hello_response,
check_server_fuck_off_response,
handle_disconnect,
).into_sequential_workload().run_if(is_join_state::<{ClientJoinState::Connected as u8}>),
(
(
receive_player_connect_events,
).into_workload(),
(
recv_block_place_events,
receive_player_movement_events,
).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>(),
).into_sequential_workload()
}
pub fn update_networking_late() -> Workload {
(
(
send_block_place_events,
send_player_movement_events,
).into_workload().run_if(is_join_state::<{ClientJoinState::Joined as u8}>),
flush_client.into_workload().make_fixed(NET_TICKRATE, 1)
).into_sequential_workload()
}
pub fn disconnect_on_exit(
exit: UniqueView<RequestExit>,
mut client: UniqueViewMut<UdpClient>,
) {
//TODO check if this works
if exit.0 {
if client.0.is_active() {
client.0.flush();
client.0.disconnect();
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>,
) -> bool {
network_events.iter().any(|event| matches!(&event.0, ClientEvent::Connect))
}
fn is_join_state<const STATE: u8>(
join_state: UniqueView<ClientJoinState>
) -> bool {
(*join_state as u8) == STATE
}
pub fn is_multiplayer(
game_type: UniqueView<GameType>
) -> bool {
*game_type == GameType::Muliplayer
}
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 uflow::{client::Event as ClientEvent, SendMode};
use kubi_shared::networking::{
messages::{ClientToServerMessage, ServerToClientMessage, ServerToClientMessageType},
state::ClientJoinState,
channels::Channel,
};
use crate::player::{spawn_local_player_multiplayer, spawn_remote_player_multiplayer};
use super::{UdpClient, NetworkEvent};
pub fn set_client_join_state_to_connected(
mut join_state: UniqueViewMut<ClientJoinState>
) {
log::info!("Setting ClientJoinState");
*join_state = ClientJoinState::Connected;
}
pub fn say_hello(
mut client: UniqueViewMut<UdpClient>,
) {
let username = "XxX-FishFucker-69420-XxX".into();
let password = None;
log::info!("Authenticating");
client.0.send(
postcard::to_allocvec(
&ClientToServerMessage::ClientHello { username, password }
).unwrap().into_boxed_slice(),
Channel::Auth as usize,
SendMode::Reliable
);
}
pub fn check_server_hello_response(
mut storages: AllStoragesViewMut,
) {
//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 {
return None
};
if !event.is_message_of_type::<{ServerToClientMessageType::ServerHello as u8}>() {
return None
}
let Ok(parsed_message) = postcard::from_bytes(data) else {
log::error!("Malformed message");
return None
};
let ServerToClientMessage::ServerHello { init } = parsed_message else {
unreachable!()
};
Some(init)
}) else { return };
// struct ClientInitData {
// client_id: ClientId,
// username: String,
// position: Vec3,
// velocity: Vec3,
// direction: Quat,
// health: Health,
// }
//Add components to main player
spawn_local_player_multiplayer(&mut storages, init.user);
//Init players
for init_data in init.users {
spawn_remote_player_multiplayer(&mut storages, init_data);
}
// Set state to connected
let mut join_state = storages.borrow::<UniqueViewMut<ClientJoinState>>().unwrap();
*join_state = ClientJoinState::Joined;
log::info!("Joined the server!");
}
use shipyard::{AllStoragesView, AllStoragesViewMut, IntoIter, Unique, UniqueView, UniqueViewMut, View};
use uflow::{client::Event as ClientEvent, SendMode};
use kubi_shared::networking::{
messages::{ClientToServerMessage, ServerToClientMessage, ServerToClientMessageType},
state::ClientJoinState,
channels::Channel,
};
use crate::player::{spawn_local_player_multiplayer, spawn_remote_player_multiplayer};
use super::{UdpClient, NetworkEvent};
#[derive(Unique)]
pub struct ConnectionRejectionReason {
pub reason: String,
}
pub fn set_client_join_state_to_connected(
mut join_state: UniqueViewMut<ClientJoinState>
) {
log::info!("Setting ClientJoinState");
*join_state = ClientJoinState::Connected;
}
pub fn say_hello(
mut client: UniqueViewMut<UdpClient>,
) {
let username = "XxX-FishFucker-69420-XxX".into();
let password = None;
log::info!("Authenticating");
client.0.send(
postcard::to_allocvec(
&ClientToServerMessage::ClientHello { username, password }
).unwrap().into_boxed_slice(),
Channel::Auth as usize,
SendMode::Reliable
);
}
pub fn check_server_hello_response(
mut storages: AllStoragesViewMut,
) {
//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 {
return None
};
if !event.is_message_of_type::<{ServerToClientMessageType::ServerHello as u8}>() {
return None
}
let Ok(parsed_message) = postcard::from_bytes(data) else {
log::error!("Malformed message");
return None
};
let ServerToClientMessage::ServerHello { init } = parsed_message else {
unreachable!()
};
Some(init)
}) else { return };
// struct ClientInitData {
// client_id: ClientId,
// username: String,
// position: Vec3,
// velocity: Vec3,
// direction: Quat,
// health: Health,
// }
//Add components to main player
spawn_local_player_multiplayer(&mut storages, init.user);
//Init players
for init_data in init.users {
spawn_remote_player_multiplayer(&mut storages, init_data);
}
// 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 std::mem::take;
#[derive(Unique, PartialEq, Eq, Default, Clone, Copy)]
pub enum GameState {
#[default]
Initial,
Connecting,
LoadingWorld,
InGame
}
#[derive(Unique, PartialEq, Eq, Default, Clone, Copy)]
pub struct NextState(pub Option<GameState>);
pub fn init_state(
all_storages: AllStoragesView,
) {
all_storages.add_unique(GameState::default());
all_storages.add_unique(NextState::default());
}
pub fn update_state(
mut state: UniqueViewMut<GameState>,
mut next: UniqueViewMut<NextState>,
) {
*state = take(&mut next.0).unwrap_or(*state);
}
pub fn is_changing_state(
state: UniqueView<NextState>
) -> bool {
state.0.is_some()
}
pub fn is_connecting(
state: UniqueView<GameState>
) -> bool {
*state == GameState::Connecting
}
pub fn is_ingame(
state: UniqueView<GameState>
) -> bool {
*state == GameState::InGame
}
pub fn is_loading(
state: UniqueView<GameState>
) -> bool {
matches!(*state, GameState::LoadingWorld)
}
pub fn is_ingame_or_loading(
state: UniqueView<GameState>
) -> bool {
matches!(*state, GameState::InGame | GameState::LoadingWorld)
}
use shipyard::{Unique, UniqueView, UniqueViewMut, AllStoragesView};
use std::mem::take;
#[derive(Unique, PartialEq, Eq, Default, Clone, Copy)]
pub enum GameState {
#[default]
Initial,
Connecting,
LoadingWorld,
InGame
}
#[derive(Unique, PartialEq, Eq, Default, Clone, Copy)]
pub struct NextState(pub Option<GameState>);
pub fn init_state(
all_storages: AllStoragesView,
) {
all_storages.add_unique(GameState::default());
all_storages.add_unique(NextState::default());
}
pub fn update_state(
mut state: UniqueViewMut<GameState>,
mut next: UniqueViewMut<NextState>,
) {
*state = take(&mut next.0).unwrap_or(*state);
}
pub fn is_changing_state(
state: UniqueView<NextState>
) -> bool {
state.0.is_some()
}
pub fn is_connecting(
state: UniqueView<GameState>
) -> bool {
*state == GameState::Connecting
}
pub fn is_ingame(
state: UniqueView<GameState>
) -> bool {
*state == GameState::InGame
}
pub fn is_loading(
state: UniqueView<GameState>
) -> bool {
matches!(*state, GameState::LoadingWorld)
}
pub fn is_ingame_or_loading(
state: UniqueView<GameState>
) -> bool {
matches!(*state, GameState::InGame | GameState::LoadingWorld)
}

View file

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