Merge branch 'sharp/fix-full-server' into 'master'

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

See merge request veloren/veloren!3600
This commit is contained in:
Joshua Yanovski 2022-09-07 05:21:08 +00:00
commit c95c08ee54
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 // Pass the server info back to the caller to ensure they can access it even
// if this function errors. // if this function errors.
mem::swap(mismatched_server_info, &mut Some(server_info.clone())); 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;
} }

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) => (),
Ok(Some(entity)) => {
frontend_events.push(Event::ClientConnected { 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;