1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
pub mod backend;
pub mod client_id;
pub mod message;
pub mod protocol;
pub mod replication;
pub mod replicon_tick;
pub mod server_entity_map;
use bevy::prelude::*;
use crate::prelude::*;
use backend::connected_client::NetworkIdMap;
use message::registry::RemoteMessageRegistry;
use replication::signature::SignatureMap;
use replication::{
receive_markers::ReceiveMarkers, registry::ReplicationRegistry, rules::ReplicationRules,
track_mutate_messages::TrackMutateMessages,
};
/// Initializes types, resources and events needed for both client and server.
#[derive(Default)]
pub struct RepliconSharedPlugin {
/**
Configures the authorization process.
# Examples
Custom authorization to set a player name before starting replication.
We re-use [`ProtocolMismatch`], which is registered only with
[`AuthMethod::ProtocolCheck`], but it could be any event.
```
use bevy::{prelude::*, state::app::StatesPlugin};
use bevy_replicon::prelude::*;
use serde::{Deserialize, Serialize};
let mut app = App::new();
app.add_plugins((
MinimalPlugins,
StatesPlugin,
RepliconPlugins.set(RepliconSharedPlugin {
auth_method: AuthMethod::Custom,
}),
))
.add_client_event::<ClientInfo>(Channel::Ordered)
.add_server_event::<ProtocolMismatch>(Channel::Unreliable)
.make_event_independent::<ProtocolMismatch>() // Let the client receive it without replication.
.add_observer(start_game)
.add_systems(OnEnter(ClientState::Connected), send_info);
fn send_info(
mut commands: Commands,
protocol: Res<ProtocolHash>,
) {
commands.client_trigger(ClientInfo {
protocol: *protocol,
player_name: "Shatur".to_string(), // Could be read from console or UI.
});
}
fn start_game(
client_info: On<FromClient<ClientInfo>>,
mut commands: Commands,
mut disconnects: MessageWriter<DisconnectRequest>,
protocol: Res<ProtocolHash>,
) {
let client = client_info
.client_id
.entity()
.expect("protocol hash sent only from clients");
// Since we are using custom authorization,
// we need to verify the protocol manually.
if client_info.protocol != *protocol {
// Notify the client about the problem. No delivery
// guarantee, since we disconnect after sending.
commands.server_trigger(ToClients {
mode: SendMode::Direct(client_info.client_id),
message: ProtocolMismatch,
});
disconnects.write(DisconnectRequest { client });
}
// Validate player name, run the necessary game logic...
// Manually mark the client as authorized.
commands.entity(client).insert(AuthorizedClient);
}
/// A client event for custom authorization.
#[derive(Event, Serialize, Deserialize)]
struct ClientInfo {
protocol: ProtocolHash,
player_name: String,
}
```
**/
pub auth_method: AuthMethod,
}
impl Plugin for RepliconSharedPlugin {
fn build(&self, app: &mut App) {
app.init_state::<ClientState>()
.init_state::<ServerState>()
.init_resource::<ProtocolHasher>()
.init_resource::<NetworkIdMap>()
.init_resource::<TrackMutateMessages>()
.init_resource::<RepliconChannels>()
.init_resource::<ReplicationRegistry>()
.init_resource::<ReplicationRules>()
.init_resource::<SignatureMap>()
.init_resource::<ReceiveMarkers>()
.init_resource::<RemoteMessageRegistry>()
.insert_resource(self.auth_method)
.add_message::<DisconnectRequest>();
if self.auth_method == AuthMethod::ProtocolCheck {
app.add_client_event::<ProtocolHash>(Channel::Ordered)
.add_server_event::<ProtocolMismatch>(Channel::Unreliable)
.make_event_independent::<ProtocolMismatch>();
}
}
fn finish(&self, app: &mut App) {
let protocol_hasher = app
.world_mut()
.remove_resource::<ProtocolHasher>()
.expect("protocol hasher should be initialized at the plugin build");
app.world_mut().insert_resource(protocol_hasher.finish());
}
}
/// Configures the insertion of [`AuthorizedClient`].
///
/// Can be set via [`RepliconSharedPlugin::auth_method`].
#[derive(Resource, Default, Debug, PartialEq, Eq, Clone, Copy)]
pub enum AuthMethod {
/// Wait for receiving [`ProtocolHash`] event from the client.
///
/// - If the hash differs from the server's, the client will be notified with
/// a [`ProtocolMismatch`] event and disconnected.
/// - If the hash matches, the [`AuthorizedClient`] component will be inserted.
#[default]
ProtocolCheck,
/// Consider all connected clients immediately authorized.
///
/// [`AuthorizedClient`] will be configured as a required component for [`ConnectedClient`].
///
/// Use with caution.
None,
/// Disable automatic insertion.
///
/// The user is responsible for manually inserting [`AuthorizedClient`] on the server.
Custom,
}