mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'ccgauche/plugin-player-join-event' into 'master'
Lots of improvement in pointer management (switched from i32 to u64) + New event implemented See merge request veloren/veloren!1837
This commit is contained in:
commit
0f969f6e59
@ -38,7 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Coral reefs, kelp forests, and seagrass
|
||||
- Talk animation
|
||||
- New bosses in 5 lower dungeons
|
||||
= New enemies in 5 lower dungeons
|
||||
- New enemies in 5 lower dungeons
|
||||
- Added on join event in plugins
|
||||
- Item stacking and splitting
|
||||
|
||||
### Changed
|
||||
|
@ -4,6 +4,7 @@ use network::{ParticipantError, StreamError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Kicked(String),
|
||||
NetworkErr(NetworkError),
|
||||
ParticipantErr(ParticipantError),
|
||||
StreamErr(StreamError),
|
||||
|
@ -500,6 +500,7 @@ impl Client {
|
||||
Err(RegisterError::AuthError(err)) => Err(Error::AuthErr(err)),
|
||||
Err(RegisterError::InvalidCharacter) => Err(Error::InvalidCharacter),
|
||||
Err(RegisterError::NotOnWhitelist) => Err(Error::NotOnWhitelist),
|
||||
Err(RegisterError::Kicked(err)) => Err(Error::Kicked(err)),
|
||||
Err(RegisterError::Banned(reason)) => Err(Error::Banned(reason)),
|
||||
Ok(()) => {
|
||||
self.registered = true;
|
||||
|
@ -191,6 +191,7 @@ pub enum RegisterError {
|
||||
AlreadyLoggedIn,
|
||||
AuthError(String),
|
||||
Banned(String),
|
||||
Kicked(String),
|
||||
InvalidCharacter,
|
||||
NotOnWhitelist,
|
||||
//TODO: InvalidAlias,
|
||||
|
@ -8,7 +8,7 @@ pub mod buff;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod character_state;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod chat;
|
||||
pub mod combo;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod combo;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod controller;
|
||||
#[cfg(not(target_arch = "wasm32"))] mod energy;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::sync::atomic::{AtomicI32, AtomicPtr, AtomicU32, Ordering};
|
||||
use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicU64, Ordering};
|
||||
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use specs::World;
|
||||
@ -52,14 +52,14 @@ impl EcsAccessManager {
|
||||
}
|
||||
|
||||
pub struct MemoryManager {
|
||||
pub pointer: AtomicI32,
|
||||
pub pointer: AtomicU64,
|
||||
pub length: AtomicU32,
|
||||
}
|
||||
|
||||
impl Default for MemoryManager {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pointer: AtomicI32::new(0),
|
||||
pointer: AtomicU64::new(0),
|
||||
length: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
@ -74,16 +74,18 @@ impl MemoryManager {
|
||||
&self,
|
||||
object_length: u32,
|
||||
allocator: &Function,
|
||||
) -> Result<i32, MemoryAllocationError> {
|
||||
) -> Result<u64, MemoryAllocationError> {
|
||||
if self.length.load(Ordering::SeqCst) >= object_length {
|
||||
return Ok(self.pointer.load(Ordering::SeqCst));
|
||||
}
|
||||
let pointer = allocator
|
||||
.call(&[Value::I32(object_length as i32)])
|
||||
.map_err(MemoryAllocationError::CantAllocate)?;
|
||||
let pointer = pointer[0]
|
||||
.i32()
|
||||
.ok_or(MemoryAllocationError::InvalidReturnType)?;
|
||||
let pointer = super::module::from_i64(
|
||||
pointer[0]
|
||||
.i64()
|
||||
.ok_or(MemoryAllocationError::InvalidReturnType)?,
|
||||
);
|
||||
self.length.store(object_length, Ordering::SeqCst);
|
||||
self.pointer.store(pointer, Ordering::SeqCst);
|
||||
Ok(pointer)
|
||||
@ -96,7 +98,7 @@ impl MemoryManager {
|
||||
memory: &Memory,
|
||||
allocator: &Function,
|
||||
object: &T,
|
||||
) -> Result<(i32, u32), PluginModuleError> {
|
||||
) -> Result<(u64, u64), PluginModuleError> {
|
||||
self.write_bytes(
|
||||
memory,
|
||||
allocator,
|
||||
@ -104,23 +106,65 @@ impl MemoryManager {
|
||||
)
|
||||
}
|
||||
|
||||
/// This function writes an object to the wasm memory using the allocator if
|
||||
/// necessary using length padding.
|
||||
///
|
||||
/// With length padding the first 8 bytes written are the length of the the
|
||||
/// following slice (The object serialized).
|
||||
pub fn write_data_as_pointer<T: Serialize>(
|
||||
&self,
|
||||
memory: &Memory,
|
||||
allocator: &Function,
|
||||
object: &T,
|
||||
) -> Result<u64, PluginModuleError> {
|
||||
self.write_bytes_as_pointer(
|
||||
memory,
|
||||
allocator,
|
||||
&bincode::serialize(object).map_err(PluginModuleError::Encoding)?,
|
||||
)
|
||||
}
|
||||
|
||||
/// This function writes an raw bytes to WASM memory returning a pointer and
|
||||
/// a length. Will realloc the buffer is not wide enough
|
||||
pub fn write_bytes(
|
||||
&self,
|
||||
memory: &Memory,
|
||||
allocator: &Function,
|
||||
array: &[u8],
|
||||
) -> Result<(i32, u32), PluginModuleError> {
|
||||
let len = array.len();
|
||||
bytes: &[u8],
|
||||
) -> Result<(u64, u64), PluginModuleError> {
|
||||
let len = bytes.len();
|
||||
let mem_position = self
|
||||
.get_pointer(len as u32, allocator)
|
||||
.map_err(PluginModuleError::MemoryAllocation)? as usize;
|
||||
memory.view()[mem_position..mem_position + len]
|
||||
.iter()
|
||||
.zip(array.iter())
|
||||
.zip(bytes.iter())
|
||||
.for_each(|(cell, byte)| cell.set(*byte));
|
||||
Ok((mem_position as i32, len as u32))
|
||||
Ok((mem_position as u64, len as u64))
|
||||
}
|
||||
|
||||
/// This function writes bytes to the wasm memory using the allocator if
|
||||
/// necessary using length padding.
|
||||
///
|
||||
/// With length padding the first 8 bytes written are the length of the the
|
||||
/// following slice.
|
||||
pub fn write_bytes_as_pointer(
|
||||
&self,
|
||||
memory: &Memory,
|
||||
allocator: &Function,
|
||||
bytes: &[u8],
|
||||
) -> Result<u64, PluginModuleError> {
|
||||
let len = bytes.len();
|
||||
let mem_position = self
|
||||
.get_pointer(len as u32 + 8, allocator)
|
||||
.map_err(PluginModuleError::MemoryAllocation)? as usize;
|
||||
// Here we write the length as le bytes followed by the slice data itself in
|
||||
// WASM memory
|
||||
memory.view()[mem_position..mem_position + len + 8]
|
||||
.iter()
|
||||
.zip((len as u64).to_le_bytes().iter().chain(bytes.iter()))
|
||||
.for_each(|(cell, byte)| cell.set(*byte));
|
||||
Ok(mem_position as u64)
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,14 +172,14 @@ impl MemoryManager {
|
||||
/// converts it to an object using bincode
|
||||
pub fn read_data<T: DeserializeOwned>(
|
||||
memory: &Memory,
|
||||
position: i32,
|
||||
length: u32,
|
||||
position: u64,
|
||||
length: u64,
|
||||
) -> Result<T, bincode::Error> {
|
||||
bincode::deserialize(&read_bytes(memory, position, length))
|
||||
}
|
||||
|
||||
/// This function read raw bytes from memory at a position with the array length
|
||||
pub fn read_bytes(memory: &Memory, position: i32, length: u32) -> Vec<u8> {
|
||||
pub fn read_bytes(memory: &Memory, position: u64, length: u64) -> Vec<u8> {
|
||||
memory.view()[(position as usize)..(position as usize) + length as usize]
|
||||
.iter()
|
||||
.map(|x| x.get())
|
||||
|
@ -84,7 +84,6 @@ impl Plugin {
|
||||
pub fn execute_prepared<T>(
|
||||
&self,
|
||||
ecs: &World,
|
||||
event_name: &str,
|
||||
event: &PreparedEventQuery<T>,
|
||||
) -> Result<Vec<T::Response>, PluginError>
|
||||
where
|
||||
@ -93,11 +92,11 @@ impl Plugin {
|
||||
self.modules
|
||||
.iter()
|
||||
.flat_map(|module| {
|
||||
module.try_execute(ecs, event_name, event).map(|x| {
|
||||
module.try_execute(ecs, event).map(|x| {
|
||||
x.map_err(|e| {
|
||||
PluginError::PluginModuleError(
|
||||
self.data.name.to_owned(),
|
||||
event_name.to_owned(),
|
||||
event.get_function_name().to_owned(),
|
||||
e,
|
||||
)
|
||||
})
|
||||
@ -123,7 +122,6 @@ impl PluginMgr {
|
||||
pub fn execute_prepared<T>(
|
||||
&self,
|
||||
ecs: &World,
|
||||
event_name: &str,
|
||||
event: &PreparedEventQuery<T>,
|
||||
) -> Result<Vec<T::Response>, PluginError>
|
||||
where
|
||||
@ -132,23 +130,18 @@ impl PluginMgr {
|
||||
Ok(self
|
||||
.plugins
|
||||
.par_iter()
|
||||
.map(|plugin| plugin.execute_prepared(ecs, event_name, event))
|
||||
.map(|plugin| plugin.execute_prepared(ecs, event))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn execute_event<T>(
|
||||
&self,
|
||||
ecs: &World,
|
||||
event_name: &str,
|
||||
event: &T,
|
||||
) -> Result<Vec<T::Response>, PluginError>
|
||||
pub fn execute_event<T>(&self, ecs: &World, event: &T) -> Result<Vec<T::Response>, PluginError>
|
||||
where
|
||||
T: Event,
|
||||
{
|
||||
self.execute_prepared(ecs, event_name, &PreparedEventQuery::new(event)?)
|
||||
self.execute_prepared(ecs, &PreparedEventQuery::new(event)?)
|
||||
}
|
||||
|
||||
pub fn from_dir<P: AsRef<Path>>(path: P) -> Result<Self, PluginError> {
|
||||
|
@ -43,8 +43,8 @@ impl PluginModule {
|
||||
let module = Module::new(&store, &wasm_data).expect("Can't compile");
|
||||
|
||||
// This is the function imported into the wasm environement
|
||||
fn raw_emit_actions(env: &HostFunctionEnvironement, ptr: u32, len: u32) {
|
||||
handle_actions(match env.read_data(ptr as i32, len) {
|
||||
fn raw_emit_actions(env: &HostFunctionEnvironement, ptr: i64, len: i64) {
|
||||
handle_actions(match env.read_data(from_i64(ptr), from_i64(len)) {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
tracing::error!(?e, "Can't decode action");
|
||||
@ -53,16 +53,15 @@ impl PluginModule {
|
||||
});
|
||||
}
|
||||
|
||||
fn raw_retrieve_action(env: &HostFunctionEnvironement, ptr: u32, len: u32) -> i64 {
|
||||
let out = match env.read_data(ptr as _, len) {
|
||||
fn raw_retrieve_action(env: &HostFunctionEnvironement, ptr: i64, len: i64) -> i64 {
|
||||
let out = match env.read_data(from_i64(ptr), from_i64(len)) {
|
||||
Ok(data) => retrieve_action(&env.ecs, data),
|
||||
Err(e) => Err(RetrieveError::BincodeError(e.to_string())),
|
||||
};
|
||||
|
||||
// If an error happen set the i64 to 0 so the WASM side can tell an error
|
||||
// occured
|
||||
let (ptr, len) = env.write_data(&out).unwrap();
|
||||
to_i64(ptr, len as _)
|
||||
to_i64(env.write_data_as_pointer(&out).unwrap())
|
||||
}
|
||||
|
||||
fn dbg(a: i32) {
|
||||
@ -112,19 +111,18 @@ impl PluginModule {
|
||||
pub fn try_execute<T>(
|
||||
&self,
|
||||
ecs: &World,
|
||||
event_name: &str,
|
||||
request: &PreparedEventQuery<T>,
|
||||
) -> Option<Result<T::Response, PluginModuleError>>
|
||||
where
|
||||
T: Event,
|
||||
{
|
||||
if !self.events.contains(event_name) {
|
||||
if !self.events.contains(&request.function_name) {
|
||||
return None;
|
||||
}
|
||||
// Store the ECS Pointer for later use in `retreives`
|
||||
let bytes = match self.ecs.execute_with(ecs, || {
|
||||
let mut state = self.wasm_state.lock().unwrap();
|
||||
execute_raw(self, &mut state, event_name, &request.bytes)
|
||||
execute_raw(self, &mut state, &request.function_name, &request.bytes)
|
||||
}) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Some(Err(e)),
|
||||
@ -137,6 +135,7 @@ impl PluginModule {
|
||||
/// reencoding for each module in every plugin)
|
||||
pub struct PreparedEventQuery<T> {
|
||||
bytes: Vec<u8>,
|
||||
function_name: String,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
@ -150,25 +149,36 @@ impl<T: Event> PreparedEventQuery<T> {
|
||||
{
|
||||
Ok(Self {
|
||||
bytes: bincode::serialize(&event).map_err(PluginError::Encoding)?,
|
||||
function_name: event.get_event_name(),
|
||||
_phantom: PhantomData::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_function_name(&self) -> &str { &self.function_name }
|
||||
}
|
||||
|
||||
fn from_i64(i: i64) -> (i32, i32) {
|
||||
/// This function split a u128 in two u64 encoding them as le bytes
|
||||
pub fn from_u128(i: u128) -> (u64, u64) {
|
||||
let i = i.to_le_bytes();
|
||||
(
|
||||
i32::from_le_bytes(i[0..4].try_into().unwrap()),
|
||||
i32::from_le_bytes(i[4..8].try_into().unwrap()),
|
||||
u64::from_le_bytes(i[0..8].try_into().unwrap()),
|
||||
u64::from_le_bytes(i[8..16].try_into().unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_i64(a: i32, b: i32) -> i64 {
|
||||
/// This function merge two u64 encoded as le in one u128
|
||||
pub fn to_u128(a: u64, b: u64) -> u128 {
|
||||
let a = a.to_le_bytes();
|
||||
let b = b.to_le_bytes();
|
||||
i64::from_le_bytes([a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3]])
|
||||
u128::from_le_bytes([a, b].concat().try_into().unwrap())
|
||||
}
|
||||
|
||||
/// This function encode a u64 into a i64 using le bytes
|
||||
pub fn to_i64(i: u64) -> i64 { i64::from_le_bytes(i.to_le_bytes()) }
|
||||
|
||||
/// This function decode a i64 into a u64 using le bytes
|
||||
pub fn from_i64(i: i64) -> u64 { u64::from_le_bytes(i.to_le_bytes()) }
|
||||
|
||||
// This function is not public because this function should not be used without
|
||||
// an interface to limit unsafe behaviours
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
@ -196,24 +206,28 @@ fn execute_raw(
|
||||
// We call the function with the pointer and the length
|
||||
|
||||
let function_result = func
|
||||
.call(&[Value::I32(mem_position as i32), Value::I32(len as i32)])
|
||||
.call(&[Value::I64(to_i64(mem_position)), Value::I64(to_i64(len))])
|
||||
.map_err(PluginModuleError::RunFunction)?;
|
||||
|
||||
// Waiting for `multi-value` to be added to LLVM. So we encode the two i32 as an
|
||||
// i64
|
||||
// Waiting for `multi-value` to be added to LLVM. So we encode a pointer to a
|
||||
// u128 that represent [u64; 2]
|
||||
|
||||
let (pointer, length) = from_i64(
|
||||
let u128_pointer = from_i64(
|
||||
function_result[0]
|
||||
.i64()
|
||||
.ok_or_else(PluginModuleError::InvalidArgumentType)?,
|
||||
);
|
||||
|
||||
let bytes = memory_manager::read_bytes(&module.memory, u128_pointer, 16);
|
||||
|
||||
// We read the return object and deserialize it
|
||||
|
||||
// The first 8 bytes are encoded as le and represent the pointer to the data
|
||||
// The next 8 bytes are encoded as le and represent the length of the data
|
||||
Ok(memory_manager::read_bytes(
|
||||
&module.memory,
|
||||
pointer,
|
||||
length as u32,
|
||||
u64::from_le_bytes(bytes[0..8].try_into().unwrap()),
|
||||
u64::from_le_bytes(bytes[8..16].try_into().unwrap()),
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ impl HostFunctionEnvironement {
|
||||
|
||||
/// This function is a safe interface to WASM memory that writes data to the
|
||||
/// memory returning a pointer and length
|
||||
pub fn write_data<T: Serialize>(&self, object: &T) -> Result<(i32, u32), PluginModuleError> {
|
||||
pub fn write_data<T: Serialize>(&self, object: &T) -> Result<(u64, u64), PluginModuleError> {
|
||||
self.memory_manager.write_data(
|
||||
self.memory.get_ref().unwrap(),
|
||||
self.allocator.get_ref().unwrap(),
|
||||
@ -45,12 +45,25 @@ impl HostFunctionEnvironement {
|
||||
)
|
||||
}
|
||||
|
||||
/// This function is a safe interface to WASM memory that writes data to the
|
||||
/// memory returning a pointer and length
|
||||
pub fn write_data_as_pointer<T: Serialize>(
|
||||
&self,
|
||||
object: &T,
|
||||
) -> Result<u64, PluginModuleError> {
|
||||
self.memory_manager.write_data_as_pointer(
|
||||
self.memory.get_ref().unwrap(),
|
||||
self.allocator.get_ref().unwrap(),
|
||||
object,
|
||||
)
|
||||
}
|
||||
|
||||
/// This function is a safe interface to WASM memory that reads memory from
|
||||
/// pointer and length returning an object
|
||||
pub fn read_data<T: DeserializeOwned>(
|
||||
&self,
|
||||
position: i32,
|
||||
length: u32,
|
||||
position: u64,
|
||||
length: u64,
|
||||
) -> Result<T, bincode::Error> {
|
||||
memory_manager::read_data(self.memory.get_ref().unwrap(), position, length)
|
||||
}
|
||||
|
@ -209,10 +209,8 @@ impl State {
|
||||
#[cfg(feature = "plugins")]
|
||||
ecs.insert(match PluginMgr::from_assets() {
|
||||
Ok(plugin_mgr) => {
|
||||
if let Err(e) =
|
||||
plugin_mgr.execute_event(&ecs, "on_load", &plugin_api::event::PluginLoadEvent {
|
||||
game_mode,
|
||||
})
|
||||
if let Err(e) = plugin_mgr
|
||||
.execute_event(&ecs, &plugin_api::event::PluginLoadEvent { game_mode })
|
||||
{
|
||||
tracing::error!(?e, "Failed to run plugin init");
|
||||
tracing::info!(
|
||||
|
@ -96,6 +96,8 @@ pub enum RetrieveResult {
|
||||
/// This trait is implement by all events and ensure type safety of FFI.
|
||||
pub trait Event: Serialize + DeserializeOwned + Send + Sync {
|
||||
type Response: Serialize + DeserializeOwned + Send + Sync;
|
||||
|
||||
fn get_event_name(&self) -> String;
|
||||
}
|
||||
|
||||
/// This module contains all events from the api
|
||||
@ -138,6 +140,8 @@ pub mod event {
|
||||
|
||||
impl Event for ChatCommandEvent {
|
||||
type Response = Result<Vec<String>, String>;
|
||||
|
||||
fn get_event_name(&self) -> String { format!("on_command_{}", self.command) }
|
||||
}
|
||||
|
||||
/// This struct represent a player
|
||||
@ -162,11 +166,13 @@ pub mod event {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct PlayerJoinEvent {
|
||||
pub player_name: String,
|
||||
pub player_id: Uid,
|
||||
pub player_id: [u8; 16],
|
||||
}
|
||||
|
||||
impl Event for PlayerJoinEvent {
|
||||
type Response = PlayerJoinResult;
|
||||
|
||||
fn get_event_name(&self) -> String { "on_join".to_owned() }
|
||||
}
|
||||
|
||||
/// This is the return type of an `on_join` event. See [`PlayerJoinEvent`]
|
||||
@ -175,8 +181,9 @@ pub mod event {
|
||||
/// - `CloseConnection` will kick the player.
|
||||
/// - `None` will let the player join the server.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum PlayerJoinResult {
|
||||
CloseConnection,
|
||||
Kick(String),
|
||||
None,
|
||||
}
|
||||
|
||||
@ -205,6 +212,8 @@ pub mod event {
|
||||
|
||||
impl Event for PluginLoadEvent {
|
||||
type Response = ();
|
||||
|
||||
fn get_event_name(&self) -> String { "on_load".to_owned() }
|
||||
}
|
||||
|
||||
// impl Default for PlayerJoinResult {
|
||||
|
@ -16,8 +16,8 @@ pub fn event_handler(_args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let out: proc_macro2::TokenStream = quote! {
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
#[no_mangle]
|
||||
pub fn #fn_name(intern__ptr: i32, intern__len: i32) -> i64 {
|
||||
let input = ::veloren_plugin_rt::read_input(intern__ptr,intern__len as u32).unwrap();
|
||||
pub fn #fn_name(intern__ptr: i64, intern__len: i64) -> i64 {
|
||||
let input = ::veloren_plugin_rt::read_input(intern__ptr as _,intern__len as _).unwrap();
|
||||
#[inline]
|
||||
fn inner(#fn_args) #fn_return {
|
||||
#fn_body
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use veloren_plugin_rt::{
|
||||
api::{event::*, Action, GameMode},
|
||||
*,
|
||||
@ -29,14 +31,12 @@ pub fn on_command_testplugin(command: ChatCommandEvent) -> Result<Vec<String>, S
|
||||
)])
|
||||
}
|
||||
|
||||
static COUNTER: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[event_handler]
|
||||
pub fn on_player_join(input: PlayerJoinEvent) -> PlayerJoinResult {
|
||||
emit_action(Action::PlayerSendMessage(
|
||||
input.player_id,
|
||||
format!("Welcome {} on our server", input.player_name),
|
||||
));
|
||||
if input.player_name == "Cheater123" {
|
||||
PlayerJoinResult::CloseConnection
|
||||
pub fn on_join(input: PlayerJoinEvent) -> PlayerJoinResult {
|
||||
if COUNTER.swap(!COUNTER.load(Ordering::SeqCst), Ordering::SeqCst) {
|
||||
PlayerJoinResult::Kick(format!("You are a cheater {:?}", input))
|
||||
} else {
|
||||
PlayerJoinResult::None
|
||||
}
|
||||
|
@ -18,8 +18,8 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
extern "C" {
|
||||
fn raw_emit_actions(ptr: *const u8, len: usize);
|
||||
fn raw_retrieve_action(ptr: *const u8, len: usize) -> i64;
|
||||
fn raw_emit_actions(ptr: i64, len: i64);
|
||||
fn raw_retrieve_action(ptr: i64, len: i64) -> i64;
|
||||
pub fn dbg(i: i32);
|
||||
}
|
||||
|
||||
@ -28,8 +28,11 @@ pub fn retrieve_action<T: DeserializeOwned>(_actions: &api::Retrieve) -> Result<
|
||||
{
|
||||
let ret = bincode::serialize(&_actions).expect("Can't serialize action in emit");
|
||||
unsafe {
|
||||
let (ptr, len) = from_i64(raw_retrieve_action(ret.as_ptr(), ret.len()));
|
||||
let a = ::std::slice::from_raw_parts(ptr as _, len as _);
|
||||
let ptr = raw_retrieve_action(to_i64(ret.as_ptr() as _), to_i64(ret.len() as _));
|
||||
let ptr = from_i64(ptr);
|
||||
let len =
|
||||
u64::from_le_bytes(std::slice::from_raw_parts(ptr as _, 8).try_into().unwrap());
|
||||
let a = ::std::slice::from_raw_parts((ptr + 8) as _, len as _);
|
||||
bincode::deserialize::<Result<T, RetrieveError>>(&a)
|
||||
.map_err(|x| RetrieveError::BincodeError(x.to_string()))?
|
||||
}
|
||||
@ -45,36 +48,54 @@ pub fn emit_actions(_actions: Vec<api::Action>) {
|
||||
{
|
||||
let ret = bincode::serialize(&_actions).expect("Can't serialize action in emit");
|
||||
unsafe {
|
||||
raw_emit_actions(ret.as_ptr(), ret.len());
|
||||
raw_emit_actions(to_i64(ret.as_ptr() as _), to_i64(ret.len() as _));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_input<T>(ptr: i32, len: u32) -> Result<T, &'static str>
|
||||
pub fn read_input<T>(ptr: i64, len: i64) -> Result<T, &'static str>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let slice = unsafe { ::std::slice::from_raw_parts(ptr as _, len as _) };
|
||||
let slice = unsafe { ::std::slice::from_raw_parts(from_i64(ptr) as _, from_i64(len) as _) };
|
||||
bincode::deserialize(slice).map_err(|_| "Failed to deserialize function input")
|
||||
}
|
||||
|
||||
pub fn from_i64(i: i64) -> (i32, i32) {
|
||||
/// This function split a u128 in two u64 encoding them as le bytes
|
||||
pub fn from_u128(i: u128) -> (u64, u64) {
|
||||
let i = i.to_le_bytes();
|
||||
(
|
||||
i32::from_le_bytes(i[0..4].try_into().unwrap()),
|
||||
i32::from_le_bytes(i[4..8].try_into().unwrap()),
|
||||
u64::from_le_bytes(i[0..8].try_into().unwrap()),
|
||||
u64::from_le_bytes(i[8..16].try_into().unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_i64(a: i32, b: i32) -> i64 {
|
||||
/// This function merge two u64 encoded as le in one u128
|
||||
pub fn to_u128(a: u64, b: u64) -> u128 {
|
||||
let a = a.to_le_bytes();
|
||||
let b = b.to_le_bytes();
|
||||
i64::from_le_bytes([a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3]])
|
||||
u128::from_le_bytes([a, b].concat().try_into().unwrap())
|
||||
}
|
||||
|
||||
/// This function encode a u64 into a i64 using le bytes
|
||||
pub fn to_i64(i: u64) -> i64 { i64::from_le_bytes(i.to_le_bytes()) }
|
||||
|
||||
/// This function decode a i64 into a u64 using le bytes
|
||||
pub fn from_i64(i: i64) -> u64 { u64::from_le_bytes(i.to_le_bytes()) }
|
||||
|
||||
static mut VEC: Vec<u8> = vec![];
|
||||
static mut DATA: Vec<u8> = vec![];
|
||||
|
||||
pub fn write_output(value: impl Serialize) -> i64 {
|
||||
let ret = bincode::serialize(&value).expect("Can't serialize event output");
|
||||
to_i64(ret.as_ptr() as _, ret.len() as _)
|
||||
unsafe {
|
||||
VEC = bincode::serialize(&value).expect("Can't serialize event output");
|
||||
DATA = [
|
||||
(VEC.as_ptr() as u64).to_le_bytes(),
|
||||
(VEC.len() as u64).to_le_bytes(),
|
||||
]
|
||||
.concat();
|
||||
to_i64(DATA.as_ptr() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
static mut BUFFERS: Vec<u8> = Vec::new();
|
||||
@ -83,7 +104,7 @@ static mut BUFFERS: Vec<u8> = Vec::new();
|
||||
/// # Safety
|
||||
/// This function should never be used only intented to by used by the host
|
||||
#[no_mangle]
|
||||
pub unsafe fn wasm_prepare_buffer(size: i32) -> i32 {
|
||||
pub unsafe fn wasm_prepare_buffer(size: i32) -> i64 {
|
||||
BUFFERS = vec![0u8; size as usize];
|
||||
BUFFERS.as_ptr() as i32
|
||||
BUFFERS.as_ptr() as i64
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ pub mod login_provider;
|
||||
pub mod metrics;
|
||||
pub mod persistence;
|
||||
pub mod presence;
|
||||
mod register;
|
||||
pub mod rtsim;
|
||||
pub mod settings;
|
||||
pub mod state_ext;
|
||||
@ -58,7 +59,7 @@ use common::{
|
||||
assets::AssetExt,
|
||||
cmd::ChatCommand,
|
||||
comp,
|
||||
comp::{item::MaterialStatManifest, CharacterAbility},
|
||||
comp::{item::MaterialStatManifest, Admin, CharacterAbility, Player, Stats},
|
||||
event::{EventBus, ServerEvent},
|
||||
recipe::default_recipe_book,
|
||||
resources::TimeOfDay,
|
||||
@ -68,19 +69,22 @@ use common::{
|
||||
};
|
||||
use common_net::{
|
||||
msg::{
|
||||
ClientType, DisconnectReason, ServerGeneral, ServerInfo, ServerInit, ServerMsg, WorldMapMsg,
|
||||
CharacterInfo, ClientType, DisconnectReason, PlayerInfo, PlayerListUpdate, ServerGeneral,
|
||||
ServerInfo, ServerInit, ServerMsg, WorldMapMsg,
|
||||
},
|
||||
sync::WorldSyncExt,
|
||||
};
|
||||
#[cfg(feature = "plugins")]
|
||||
use common_sys::plugin::PluginMgr;
|
||||
use common_sys::state::State;
|
||||
use metrics::{PhysicsMetrics, StateTickMetrics, TickMetrics};
|
||||
use hashbrown::HashMap;
|
||||
use metrics::{PhysicsMetrics, PlayerMetrics, StateTickMetrics, TickMetrics};
|
||||
use network::{Network, Pid, ProtocolAddr};
|
||||
use persistence::{
|
||||
character_loader::{CharacterLoader, CharacterLoaderResponseKind},
|
||||
character_updater::CharacterUpdater,
|
||||
};
|
||||
use plugin_api::Uid;
|
||||
use prometheus::Registry;
|
||||
use prometheus_hyper::Server as PrometheusServer;
|
||||
use specs::{join::Join, Builder, Entity as EcsEntity, RunNow, SystemData, WorldExt};
|
||||
@ -195,7 +199,6 @@ impl Server {
|
||||
state.ecs_mut().insert(sys::EntitySyncTimer::default());
|
||||
state.ecs_mut().insert(sys::GeneralMsgTimer::default());
|
||||
state.ecs_mut().insert(sys::PingMsgTimer::default());
|
||||
state.ecs_mut().insert(sys::RegisterMsgTimer::default());
|
||||
state
|
||||
.ecs_mut()
|
||||
.insert(sys::CharacterScreenMsgTimer::default());
|
||||
@ -507,7 +510,7 @@ impl Server {
|
||||
// (e.g. run before controller system)
|
||||
//TODO: run in parallel
|
||||
sys::msg::general::Sys.run_now(&self.state.ecs());
|
||||
sys::msg::register::Sys.run_now(&self.state.ecs());
|
||||
self.register_run();
|
||||
sys::msg::character_screen::Sys.run_now(&self.state.ecs());
|
||||
sys::msg::in_game::Sys.run_now(&self.state.ecs());
|
||||
sys::msg::ping::Sys.run_now(&self.state.ecs());
|
||||
@ -711,7 +714,6 @@ impl Server {
|
||||
let state = self.state.ecs();
|
||||
(state.read_resource::<sys::GeneralMsgTimer>().nanos
|
||||
+ state.read_resource::<sys::PingMsgTimer>().nanos
|
||||
+ state.read_resource::<sys::RegisterMsgTimer>().nanos
|
||||
+ state.read_resource::<sys::CharacterScreenMsgTimer>().nanos
|
||||
+ state.read_resource::<sys::InGameMsgTimer>().nanos) as i64
|
||||
};
|
||||
@ -917,6 +919,75 @@ impl Server {
|
||||
self.state.cleanup();
|
||||
}
|
||||
|
||||
pub fn register_run(&mut self) {
|
||||
let world = self.state_mut().ecs_mut();
|
||||
let entities = world.entities();
|
||||
let player_metrics = world.read_resource::<PlayerMetrics>();
|
||||
let uids = world.read_storage::<Uid>();
|
||||
let clients = world.read_storage::<Client>();
|
||||
let mut players = world.write_storage::<Player>();
|
||||
let stats = world.read_storage::<Stats>();
|
||||
let mut login_provider = world.write_resource::<LoginProvider>();
|
||||
let mut admins = world.write_storage::<Admin>();
|
||||
let editable_settings = world.read_resource::<EditableSettings>();
|
||||
|
||||
// Player list to send new players.
|
||||
let player_list = (&uids, &players, stats.maybe(), admins.maybe())
|
||||
.join()
|
||||
.map(|(uid, player, stats, admin)| {
|
||||
(*uid, PlayerInfo {
|
||||
is_online: true,
|
||||
is_admin: admin.is_some(),
|
||||
player_alias: player.alias.clone(),
|
||||
character: stats.map(|stats| CharacterInfo {
|
||||
name: stats.name.clone(),
|
||||
}),
|
||||
})
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
// List of new players to update player lists of all clients.
|
||||
let mut new_players = Vec::new();
|
||||
|
||||
for (entity, client) in (&entities, &clients).join() {
|
||||
let _ = sys::msg::try_recv_all(client, 0, |client, msg| {
|
||||
register::handle_register_msg(
|
||||
&world,
|
||||
&player_list,
|
||||
&mut new_players,
|
||||
entity,
|
||||
client,
|
||||
&player_metrics,
|
||||
&mut login_provider,
|
||||
&mut admins,
|
||||
&mut players,
|
||||
&editable_settings,
|
||||
msg,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
// Handle new players.
|
||||
// Tell all clients to add them to the player list.
|
||||
for entity in new_players {
|
||||
if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) {
|
||||
let mut lazy_msg = None;
|
||||
for (_, client) in (&players, &clients).join() {
|
||||
if lazy_msg.is_none() {
|
||||
lazy_msg = Some(client.prepare(ServerGeneral::PlayerListUpdate(
|
||||
PlayerListUpdate::Add(*uid, PlayerInfo {
|
||||
player_alias: player.alias.clone(),
|
||||
is_online: true,
|
||||
is_admin: admins.get(entity).is_some(),
|
||||
character: None, // new players will be on character select.
|
||||
}),
|
||||
)));
|
||||
}
|
||||
lazy_msg.as_ref().map(|ref msg| client.send_prepared(&msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_client(
|
||||
&mut self,
|
||||
client: crate::connection_handler::IncomingClient,
|
||||
@ -1033,11 +1104,9 @@ impl Server {
|
||||
} else {
|
||||
#[cfg(feature = "plugins")]
|
||||
{
|
||||
use common::uid::Uid;
|
||||
let plugin_manager = self.state.ecs().read_resource::<PluginMgr>();
|
||||
let rs = plugin_manager.execute_event(
|
||||
self.state.ecs(),
|
||||
&format!("on_command_{}", &kwd),
|
||||
&plugin_api::event::ChatCommandEvent {
|
||||
command: kwd.clone(),
|
||||
command_args: args.split(' ').map(|x| x.to_owned()).collect(),
|
||||
|
@ -1,7 +1,10 @@
|
||||
use crate::settings::BanRecord;
|
||||
use authc::{AuthClient, AuthClientError, AuthToken, Uuid};
|
||||
use common_net::msg::RegisterError;
|
||||
use common_sys::plugin::PluginMgr;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use plugin_api::event::{PlayerJoinEvent, PlayerJoinResult};
|
||||
use specs::World;
|
||||
use std::str::FromStr;
|
||||
use tracing::{error, info};
|
||||
|
||||
@ -53,6 +56,8 @@ impl LoginProvider {
|
||||
pub fn try_login(
|
||||
&mut self,
|
||||
username_or_token: &str,
|
||||
world: &World,
|
||||
plugin_manager: &PluginMgr,
|
||||
admins: &HashSet<Uuid>,
|
||||
whitelist: &HashSet<Uuid>,
|
||||
banlist: &HashMap<Uuid, BanRecord>,
|
||||
@ -74,6 +79,22 @@ impl LoginProvider {
|
||||
return Err(RegisterError::NotOnWhitelist);
|
||||
}
|
||||
|
||||
match plugin_manager.execute_event(&world, &PlayerJoinEvent {
|
||||
player_name: username.clone(),
|
||||
player_id: *uuid.as_bytes(),
|
||||
}) {
|
||||
Ok(e) => {
|
||||
for i in e.into_iter() {
|
||||
if let PlayerJoinResult::Kick(a) = i {
|
||||
return Err(RegisterError::Kicked(a));
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error occured while executing `on_join`: {:?}",e);
|
||||
},
|
||||
};
|
||||
|
||||
// add the user to self.accounts
|
||||
self.login(uuid, username.clone())?;
|
||||
|
||||
|
81
server/src/register.rs
Normal file
81
server/src/register.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use common::comp::{Admin, Player};
|
||||
use common_net::msg::{
|
||||
ClientRegister, PlayerInfo, PlayerListUpdate, RegisterError, ServerGeneral,
|
||||
ServerRegisterAnswer,
|
||||
};
|
||||
use common_sys::plugin::PluginMgr;
|
||||
use hashbrown::HashMap;
|
||||
use plugin_api::Uid;
|
||||
use specs::{
|
||||
shred::{Fetch, FetchMut},
|
||||
Entity, World, WorldExt, WriteStorage,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
client::Client, login_provider::LoginProvider, metrics::PlayerMetrics, EditableSettings,
|
||||
};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn handle_register_msg(
|
||||
world: &World,
|
||||
player_list: &HashMap<Uid, PlayerInfo>,
|
||||
new_players: &mut Vec<Entity>,
|
||||
entity: Entity,
|
||||
client: &Client,
|
||||
player_metrics: &Fetch<'_, PlayerMetrics>,
|
||||
login_provider: &mut FetchMut<'_, LoginProvider>,
|
||||
admins: &mut WriteStorage<'_, Admin>,
|
||||
players: &mut WriteStorage<'_, Player>,
|
||||
editable_settings: &Fetch<'_, EditableSettings>,
|
||||
msg: ClientRegister,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
let plugin_mgr = world.read_resource::<PluginMgr>();
|
||||
let (username, uuid) = match login_provider.try_login(
|
||||
&msg.token_or_username,
|
||||
world,
|
||||
&plugin_mgr,
|
||||
&*editable_settings.admins,
|
||||
&*editable_settings.whitelist,
|
||||
&*editable_settings.banlist,
|
||||
) {
|
||||
Err(err) => {
|
||||
client.send(ServerRegisterAnswer::Err(err))?;
|
||||
return Ok(());
|
||||
},
|
||||
Ok((username, uuid)) => (username, uuid),
|
||||
};
|
||||
|
||||
let player = Player::new(username, uuid);
|
||||
let is_admin = editable_settings.admins.contains(&uuid);
|
||||
|
||||
if !player.is_valid() {
|
||||
// Invalid player
|
||||
client.send(ServerRegisterAnswer::Err(RegisterError::InvalidCharacter))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !players.contains(entity) {
|
||||
// Add Player component to this client
|
||||
let _ = players.insert(entity, player);
|
||||
player_metrics.players_connected.inc();
|
||||
|
||||
// Give the Admin component to the player if their name exists in
|
||||
// admin list
|
||||
if is_admin {
|
||||
let _ = admins.insert(entity, Admin);
|
||||
}
|
||||
|
||||
// Tell the client its request was successful.
|
||||
client.send(ServerRegisterAnswer::Ok(()))?;
|
||||
|
||||
// Send initial player list
|
||||
client.send(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init(
|
||||
player_list.clone(),
|
||||
)))?;
|
||||
|
||||
// Add to list to notify all clients of the new player
|
||||
new_players.push(entity);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -19,7 +19,6 @@ use std::{
|
||||
pub type EntitySyncTimer = SysTimer<entity_sync::Sys>;
|
||||
pub type GeneralMsgTimer = SysTimer<msg::general::Sys>;
|
||||
pub type PingMsgTimer = SysTimer<msg::ping::Sys>;
|
||||
pub type RegisterMsgTimer = SysTimer<msg::register::Sys>;
|
||||
pub type CharacterScreenMsgTimer = SysTimer<msg::character_screen::Sys>;
|
||||
pub type InGameMsgTimer = SysTimer<msg::in_game::Sys>;
|
||||
pub type SentinelTimer = SysTimer<sentinel::Sys>;
|
||||
|
@ -2,14 +2,13 @@ pub mod character_screen;
|
||||
pub mod general;
|
||||
pub mod in_game;
|
||||
pub mod ping;
|
||||
pub mod register;
|
||||
|
||||
use crate::client::Client;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
/// handles all send msg and calls a handle fn
|
||||
/// Aborts when a error occurred returns cnt of successful msg otherwise
|
||||
pub(in crate::sys::msg) fn try_recv_all<M, F>(
|
||||
pub(crate) fn try_recv_all<M, F>(
|
||||
client: &Client,
|
||||
stream_id: u8,
|
||||
mut f: F,
|
||||
|
@ -1,172 +0,0 @@
|
||||
use super::super::SysTimer;
|
||||
use crate::{
|
||||
client::Client, login_provider::LoginProvider, metrics::PlayerMetrics, EditableSettings,
|
||||
};
|
||||
use common::{
|
||||
comp::{Admin, Player, Stats},
|
||||
span,
|
||||
uid::Uid,
|
||||
};
|
||||
use common_net::msg::{
|
||||
CharacterInfo, ClientRegister, PlayerInfo, PlayerListUpdate, RegisterError, ServerGeneral,
|
||||
ServerRegisterAnswer,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use specs::{Entities, Join, ReadExpect, ReadStorage, System, Write, WriteExpect, WriteStorage};
|
||||
|
||||
impl Sys {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_register_msg(
|
||||
player_list: &HashMap<Uid, PlayerInfo>,
|
||||
new_players: &mut Vec<specs::Entity>,
|
||||
entity: specs::Entity,
|
||||
client: &Client,
|
||||
player_metrics: &ReadExpect<'_, PlayerMetrics>,
|
||||
login_provider: &mut WriteExpect<'_, LoginProvider>,
|
||||
admins: &mut WriteStorage<'_, Admin>,
|
||||
players: &mut WriteStorage<'_, Player>,
|
||||
editable_settings: &ReadExpect<'_, EditableSettings>,
|
||||
msg: ClientRegister,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
let (username, uuid) = match login_provider.try_login(
|
||||
&msg.token_or_username,
|
||||
&*editable_settings.admins,
|
||||
&*editable_settings.whitelist,
|
||||
&*editable_settings.banlist,
|
||||
) {
|
||||
Err(err) => {
|
||||
client.send(ServerRegisterAnswer::Err(err))?;
|
||||
return Ok(());
|
||||
},
|
||||
Ok((username, uuid)) => (username, uuid),
|
||||
};
|
||||
|
||||
let player = Player::new(username, uuid);
|
||||
let is_admin = editable_settings.admins.contains(&uuid);
|
||||
|
||||
if !player.is_valid() {
|
||||
// Invalid player
|
||||
client.send(ServerRegisterAnswer::Err(RegisterError::InvalidCharacter))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !players.contains(entity) {
|
||||
// Add Player component to this client
|
||||
let _ = players.insert(entity, player);
|
||||
player_metrics.players_connected.inc();
|
||||
|
||||
// Give the Admin component to the player if their name exists in
|
||||
// admin list
|
||||
if is_admin {
|
||||
let _ = admins.insert(entity, Admin);
|
||||
}
|
||||
|
||||
// Tell the client its request was successful.
|
||||
client.send(ServerRegisterAnswer::Ok(()))?;
|
||||
|
||||
// Send initial player list
|
||||
client.send(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init(
|
||||
player_list.clone(),
|
||||
)))?;
|
||||
|
||||
// Add to list to notify all clients of the new player
|
||||
new_players.push(entity);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This system will handle new messages from clients
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
#[allow(clippy::type_complexity)]
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
ReadExpect<'a, PlayerMetrics>,
|
||||
Write<'a, SysTimer<Self>>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Client>,
|
||||
WriteStorage<'a, Player>,
|
||||
ReadStorage<'a, Stats>,
|
||||
WriteExpect<'a, LoginProvider>,
|
||||
WriteStorage<'a, Admin>,
|
||||
ReadExpect<'a, EditableSettings>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
(
|
||||
entities,
|
||||
player_metrics,
|
||||
mut timer,
|
||||
uids,
|
||||
clients,
|
||||
mut players,
|
||||
stats,
|
||||
mut login_provider,
|
||||
mut admins,
|
||||
editable_settings,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
span!(_guard, "run", "msg::register::Sys::run");
|
||||
timer.start();
|
||||
|
||||
// Player list to send new players.
|
||||
let player_list = (&uids, &players, stats.maybe(), admins.maybe())
|
||||
.join()
|
||||
.map(|(uid, player, stats, admin)| {
|
||||
(*uid, PlayerInfo {
|
||||
is_online: true,
|
||||
is_admin: admin.is_some(),
|
||||
player_alias: player.alias.clone(),
|
||||
character: stats.map(|stats| CharacterInfo {
|
||||
name: stats.name.clone(),
|
||||
}),
|
||||
})
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
// List of new players to update player lists of all clients.
|
||||
let mut new_players = Vec::new();
|
||||
|
||||
for (entity, client) in (&entities, &clients).join() {
|
||||
let _ = super::try_recv_all(client, 0, |client, msg| {
|
||||
Self::handle_register_msg(
|
||||
&player_list,
|
||||
&mut new_players,
|
||||
entity,
|
||||
client,
|
||||
&player_metrics,
|
||||
&mut login_provider,
|
||||
&mut admins,
|
||||
&mut players,
|
||||
&editable_settings,
|
||||
msg,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
// Handle new players.
|
||||
// Tell all clients to add them to the player list.
|
||||
for entity in new_players {
|
||||
if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) {
|
||||
let mut lazy_msg = None;
|
||||
for (_, client) in (&players, &clients).join() {
|
||||
if lazy_msg.is_none() {
|
||||
lazy_msg = Some(client.prepare(ServerGeneral::PlayerListUpdate(
|
||||
PlayerListUpdate::Add(*uid, PlayerInfo {
|
||||
player_alias: player.alias.clone(),
|
||||
is_online: true,
|
||||
is_admin: admins.get(entity).is_some(),
|
||||
character: None, // new players will be on character select.
|
||||
}),
|
||||
)));
|
||||
}
|
||||
lazy_msg.as_ref().map(|ref msg| client.send_prepared(&msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timer.end()
|
||||
}
|
||||
}
|
@ -134,6 +134,7 @@ impl PlayState for MainMenuState {
|
||||
localized_strings.get("main.login.authentication_error"),
|
||||
e
|
||||
),
|
||||
client::Error::Kicked(e) => e,
|
||||
client::Error::TooManyPlayers => {
|
||||
localized_strings.get("main.login.server_full").into()
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user