* make menu use actual soundsettings values * allow changing volume in pause menu * persist saving settings
212 lines
6.1 KiB
Rust
212 lines
6.1 KiB
Rust
use crate::{
|
|
GameState,
|
|
config::NetworkingConfig,
|
|
protocol::{
|
|
ClientEnteredPlaying, TbMapEntityId, TbMapEntityMapping, messages::DespawnTbMapEntity,
|
|
},
|
|
tb_entities::{Movable, Platform, PlatformTarget},
|
|
};
|
|
use avian3d::prelude::{
|
|
Collider, ColliderAabb, ColliderDensity, ColliderMarker, ColliderOf, ColliderTransform,
|
|
CollisionEventsEnabled, CollisionLayers, Sensor,
|
|
};
|
|
use bevy::{ecs::bundle::BundleFromComponents, prelude::*, scene::SceneInstance};
|
|
use bevy_replicon::{
|
|
client::{ClientSystems, confirm_history::ConfirmHistory},
|
|
prelude::{ClientState, ClientTriggerExt, RepliconChannels},
|
|
};
|
|
use bevy_replicon_renet::{
|
|
RenetChannelsExt,
|
|
netcode::{ClientAuthentication, NetcodeClientTransport, NetcodeError},
|
|
renet::{ConnectionConfig, RenetClient},
|
|
};
|
|
use bevy_trenchbroom::geometry::Brushes;
|
|
use std::{
|
|
net::{Ipv4Addr, UdpSocket},
|
|
time::SystemTime,
|
|
};
|
|
|
|
pub mod audio;
|
|
pub mod backpack;
|
|
pub mod control;
|
|
pub mod debug;
|
|
pub mod enemy;
|
|
pub mod heal_effect;
|
|
pub mod player;
|
|
mod settings;
|
|
pub mod setup;
|
|
pub mod steam;
|
|
pub mod ui;
|
|
|
|
pub fn plugin(app: &mut App) {
|
|
app.add_plugins((
|
|
backpack::plugin,
|
|
control::plugin,
|
|
debug::plugin,
|
|
enemy::plugin,
|
|
heal_effect::plugin,
|
|
player::plugin,
|
|
setup::plugin,
|
|
audio::plugin,
|
|
steam::plugin,
|
|
ui::plugin,
|
|
settings::plugin,
|
|
));
|
|
|
|
app.add_systems(
|
|
OnEnter(GameState::Connecting),
|
|
connect_to_server.run_if(|config: Res<NetworkingConfig>| config.server.is_some()),
|
|
);
|
|
app.add_systems(Update, despawn_absent_map_entities);
|
|
app.add_systems(
|
|
PreUpdate,
|
|
(migrate_remote_entities, ApplyDeferred)
|
|
.chain()
|
|
.after(ClientSystems::Receive),
|
|
);
|
|
|
|
app.add_systems(OnEnter(ClientState::Connected), on_connected_state);
|
|
app.add_systems(OnExit(ClientState::Connected), on_disconnect);
|
|
}
|
|
|
|
//
|
|
// Client logic
|
|
//
|
|
|
|
fn on_connected_state(mut commands: Commands, mut game_state: ResMut<NextState<GameState>>) {
|
|
info!("sent entered playing signal");
|
|
commands.client_trigger(ClientEnteredPlaying);
|
|
game_state.set(GameState::Playing);
|
|
}
|
|
|
|
fn on_disconnect() {
|
|
info!("disconnected from the server");
|
|
}
|
|
|
|
//
|
|
// Renet
|
|
//
|
|
|
|
fn connect_to_server(
|
|
mut commands: Commands,
|
|
config: Res<NetworkingConfig>,
|
|
channels: Res<RepliconChannels>,
|
|
) -> Result {
|
|
let server_channels_config = channels.server_configs();
|
|
let client_channels_config = channels.client_configs();
|
|
|
|
let client = RenetClient::new(ConnectionConfig {
|
|
server_channels_config,
|
|
client_channels_config,
|
|
..Default::default()
|
|
});
|
|
|
|
commands.insert_resource(client);
|
|
commands.insert_resource(client_transport(&config)?);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn client_transport(config: &NetworkingConfig) -> Result<NetcodeClientTransport, NetcodeError> {
|
|
let current_time = SystemTime::now()
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
.unwrap();
|
|
let client_id = current_time.as_millis() as u64;
|
|
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?;
|
|
let server_addr = config
|
|
.server
|
|
.flatten()
|
|
.unwrap_or_else(|| "127.0.0.1:31111".parse().unwrap());
|
|
let authentication = ClientAuthentication::Unsecure {
|
|
client_id,
|
|
protocol_id: 0,
|
|
server_addr,
|
|
user_data: None,
|
|
};
|
|
|
|
info!("attempting connection to {server_addr}");
|
|
NetcodeClientTransport::new(current_time, authentication, socket)
|
|
}
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
fn migrate_remote_entities(
|
|
query: Query<(Entity, &TbMapEntityId), (Added<TbMapEntityId>, With<ConfirmHistory>)>,
|
|
children: Query<&Children>,
|
|
mut commands: Commands,
|
|
mut mapping: ResMut<TbMapEntityMapping>,
|
|
) {
|
|
for (serverside, tb_id) in query.iter() {
|
|
received_remote_map_entity(serverside, tb_id.id, &children, &mut mapping, &mut commands);
|
|
}
|
|
}
|
|
|
|
fn received_remote_map_entity(
|
|
serverside: Entity,
|
|
tb_id: u64,
|
|
children: &Query<&Children>,
|
|
mapping: &mut TbMapEntityMapping,
|
|
commands: &mut Commands,
|
|
) {
|
|
let Some(clientside) = mapping.0.remove(&tb_id) else {
|
|
warn!("received unknown MapEntity ID `{tb_id:?}`");
|
|
return;
|
|
};
|
|
|
|
// cannot just use `take` directly with a bundle because then any missing component would cause
|
|
// the entire bundle to fail
|
|
move_component::<Brushes>(commands, clientside, serverside);
|
|
move_component::<(
|
|
Collider,
|
|
ColliderAabb,
|
|
ColliderDensity,
|
|
ColliderMarker,
|
|
CollisionLayers,
|
|
)>(commands, clientside, serverside);
|
|
move_component::<ColliderOf>(commands, clientside, serverside);
|
|
move_component::<ColliderTransform>(commands, clientside, serverside);
|
|
move_component::<CollisionEventsEnabled>(commands, clientside, serverside);
|
|
move_component::<Movable>(commands, clientside, serverside);
|
|
move_component::<Platform>(commands, clientside, serverside);
|
|
move_component::<PlatformTarget>(commands, clientside, serverside);
|
|
move_component::<SceneInstance>(commands, clientside, serverside);
|
|
move_component::<SceneRoot>(commands, clientside, serverside);
|
|
move_component::<Sensor>(commands, clientside, serverside);
|
|
|
|
if let Ok(children) = children.get(clientside) {
|
|
for child in children.iter() {
|
|
commands.entity(child).insert(ChildOf(serverside));
|
|
}
|
|
}
|
|
|
|
commands.entity(clientside).despawn();
|
|
}
|
|
|
|
fn move_component<B: Bundle + BundleFromComponents>(
|
|
commands: &mut Commands,
|
|
from: Entity,
|
|
to: Entity,
|
|
) {
|
|
commands.queue(move |world: &mut World| {
|
|
let comp = world.entity_mut(from).take::<B>();
|
|
if let Some(comp) = comp {
|
|
world.entity_mut(to).insert(comp);
|
|
}
|
|
});
|
|
}
|
|
|
|
fn despawn_absent_map_entities(
|
|
mut commands: Commands,
|
|
mut messages: MessageReader<DespawnTbMapEntity>,
|
|
mut map: ResMut<TbMapEntityMapping>,
|
|
) {
|
|
for msg in messages.read() {
|
|
// the server may double-send DespawnTbMapEntity for a given ID, so ignore it if the entity
|
|
// was already despawned.
|
|
let Some(entity) = map.0.remove(&msg.0) else {
|
|
continue;
|
|
};
|
|
|
|
commands.entity(entity).despawn();
|
|
}
|
|
}
|