Allow mods/admins to log in when server is full.

As a side effect, this moves the initial game server sync message into
the login code, since that's the first place we can check for admin
permissions and we want to avoid sending large messages to users who are
not authenticated (especially if the player cap has been reached;
previously, the player cap check limited the damage that could be done
by unauthenticated players).

Some fallout from this is that we don't synchronize the Player component
anymore, which had some minor effects on voxygen.  This update also
breaks Torvus, since Client::new now expects the username and password
to be provided from the getgo--an accompanying MR will be submitted to
fix it.
This commit is contained in:
Joshua Yanovski 2022-09-06 20:59:41 -07:00
parent 7ea720b2ef
commit f5aee1d2a7
13 changed files with 156 additions and 159 deletions

View File

@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed bug where the view distance selection was not immediately applied to entity syncing when - Fixed bug where the view distance selection was not immediately applied to entity syncing when
first joining a server and when changing the view distance (previously this required moving to a first joining a server and when changing the view distance (previously this required moving to a
new chunk for the initial setting or subsequent change to apply). new chunk for the initial setting or subsequent change to apply).
- Moderators and admins are no longer blocked from logging in when there are too many players.
## [0.13.0] - 2022-07-23 ## [0.13.0] - 2022-07-23

View File

@ -51,19 +51,20 @@ fn main() {
// Create a client. // Create a client.
let mut client = runtime let mut client = runtime
.block_on(Client::new(addr, runtime2, &mut None)) .block_on(Client::new(
addr,
runtime2,
&mut None,
&username,
&password,
|provider| provider == "https://auth.veloren.net",
))
.expect("Failed to create client instance"); .expect("Failed to create client instance");
println!("Server info: {:?}", client.server_info()); println!("Server info: {:?}", client.server_info());
println!("Players online: {:?}", client.players().collect::<Vec<_>>()); println!("Players online: {:?}", client.players().collect::<Vec<_>>());
runtime
.block_on(client.register(username, password, |provider| {
provider == "https://auth.veloren.net"
}))
.unwrap();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
thread::spawn(move || { thread::spawn(move || {
loop { loop {

View File

@ -13,6 +13,7 @@ mod settings;
mod tui; mod tui;
use common::comp::body::humanoid::Body; use common::comp::body::humanoid::Body;
use common_net::msg::ServerInfo;
use settings::Settings; use settings::Settings;
use tui::Cmd; use tui::Cmd;
@ -46,31 +47,47 @@ pub fn main() {
pub struct BotClient { pub struct BotClient {
settings: Settings, settings: Settings,
runtime: Arc<Runtime>, runtime: Arc<Runtime>,
menu_client: Client, server_info: ServerInfo,
bot_clients: HashMap<String, Client>, bot_clients: HashMap<String, Client>,
clock: Clock, clock: Clock,
} }
pub fn make_client(runtime: &Arc<Runtime>, server: &str) -> Client { pub fn make_client(
runtime: &Arc<Runtime>,
server: &str,
server_info: &mut Option<ServerInfo>,
username: &str,
password: &str,
) -> Option<Client> {
let runtime_clone = Arc::clone(runtime); let runtime_clone = Arc::clone(runtime);
let addr = ConnectionArgs::Tcp { let addr = ConnectionArgs::Tcp {
prefer_ipv6: false, prefer_ipv6: false,
hostname: server.to_owned(), hostname: server.to_owned(),
}; };
runtime runtime
.block_on(Client::new(addr, runtime_clone, &mut None)) .block_on(Client::new(
.expect("Failed to connect to server") addr,
runtime_clone,
server_info,
username,
password,
|_| true,
))
.ok()
} }
impl BotClient { impl BotClient {
pub fn new(settings: Settings) -> BotClient { pub fn new(settings: Settings) -> BotClient {
let runtime = Arc::new(Runtime::new().unwrap()); let runtime = Arc::new(Runtime::new().unwrap());
let menu_client: Client = make_client(&runtime, &settings.server); let mut server_info = None;
// Don't care if we connect, just trying to grab the server info.
let _ = make_client(&runtime, &settings.server, &mut server_info, "", "");
let server_info = server_info.expect("Failed to connect to server.");
let clock = Clock::new(Duration::from_secs_f64(1.0 / 60.0)); let clock = Clock::new(Duration::from_secs_f64(1.0 / 60.0));
BotClient { BotClient {
settings, settings,
runtime, runtime,
menu_client, server_info,
bot_clients: HashMap::new(), bot_clients: HashMap::new(),
clock, clock,
} }
@ -106,7 +123,7 @@ impl BotClient {
None => vec![prefix.to_string()], None => vec![prefix.to_string()],
}; };
info!("usernames: {:?}", usernames); info!("usernames: {:?}", usernames);
if let Some(auth_addr) = self.menu_client.server_info().auth_provider.as_ref() { if let Some(auth_addr) = self.server_info.auth_provider.as_ref() {
let (scheme, authority) = auth_addr.split_once("://").expect("invalid auth url"); let (scheme, authority) = auth_addr.split_once("://").expect("invalid auth url");
let scheme = scheme let scheme = scheme
.parse::<authc::Scheme>() .parse::<authc::Scheme>()
@ -156,20 +173,16 @@ impl BotClient {
for cred in creds.iter() { for cred in creds.iter() {
let runtime = Arc::clone(&self.runtime); let runtime = Arc::clone(&self.runtime);
let server = self.settings.server.clone(); let server = &self.settings.server;
// TODO: log the clients in in parallel instead of in series
let client = self let client = self
.bot_clients .bot_clients
.entry(cred.username.clone()) .entry(cred.username.clone())
.or_insert_with(|| make_client(&runtime, &server)); .or_insert_with(|| {
make_client(&runtime, server, &mut None, &cred.username, &cred.password)
.expect("Failed to connect to server")
});
// TODO: log the clients in in parallel instead of in series
if let Err(e) = runtime.block_on(client.register(
cred.username.clone(),
cred.password.clone(),
|_| true,
)) {
warn!("error logging in {:?}: {:?}", cred.username, e);
}
let body = BotClient::create_default_body(); let body = BotClient::create_default_body();
client.create_character( client.create_character(
cred.username.clone(), cred.username.clone(),

View File

@ -108,15 +108,17 @@ fn run_client(
hostname: "localhost".into(), hostname: "localhost".into(),
}; };
let runtime_clone = Arc::clone(&runtime); let runtime_clone = Arc::clone(&runtime);
let mut client = runtime
.block_on(Client::new(addr, runtime_clone, &mut None))
.expect("Failed to connect to the server");
// Login
// NOTE: use a no-auth server // NOTE: use a no-auth server
runtime let mut client = runtime
.block_on(client.register(username.clone(), String::new(), |_| false)) .block_on(Client::new(
.expect("Failed to log in"); addr,
runtime_clone,
&mut None,
&username,
"",
|_| false,
))
.expect("Failed to connect to the server");
let mut clock = common::clock::Clock::new(Duration::from_secs_f32(1.0 / 30.0)); let mut clock = common::clock::Clock::new(Duration::from_secs_f32(1.0 / 30.0));

View File

@ -275,6 +275,9 @@ impl Client {
runtime: Arc<Runtime>, runtime: Arc<Runtime>,
// TODO: refactor to avoid needing to use this out parameter // TODO: refactor to avoid needing to use this out parameter
mismatched_server_info: &mut Option<ServerInfo>, mismatched_server_info: &mut Option<ServerInfo>,
username: &str,
password: &str,
auth_trusted: impl FnMut(&str) -> bool,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let network = Network::new(Pid::new(), &runtime); let network = Network::new(Pid::new(), &runtime);
@ -317,15 +320,24 @@ impl Client {
common::util::GIT_HASH.to_string(), common::util::GIT_HASH.to_string(),
common::util::GIT_DATE.to_string(), common::util::GIT_DATE.to_string(),
); );
// Pass the server info back to the caller to ensure they can access it even
// if this function errors.
mem::swap(mismatched_server_info, &mut Some(server_info.clone()));
} }
// Pass the server info back to the caller to ensure they can access it even
// if this function errors.
mem::swap(mismatched_server_info, &mut Some(server_info.clone()));
debug!("Auth Server: {:?}", server_info.auth_provider); debug!("Auth Server: {:?}", server_info.auth_provider);
ping_stream.send(PingMsg::Ping)?; ping_stream.send(PingMsg::Ping)?;
// Register client
Self::register(
username,
password,
auth_trusted,
&server_info,
&mut register_stream,
)
.await?;
// Wait for initial sync // Wait for initial sync
let mut ping_interval = tokio::time::interval(Duration::from_secs(1)); let mut ping_interval = tokio::time::interval(Duration::from_secs(1));
let ( let (
@ -632,7 +644,7 @@ impl Client {
let map_bounds = Vec2::new(sea_level, max_height); let map_bounds = Vec2::new(sea_level, max_height);
debug!("Done preparing image..."); debug!("Done preparing image...");
Ok(( (
state, state,
lod_base, lod_base,
lod_alt, lod_alt,
@ -644,16 +656,15 @@ impl Client {
component_recipe_book, component_recipe_book,
max_group_size, max_group_size,
client_timeout, client_timeout,
)) )
}, },
ServerInit::TooManyPlayers => Err(Error::TooManyPlayers), };
}?;
ping_stream.send(PingMsg::Ping)?; ping_stream.send(PingMsg::Ping)?;
debug!("Initial sync done"); debug!("Initial sync done");
Ok(Self { Ok(Self {
registered: false, registered: true,
presence: None, presence: None,
runtime, runtime,
server_info, server_info,
@ -722,14 +733,15 @@ impl Client {
} }
/// Request a state transition to `ClientState::Registered`. /// Request a state transition to `ClientState::Registered`.
pub async fn register( async fn register(
&mut self, username: &str,
username: String, password: &str,
password: String,
mut auth_trusted: impl FnMut(&str) -> bool, mut auth_trusted: impl FnMut(&str) -> bool,
server_info: &ServerInfo,
register_stream: &mut Stream,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Authentication // Authentication
let token_or_username = match &self.server_info.auth_provider { let token_or_username = match &server_info.auth_provider {
Some(addr) => { Some(addr) => {
// Query whether this is a trusted auth server // Query whether this is a trusted auth server
if auth_trusted(addr) { if auth_trusted(addr) {
@ -749,26 +761,29 @@ impl Client {
}; };
Ok(authc::AuthClient::new(scheme, authority)? Ok(authc::AuthClient::new(scheme, authority)?
.sign_in(&username, &password) .sign_in(username, password)
.await? .await?
.serialize()) .serialize())
} else { } else {
Err(Error::AuthServerNotTrusted) Err(Error::AuthServerNotTrusted)
} }
}, },
None => Ok(username), None => Ok(username.to_owned()),
}?; }?;
self.send_msg_err(ClientRegister { token_or_username })?; debug!("Registering client...");
match self.register_stream.recv::<ServerRegisterAnswer>().await? { register_stream.send(ClientRegister { token_or_username })?;
match register_stream.recv::<ServerRegisterAnswer>().await? {
Err(RegisterError::AuthError(err)) => Err(Error::AuthErr(err)), Err(RegisterError::AuthError(err)) => Err(Error::AuthErr(err)),
Err(RegisterError::InvalidCharacter) => Err(Error::InvalidCharacter), Err(RegisterError::InvalidCharacter) => Err(Error::InvalidCharacter),
Err(RegisterError::NotOnWhitelist) => Err(Error::NotOnWhitelist), Err(RegisterError::NotOnWhitelist) => Err(Error::NotOnWhitelist),
Err(RegisterError::Kicked(err)) => Err(Error::Kicked(err)), Err(RegisterError::Kicked(err)) => Err(Error::Kicked(err)),
Err(RegisterError::Banned(reason)) => Err(Error::Banned(reason)), Err(RegisterError::Banned(reason)) => Err(Error::Banned(reason)),
Err(RegisterError::TooManyPlayers) => Err(Error::TooManyPlayers),
Ok(()) => { Ok(()) => {
self.registered = true; debug!("Client registered successfully.");
Ok(()) Ok(())
}, },
} }
@ -2917,6 +2932,9 @@ mod tests {
let runtime = Arc::new(Runtime::new().unwrap()); let runtime = Arc::new(Runtime::new().unwrap());
let runtime2 = Arc::clone(&runtime); let runtime2 = Arc::clone(&runtime);
let username = "Foo";
let password = "Bar";
let auth_server = "auth.veloren.net";
let veloren_client: Result<Client, Error> = runtime.block_on(Client::new( let veloren_client: Result<Client, Error> = runtime.block_on(Client::new(
ConnectionArgs::Tcp { ConnectionArgs::Tcp {
hostname: "127.0.0.1:9000".to_owned(), hostname: "127.0.0.1:9000".to_owned(),
@ -2924,18 +2942,12 @@ mod tests {
}, },
runtime2, runtime2,
&mut None, &mut None,
username,
password,
|suggestion: &str| suggestion == auth_server,
)); ));
let _ = veloren_client.map(|mut client| { let _ = veloren_client.map(|mut client| {
//register
let username: String = "Foo".to_string();
let password: String = "Bar".to_string();
let auth_server: String = "auth.veloren.net".to_string();
let _result: Result<(), Error> =
runtime.block_on(client.register(username, password, |suggestion: &str| {
suggestion == auth_server
}));
//clock //clock
let mut clock = Clock::new(Duration::from_secs_f64(SPT)); let mut clock = Clock::new(Duration::from_secs_f64(SPT));

View File

@ -55,7 +55,6 @@ pub struct ServerInfo {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum ServerInit { pub enum ServerInit {
TooManyPlayers,
GameSync { GameSync {
entity_package: sync::EntityPackage<EcsCompPacket>, entity_package: sync::EntityPackage<EcsCompPacket>,
time_of_day: TimeOfDay, time_of_day: TimeOfDay,
@ -275,6 +274,7 @@ pub enum RegisterError {
Kicked(String), Kicked(String),
InvalidCharacter, InvalidCharacter,
NotOnWhitelist, NotOnWhitelist,
TooManyPlayers,
//TODO: InvalidAlias, //TODO: InvalidAlias,
} }

View File

@ -42,9 +42,6 @@ macro_rules! synced_components {
shockwave: Shockwave, shockwave: Shockwave,
beam_segment: BeamSegment, beam_segment: BeamSegment,
alignment: Alignment, alignment: Alignment,
// TODO: evaluate if this is used on the client,
// and if so what it is used for
player: Player,
// TODO: change this to `SyncFrom::ClientEntity` and sync the bare minimum // TODO: change this to `SyncFrom::ClientEntity` and sync the bare minimum
// from other entities (e.g. just keys needed to show appearance // from other entities (e.g. just keys needed to show appearance
// based on their loadout). Also, it looks like this actually has // based on their loadout). Also, it looks like this actually has
@ -210,10 +207,6 @@ impl NetSync for Alignment {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for Player {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
}
impl NetSync for Inventory { impl NetSync for Inventory {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
@ -222,7 +215,7 @@ impl NetSync for SkillSet {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
// These are synced only from the client's own entity. // These are synced only from the client's own entity.
impl NetSync for Combo { impl NetSync for Combo {
const SYNC_FROM: SyncFrom = SyncFrom::ClientEntity; const SYNC_FROM: SyncFrom = SyncFrom::ClientEntity;

View File

@ -67,7 +67,7 @@ use crate::{
presence::{Presence, RegionSubscription, RepositionOnChunkLoad}, presence::{Presence, RegionSubscription, RepositionOnChunkLoad},
rtsim::RtSim, rtsim::RtSim,
state_ext::StateExt, state_ext::StateExt,
sys::sentinel::{DeletedEntities, TrackedStorages}, sys::sentinel::DeletedEntities,
}; };
use censor::Censor; use censor::Censor;
#[cfg(not(feature = "worldgen"))] #[cfg(not(feature = "worldgen"))]
@ -79,7 +79,6 @@ use common::{
cmd::ServerChatCommand, cmd::ServerChatCommand,
comp, comp,
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
recipe::{default_component_recipe_book, default_recipe_book},
resources::{BattleMode, Time, TimeOfDay}, resources::{BattleMode, Time, TimeOfDay},
rtsim::RtSimEntity, rtsim::RtSimEntity,
slowjob::SlowJobPool, slowjob::SlowJobPool,
@ -88,9 +87,7 @@ use common::{
}; };
use common_ecs::run_now; use common_ecs::run_now;
use common_net::{ use common_net::{
msg::{ msg::{ClientType, DisconnectReason, ServerGeneral, ServerInfo, ServerMsg},
ClientType, DisconnectReason, ServerGeneral, ServerInfo, ServerInit, ServerMsg, WorldMapMsg,
},
sync::WorldSyncExt, sync::WorldSyncExt,
}; };
use common_state::{BuildAreas, State}; use common_state::{BuildAreas, State};
@ -103,7 +100,7 @@ use persistence::{
}; };
use prometheus::Registry; use prometheus::Registry;
use prometheus_hyper::Server as PrometheusServer; use prometheus_hyper::Server as PrometheusServer;
use specs::{join::Join, Builder, Entity as EcsEntity, Entity, SystemData, WorldExt}; use specs::{join::Join, Builder, Entity as EcsEntity, Entity, WorldExt};
use std::{ use std::{
i32, i32,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
@ -201,7 +198,6 @@ pub struct Server {
state: State, state: State,
world: Arc<World>, world: Arc<World>,
index: IndexOwned, index: IndexOwned,
map: WorldMapMsg,
connection_handler: ConnectionHandler, connection_handler: ConnectionHandler,
@ -387,6 +383,8 @@ impl Server {
pois: Vec::new(), pois: Vec::new(),
}; };
state.ecs_mut().insert(map);
#[cfg(feature = "worldgen")] #[cfg(feature = "worldgen")]
let spawn_point = SpawnPoint({ let spawn_point = SpawnPoint({
let index = index.as_index_ref(); let index = index.as_index_ref();
@ -562,8 +560,6 @@ impl Server {
state, state,
world, world,
index, index,
map,
connection_handler, connection_handler,
runtime, runtime,
@ -630,9 +626,6 @@ impl Server {
/// Get a reference to the server's world. /// Get a reference to the server's world.
pub fn world(&self) -> &World { &self.world } pub fn world(&self) -> &World { &self.world }
/// Get a reference to the server's world map.
pub fn map(&self) -> &WorldMapMsg { &self.map }
/// Execute a single server tick, handle input and update the game state by /// Execute a single server tick, handle input and update the game state by
/// the given duration. /// the given duration.
pub fn tick(&mut self, _input: Input, dt: Duration) -> Result<Vec<Event>, Error> { pub fn tick(&mut self, _input: Input, dt: Duration) -> Result<Vec<Event>, Error> {
@ -1029,19 +1022,7 @@ impl Server {
.map(|mut t| t.maintain()); .map(|mut t| t.maintain());
} }
fn initialize_client( fn initialize_client(&mut self, client: connection_handler::IncomingClient) -> Entity {
&mut self,
client: connection_handler::IncomingClient,
) -> Result<Option<Entity>, Error> {
if self.settings().max_players <= self.state.ecs().read_storage::<Client>().join().count() {
trace!(
?client.participant,
"to many players, wont allow participant to connect"
);
client.send(ServerInit::TooManyPlayers)?;
return Ok(None);
}
let entity = self let entity = self
.state .state
.ecs_mut() .ecs_mut()
@ -1053,43 +1034,7 @@ impl Server {
.read_resource::<metrics::PlayerMetrics>() .read_resource::<metrics::PlayerMetrics>()
.clients_connected .clients_connected
.inc(); .inc();
// Send client all the tracked components currently attached to its entity as entity
// well as synced resources (currently only `TimeOfDay`)
debug!("Starting initial sync with client.");
self.state
.ecs()
.read_storage::<Client>()
.get(entity)
.expect(
"We just created this entity with a Client component using build(), and we have \
&mut access to the ecs so it can't have been deleted yet.",
)
.send(ServerInit::GameSync {
// Send client their entity
entity_package: TrackedStorages::fetch(self.state.ecs())
.create_entity_package(entity, None, None, None)
.expect(
"We just created this entity as marked() (using create_entity_synced) so \
it definitely has a uid",
),
time_of_day: *self.state.ecs().read_resource(),
max_group_size: self.settings().max_player_group_size,
client_timeout: self.settings().client_timeout,
world_map: self.map.clone(),
recipe_book: default_recipe_book().cloned(),
component_recipe_book: default_component_recipe_book().cloned(),
material_stats: (&*self
.state
.ecs()
.read_resource::<comp::item::MaterialStatManifest>())
.clone(),
ability_map: (&*self
.state
.ecs()
.read_resource::<comp::item::tool::AbilityMap>())
.clone(),
})?;
Ok(Some(entity))
} }
/// Disconnects all clients if requested by either an admin command or /// Disconnects all clients if requested by either an admin command or
@ -1155,16 +1100,8 @@ impl Server {
} }
while let Ok(incoming) = self.connection_handler.client_receiver.try_recv() { while let Ok(incoming) = self.connection_handler.client_receiver.try_recv() {
match self.initialize_client(incoming) { let entity = self.initialize_client(incoming);
Ok(None) => (), frontend_events.push(Event::ClientConnected { entity });
Ok(Some(entity)) => {
frontend_events.push(Event::ClientConnected { entity });
debug!("Done initial sync with client.");
},
Err(e) => {
debug!(?e, "failed initializing a new client");
},
}
} }
} }

View File

@ -97,7 +97,7 @@ impl LoginProvider {
PendingLogin { pending_r } PendingLogin { pending_r }
} }
pub fn login( pub(crate) fn login(
&mut self, &mut self,
pending: &mut PendingLogin, pending: &mut PendingLogin,
#[cfg(feature = "plugins")] world: &EcsWorld, #[cfg(feature = "plugins")] world: &EcsWorld,
@ -105,6 +105,7 @@ impl LoginProvider {
admins: &HashMap<Uuid, AdminRecord>, admins: &HashMap<Uuid, AdminRecord>,
whitelist: &HashMap<Uuid, WhitelistRecord>, whitelist: &HashMap<Uuid, WhitelistRecord>,
banlist: &HashMap<Uuid, BanEntry>, banlist: &HashMap<Uuid, BanEntry>,
player_count_exceeded: bool,
) -> Option<Result<(String, Uuid), RegisterError>> { ) -> Option<Result<(String, Uuid), RegisterError>> {
match pending.pending_r.try_recv() { match pending.pending_r.try_recv() {
Ok(Err(e)) => Some(Err(e)), Ok(Err(e)) => Some(Err(e)),
@ -137,6 +138,11 @@ impl LoginProvider {
return Some(Err(RegisterError::NotOnWhitelist)); return Some(Err(RegisterError::NotOnWhitelist));
} }
// non-admins can only join if the player count has not been exceeded.
if admin.is_none() && player_count_exceeded {
return Some(Err(RegisterError::TooManyPlayers));
}
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]
{ {
// Plugin player join hooks execute for all players, but are only allowed to // Plugin player join hooks execute for all players, but are only allowed to

View File

@ -6,7 +6,7 @@ use crate::{
}; };
use common::{ use common::{
calendar::Calendar, calendar::Calendar,
comp::{Collider, ForceUpdate, InventoryUpdate, Last, Ori, Pos, Vel}, comp::{Collider, ForceUpdate, InventoryUpdate, Last, Ori, Player, Pos, Vel},
event::EventBus, event::EventBus,
outcome::Outcome, outcome::Outcome,
region::{Event as RegionEvent, RegionMap}, region::{Event as RegionEvent, RegionMap},
@ -40,6 +40,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Vel>, ReadStorage<'a, Vel>,
ReadStorage<'a, Ori>, ReadStorage<'a, Ori>,
ReadStorage<'a, RegionSubscription>, ReadStorage<'a, RegionSubscription>,
ReadStorage<'a, Player>,
ReadStorage<'a, Presence>, ReadStorage<'a, Presence>,
ReadStorage<'a, Client>, ReadStorage<'a, Client>,
WriteStorage<'a, Last<Pos>>, WriteStorage<'a, Last<Pos>>,
@ -70,6 +71,7 @@ impl<'a> System<'a> for Sys {
velocities, velocities,
orientations, orientations,
subscriptions, subscriptions,
players,
presences, presences,
clients, clients,
mut last_pos, mut last_pos,
@ -88,7 +90,6 @@ impl<'a> System<'a> for Sys {
let uids = &tracked_storages.uid; let uids = &tracked_storages.uid;
let colliders = &tracked_storages.collider; let colliders = &tracked_storages.collider;
let inventories = &tracked_storages.inventory; let inventories = &tracked_storages.inventory;
let players = &tracked_storages.player;
let is_rider = &tracked_storages.is_rider; let is_rider = &tracked_storages.is_rider;
// To send entity updates // To send entity updates

View File

@ -2,17 +2,20 @@ use crate::{
client::Client, client::Client,
login_provider::{LoginProvider, PendingLogin}, login_provider::{LoginProvider, PendingLogin},
metrics::PlayerMetrics, metrics::PlayerMetrics,
sys::sentinel::TrackedStorages,
EditableSettings, Settings, EditableSettings, Settings,
}; };
use common::{ use common::{
comp::{Admin, Player, Stats}, comp::{self, Admin, Player, Stats},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
recipe::{default_component_recipe_book, default_recipe_book},
resources::TimeOfDay,
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{ use common_net::msg::{
CharacterInfo, ClientRegister, DisconnectReason, PlayerInfo, PlayerListUpdate, RegisterError, CharacterInfo, ClientRegister, DisconnectReason, PlayerInfo, PlayerListUpdate, RegisterError,
ServerGeneral, ServerGeneral, ServerInit, WorldMapMsg,
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use plugin_api::Health; use plugin_api::Health;
@ -20,7 +23,7 @@ use specs::{
shred::ResourceId, storage::StorageEntry, Entities, Join, Read, ReadExpect, ReadStorage, shred::ResourceId, storage::StorageEntry, Entities, Join, Read, ReadExpect, ReadStorage,
SystemData, World, WriteExpect, WriteStorage, SystemData, World, WriteExpect, WriteStorage,
}; };
use tracing::trace; use tracing::{debug, trace};
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]
use {common_state::plugin::memory_manager::EcsWorld, common_state::plugin::PluginMgr}; use {common_state::plugin::memory_manager::EcsWorld, common_state::plugin::PluginMgr};
@ -40,6 +43,11 @@ pub struct ReadData<'a> {
player_metrics: ReadExpect<'a, PlayerMetrics>, player_metrics: ReadExpect<'a, PlayerMetrics>,
settings: ReadExpect<'a, Settings>, settings: ReadExpect<'a, Settings>,
editable_settings: ReadExpect<'a, EditableSettings>, editable_settings: ReadExpect<'a, EditableSettings>,
time_of_day: Read<'a, TimeOfDay>,
material_stats: ReadExpect<'a, comp::item::MaterialStatManifest>,
ability_map: ReadExpect<'a, comp::item::tool::AbilityMap>,
map: ReadExpect<'a, WorldMapMsg>,
trackers: TrackedStorages<'a>,
_healths: ReadStorage<'a, Health>, // used by plugin feature _healths: ReadStorage<'a, Health>, // used by plugin feature
_plugin_mgr: ReadPlugin<'a>, // used by plugin feature _plugin_mgr: ReadPlugin<'a>, // used by plugin feature
_uid_allocator: Read<'a, UidAllocator>, // used by plugin feature _uid_allocator: Read<'a, UidAllocator>, // used by plugin feature
@ -107,6 +115,8 @@ impl<'a> System<'a> for Sys {
let mut finished_pending = vec![]; let mut finished_pending = vec![];
let mut retries = vec![]; let mut retries = vec![];
let mut player_count = player_list.len();
let max_players = read_data.settings.max_players;
for (entity, client, pending) in for (entity, client, pending) in
(&read_data.entities, &read_data.clients, &mut pending_logins).join() (&read_data.entities, &read_data.clients, &mut pending_logins).join()
{ {
@ -129,6 +139,7 @@ impl<'a> System<'a> for Sys {
&*read_data.editable_settings.admins, &*read_data.editable_settings.admins,
&*read_data.editable_settings.whitelist, &*read_data.editable_settings.whitelist,
&*read_data.editable_settings.banlist, &*read_data.editable_settings.banlist,
player_count >= max_players,
) { ) {
None => return Ok(()), None => return Ok(()),
Some(r) => { Some(r) => {
@ -190,6 +201,7 @@ impl<'a> System<'a> for Sys {
if let Ok(StorageEntry::Vacant(v)) = players.entry(entity) { if let Ok(StorageEntry::Vacant(v)) = players.entry(entity) {
// Add Player component to this client, if the entity exists. // Add Player component to this client, if the entity exists.
v.insert(player); v.insert(player);
player_count += 1;
read_data.player_metrics.players_connected.inc(); read_data.player_metrics.players_connected.inc();
// Give the Admin component to the player if their name exists in // Give the Admin component to the player if their name exists in
@ -203,6 +215,31 @@ impl<'a> System<'a> for Sys {
// Tell the client its request was successful. // Tell the client its request was successful.
client.send(Ok(()))?; client.send(Ok(()))?;
// Send client all the tracked components currently attached to its entity as
// well as synced resources (currently only `TimeOfDay`)
debug!("Starting initial sync with client.");
client.send(ServerInit::GameSync {
// Send client their entity
entity_package:
read_data.trackers
.create_entity_package(entity, None, None, None)
// NOTE: We are apparently okay with crashing if a uid is removed from
// a non-logged-in player without removing the whole thing.
.expect(
"We created this entity as marked() (using create_entity_synced) so \
it definitely has a uid",
),
time_of_day: *read_data.time_of_day,
max_group_size: read_data.settings.max_player_group_size,
client_timeout: read_data.settings.client_timeout,
world_map: (&*read_data.map).clone(),
recipe_book: default_recipe_book().cloned(),
component_recipe_book: default_component_recipe_book().cloned(),
material_stats: (&*read_data.material_stats).clone(),
ability_map: (&*read_data.ability_map).clone(),
})?;
debug!("Done initial sync with client.");
// Send initial player list // Send initial player list
client.send(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init( client.send(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init(
player_list.clone(), player_list.clone(),

View File

@ -1278,7 +1278,6 @@ impl Hud {
let bodies = ecs.read_storage::<comp::Body>(); let bodies = ecs.read_storage::<comp::Body>();
let items = ecs.read_storage::<Item>(); let items = ecs.read_storage::<Item>();
let inventories = ecs.read_storage::<comp::Inventory>(); let inventories = ecs.read_storage::<comp::Inventory>();
let players = ecs.read_storage::<comp::Player>();
let msm = ecs.read_resource::<MaterialStatManifest>(); let msm = ecs.read_resource::<MaterialStatManifest>();
let entities = ecs.entities(); let entities = ecs.entities();
let me = info.viewpoint_entity; let me = info.viewpoint_entity;
@ -1978,7 +1977,6 @@ impl Hud {
&mut hp_floater_lists, &mut hp_floater_lists,
&uids, &uids,
&inventories, &inventories,
players.maybe(),
poises.maybe(), poises.maybe(),
(alignments.maybe(), is_mount.maybe()), (alignments.maybe(), is_mount.maybe()),
) )
@ -2002,7 +2000,6 @@ impl Hud {
hpfl, hpfl,
uid, uid,
inventory, inventory,
player,
poise, poise,
(alignment, is_mount), (alignment, is_mount),
)| { )| {
@ -2013,7 +2010,8 @@ impl Hud {
// TODO: once the site2 rework lands and merchants have dedicated stalls or // TODO: once the site2 rework lands and merchants have dedicated stalls or
// buildings, they no longer need to be emphasized via the higher overhead // buildings, they no longer need to be emphasized via the higher overhead
// text radius relative to other NPCs // text radius relative to other NPCs
let is_merchant = stats.name == "Merchant" && player.is_none(); let is_merchant =
stats.name == "Merchant" && client.player_list().get(uid).is_none();
let dist_sqr = pos.distance_squared(player_pos); let dist_sqr = pos.distance_squared(player_pos);
// Determine whether to display nametag and healthbar based on whether the // Determine whether to display nametag and healthbar based on whether the
// entity has been damaged, is targeted/selected, or is in your group // entity has been damaged, is targeted/selected, or is in your group

View File

@ -77,17 +77,13 @@ impl ClientInit {
connection_args.clone(), connection_args.clone(),
Arc::clone(&runtime2), Arc::clone(&runtime2),
&mut mismatched_server_info, &mut mismatched_server_info,
&username,
&password,
trust_fn,
) )
.await .await
{ {
Ok(mut client) => { Ok(client) => {
if let Err(e) = client.register(username, password, trust_fn).await {
last_err = Some(Error::ClientError {
error: e,
mismatched_server_info: None,
});
break 'tries;
}
let _ = tx.send(Msg::Done(Ok(client))); let _ = tx.send(Msg::Done(Ok(client)));
tokio::task::block_in_place(move || drop(runtime2)); tokio::task::block_in_place(move || drop(runtime2));
return; return;