From dbd4d70b79607ed4c844cde13e5486c2bd8cda25 Mon Sep 17 00:00:00 2001
From: ccgauche <gaucheron.laurent@gmail.com>
Date: Mon, 1 Mar 2021 19:00:44 +0100
Subject: [PATCH 1/7] Lots of improvement in pointer management (switched from
 i32 to u64) + New event implemented

---
 client/src/error.rs                     |   1 +
 client/src/lib.rs                       |   1 +
 common/net/src/msg/server.rs            |   1 +
 common/src/comp/mod.rs                  |   2 +-
 common/sys/src/plugin/memory_manager.rs |  58 +++--
 common/sys/src/plugin/module.rs         |  35 ++--
 common/sys/src/plugin/wasm_env.rs       |  19 +-
 plugin/api/src/lib.rs                   |   5 +-
 plugin/derive/src/lib.rs                |   4 +-
 plugin/rt/examples/hello.rs             |  14 +-
 plugin/rt/src/lib.rs                    |  49 +++--
 server/src/lib.rs                       |   2 +-
 server/src/login_provider.rs            |  21 ++
 server/src/sys/msg/register.rs          | 268 +++++++++++-------------
 voxygen/src/menu/main/mod.rs            |   1 +
 15 files changed, 281 insertions(+), 200 deletions(-)

diff --git a/client/src/error.rs b/client/src/error.rs
index 2a6ac310ef..ef2a94f34c 100644
--- a/client/src/error.rs
+++ b/client/src/error.rs
@@ -4,6 +4,7 @@ use network::{ParticipantError, StreamError};
 
 #[derive(Debug)]
 pub enum Error {
+    KickedByPlugin(String),
     NetworkErr(NetworkError),
     ParticipantErr(ParticipantError),
     StreamErr(StreamError),
diff --git a/client/src/lib.rs b/client/src/lib.rs
index c30c5db091..866904ec78 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -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::KickedByPlugin(err)) => Err(Error::KickedByPlugin(err)),
             Err(RegisterError::Banned(reason)) => Err(Error::Banned(reason)),
             Ok(()) => {
                 self.registered = true;
diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs
index 2f55c0d21f..146bde5773 100644
--- a/common/net/src/msg/server.rs
+++ b/common/net/src/msg/server.rs
@@ -191,6 +191,7 @@ pub enum RegisterError {
     AlreadyLoggedIn,
     AuthError(String),
     Banned(String),
+    KickedByPlugin(String),
     InvalidCharacter,
     NotOnWhitelist,
     //TODO: InvalidAlias,
diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs
index 8e342cde4e..f54f44abed 100644
--- a/common/src/comp/mod.rs
+++ b/common/src/comp/mod.rs
@@ -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;
diff --git a/common/sys/src/plugin/memory_manager.rs b/common/sys/src/plugin/memory_manager.rs
index 771e78b9e4..5d317fe0fa 100644
--- a/common/sys/src/plugin/memory_manager.rs
+++ b/common/sys/src/plugin/memory_manager.rs
@@ -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,6 +106,19 @@ impl MemoryManager {
         )
     }
 
+    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(
@@ -111,7 +126,7 @@ impl MemoryManager {
         memory: &Memory,
         allocator: &Function,
         array: &[u8],
-    ) -> Result<(i32, u32), PluginModuleError> {
+    ) -> Result<(u64, u64), PluginModuleError> {
         let len = array.len();
         let mem_position = self
             .get_pointer(len as u32, allocator)
@@ -120,7 +135,24 @@ impl MemoryManager {
             .iter()
             .zip(array.iter())
             .for_each(|(cell, byte)| cell.set(*byte));
-        Ok((mem_position as i32, len as u32))
+        Ok((mem_position as u64, len as u64))
+    }
+
+    pub fn write_bytes_as_pointer(
+        &self,
+        memory: &Memory,
+        allocator: &Function,
+        array: &[u8],
+    ) -> Result<u64, PluginModuleError> {
+        let len = array.len();
+        let mem_position = self
+            .get_pointer(len as u32 + 8, allocator)
+            .map_err(PluginModuleError::MemoryAllocation)? as usize;
+        memory.view()[mem_position..mem_position + len + 8]
+            .iter()
+            .zip((len as u64).to_le_bytes().iter().chain(array.iter()))
+            .for_each(|(cell, byte)| cell.set(*byte));
+        Ok(mem_position as u64)
     }
 }
 
@@ -128,14 +160,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())
diff --git a/common/sys/src/plugin/module.rs b/common/sys/src/plugin/module.rs
index c8b16d613c..248e068237 100644
--- a/common/sys/src/plugin/module.rs
+++ b/common/sys/src/plugin/module.rs
@@ -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) {
@@ -155,20 +154,24 @@ impl<T: Event> PreparedEventQuery<T> {
     }
 }
 
-fn from_i64(i: i64) -> (i32, i32) {
+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 {
+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())
 }
 
+pub fn to_i64(i: u64) -> i64 { i64::from_le_bytes(i.to_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 +199,26 @@ 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
 
-    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
 
     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()),
     ))
 }
 
diff --git a/common/sys/src/plugin/wasm_env.rs b/common/sys/src/plugin/wasm_env.rs
index e383f7f3d5..bcd5ec3d55 100644
--- a/common/sys/src/plugin/wasm_env.rs
+++ b/common/sys/src/plugin/wasm_env.rs
@@ -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)
     }
diff --git a/plugin/api/src/lib.rs b/plugin/api/src/lib.rs
index 043ac0ea0f..2d159b3c25 100644
--- a/plugin/api/src/lib.rs
+++ b/plugin/api/src/lib.rs
@@ -56,7 +56,7 @@ 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 {
@@ -64,8 +64,9 @@ pub mod event {
     }
 
     #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
+    #[repr(u8)]
     pub enum PlayerJoinResult {
-        CloseConnection,
+        CloseConnection(String),
         None,
     }
 
diff --git a/plugin/derive/src/lib.rs b/plugin/derive/src/lib.rs
index 69fd927fa5..d275848d87 100644
--- a/plugin/derive/src/lib.rs
+++ b/plugin/derive/src/lib.rs
@@ -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
diff --git a/plugin/rt/examples/hello.rs b/plugin/rt/examples/hello.rs
index bd4ad051db..752a6f32b8 100644
--- a/plugin/rt/examples/hello.rs
+++ b/plugin/rt/examples/hello.rs
@@ -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::CloseConnection(format!("You are a cheater {:?}", input))
     } else {
         PlayerJoinResult::None
     }
diff --git a/plugin/rt/src/lib.rs b/plugin/rt/src/lib.rs
index 954f412e52..78e1990395 100644
--- a/plugin/rt/src/lib.rs
+++ b/plugin/rt/src/lib.rs
@@ -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,50 @@ 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) {
+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 {
+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())
 }
 
+pub fn to_i64(i: u64) -> i64 { i64::from_le_bytes(i.to_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 +100,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
 }
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 089133d7bd..2f049e44c8 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -507,7 +507,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());
+        sys::msg::register::register_run(self.state_mut().ecs_mut());
         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());
diff --git a/server/src/login_provider.rs b/server/src/login_provider.rs
index 67c5aaaa7f..a4205ff28f 100644
--- a/server/src/login_provider.rs
+++ b/server/src/login_provider.rs
@@ -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, "on_join", &PlayerJoinEvent {
+                    player_name: username.clone(),
+                    player_id: uuid.as_bytes().clone(),
+                }) {
+                    Ok(e) => {
+                        for i in e.into_iter() {
+                            if let PlayerJoinResult::CloseConnection(a) = i {
+                                return Err(RegisterError::KickedByPlugin(a));
+                            }
+                        }
+                    },
+                    Err(e) => {
+                        error!("Error occured while executing `on_join`: {:?}",e);
+                    },
+                };
+
                 // add the user to self.accounts
                 self.login(uuid, username.clone())?;
 
diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs
index e433f4d4ef..fc807662d6 100644
--- a/server/src/sys/msg/register.rs
+++ b/server/src/sys/msg/register.rs
@@ -1,4 +1,3 @@
-use super::super::SysTimer;
 use crate::{
     client::Client, login_provider::LoginProvider, metrics::PlayerMetrics, EditableSettings,
 };
@@ -11,162 +10,151 @@ use common_net::msg::{
     CharacterInfo, ClientRegister, PlayerInfo, PlayerListUpdate, RegisterError, ServerGeneral,
     ServerRegisterAnswer,
 };
+use common_sys::plugin::PluginMgr;
 use hashbrown::HashMap;
-use specs::{Entities, Join, ReadExpect, ReadStorage, System, Write, WriteExpect, WriteStorage};
+use specs::{
+    shred::{Fetch, FetchMut},
+    Join, World, WorldExt, 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))?;
+#[allow(clippy::too_many_arguments)]
+fn handle_register_msg(
+    world: &World,
+    player_list: &HashMap<Uid, PlayerInfo>,
+    new_players: &mut Vec<specs::Entity>,
+    entity: specs::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),
+    };
 
-        if !players.contains(entity) {
-            // Add Player component to this client
-            let _ = players.insert(entity, player);
-            player_metrics.players_connected.inc();
+    let player = Player::new(username, uuid);
+    let is_admin = editable_settings.admins.contains(&uuid);
 
-            // 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(())
+    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();
+pub fn register_run(world: &mut World) {
+    let entities = world.entities();
+    let player_metrics = world.read_resource::<PlayerMetrics>();
+    //let mut timer = world.write_resource::<SysTimer<Self>>();
+    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(),
-                    }),
-                })
+    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();
+        })
+        .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,
-                )
-            });
-        }
+    for (entity, client) in (&entities, &clients).join() {
+        let _ = super::try_recv_all(client, 0, |client, msg| {
+            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));
+    // 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()
     }
+
+    //timer.end()
 }
diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs
index 6cc4df7ff0..b5ee1da042 100644
--- a/voxygen/src/menu/main/mod.rs
+++ b/voxygen/src/menu/main/mod.rs
@@ -134,6 +134,7 @@ impl PlayState for MainMenuState {
                                 localized_strings.get("main.login.authentication_error"),
                                 e
                             ),
+                            client::Error::KickedByPlugin(e) => e,
                             client::Error::TooManyPlayers => {
                                 localized_strings.get("main.login.server_full").into()
                             },

From 4cffd2cadd632667bf98d4efe1972f0a3a91663f Mon Sep 17 00:00:00 2001
From: ccgauche <gaucheron.laurent@gmail.com>
Date: Mon, 1 Mar 2021 19:28:27 +0100
Subject: [PATCH 2/7] Updated CI + Changelog

---
 CHANGELOG.md                 | 3 ++-
 server/src/login_provider.rs | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae521df40a..7c1aa87bee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
 
 ### Changed
 
diff --git a/server/src/login_provider.rs b/server/src/login_provider.rs
index a4205ff28f..6d736b7061 100644
--- a/server/src/login_provider.rs
+++ b/server/src/login_provider.rs
@@ -81,7 +81,7 @@ impl LoginProvider {
 
                 match plugin_manager.execute_event(&world, "on_join", &PlayerJoinEvent {
                     player_name: username.clone(),
-                    player_id: uuid.as_bytes().clone(),
+                    player_id: *uuid.as_bytes(),
                 }) {
                     Ok(e) => {
                         for i in e.into_iter() {

From 9628dfaf99ebf81f65a1f56857976ff8e022eafd Mon Sep 17 00:00:00 2001
From: ccgauche <gaucheron.laurent@gmail.com>
Date: Mon, 1 Mar 2021 21:29:18 +0100
Subject: [PATCH 3/7] Fixed comments

---
 client/src/error.rs                     |  2 +-
 client/src/lib.rs                       |  2 +-
 common/net/src/msg/server.rs            |  2 +-
 common/sys/src/plugin/memory_manager.rs | 24 ++++++++++++++++++------
 common/sys/src/plugin/mod.rs            | 17 +++++------------
 common/sys/src/plugin/module.rs         | 15 ++++++++++-----
 common/sys/src/state.rs                 |  6 ++----
 plugin/api/src/lib.rs                   | 10 +++++++++-
 plugin/rt/examples/hello.rs             |  2 +-
 server/src/lib.rs                       |  1 -
 server/src/login_provider.rs            |  6 +++---
 voxygen/src/menu/main/mod.rs            |  2 +-
 12 files changed, 52 insertions(+), 37 deletions(-)

diff --git a/client/src/error.rs b/client/src/error.rs
index ef2a94f34c..bf7770998a 100644
--- a/client/src/error.rs
+++ b/client/src/error.rs
@@ -4,7 +4,7 @@ use network::{ParticipantError, StreamError};
 
 #[derive(Debug)]
 pub enum Error {
-    KickedByPlugin(String),
+    Kicked(String),
     NetworkErr(NetworkError),
     ParticipantErr(ParticipantError),
     StreamErr(StreamError),
diff --git a/client/src/lib.rs b/client/src/lib.rs
index 866904ec78..f38f2113ba 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -500,7 +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::KickedByPlugin(err)) => Err(Error::KickedByPlugin(err)),
+            Err(RegisterError::Kicked(err)) => Err(Error::Kicked(err)),
             Err(RegisterError::Banned(reason)) => Err(Error::Banned(reason)),
             Ok(()) => {
                 self.registered = true;
diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs
index 146bde5773..9ea2297a97 100644
--- a/common/net/src/msg/server.rs
+++ b/common/net/src/msg/server.rs
@@ -191,7 +191,7 @@ pub enum RegisterError {
     AlreadyLoggedIn,
     AuthError(String),
     Banned(String),
-    KickedByPlugin(String),
+    Kicked(String),
     InvalidCharacter,
     NotOnWhitelist,
     //TODO: InvalidAlias,
diff --git a/common/sys/src/plugin/memory_manager.rs b/common/sys/src/plugin/memory_manager.rs
index 5d317fe0fa..b69834c74e 100644
--- a/common/sys/src/plugin/memory_manager.rs
+++ b/common/sys/src/plugin/memory_manager.rs
@@ -106,6 +106,11 @@ 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,
@@ -125,32 +130,39 @@ impl MemoryManager {
         &self,
         memory: &Memory,
         allocator: &Function,
-        array: &[u8],
+        bytes: &[u8],
     ) -> Result<(u64, u64), PluginModuleError> {
-        let len = array.len();
+        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 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,
-        array: &[u8],
+        bytes: &[u8],
     ) -> Result<u64, PluginModuleError> {
-        let len = array.len();
+        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(array.iter()))
+            .zip((len as u64).to_le_bytes().iter().chain(bytes.iter()))
             .for_each(|(cell, byte)| cell.set(*byte));
         Ok(mem_position as u64)
     }
diff --git a/common/sys/src/plugin/mod.rs b/common/sys/src/plugin/mod.rs
index 2d2c0a8342..df1b733457 100644
--- a/common/sys/src/plugin/mod.rs
+++ b/common/sys/src/plugin/mod.rs
@@ -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> {
diff --git a/common/sys/src/plugin/module.rs b/common/sys/src/plugin/module.rs
index 248e068237..ecc27b07a1 100644
--- a/common/sys/src/plugin/module.rs
+++ b/common/sys/src/plugin/module.rs
@@ -111,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)),
@@ -136,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>,
 }
 
@@ -149,9 +149,12 @@ 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 }
 }
 
 pub fn from_u128(i: u128) -> (u64, u64) {
@@ -202,8 +205,8 @@ fn execute_raw(
         .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 u128_pointer = from_i64(
         function_result[0]
@@ -215,6 +218,8 @@ fn execute_raw(
 
     // 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,
         u64::from_le_bytes(bytes[0..8].try_into().unwrap()),
diff --git a/common/sys/src/state.rs b/common/sys/src/state.rs
index 32c6c20327..d53ad1e1e0 100644
--- a/common/sys/src/state.rs
+++ b/common/sys/src/state.rs
@@ -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!(
diff --git a/plugin/api/src/lib.rs b/plugin/api/src/lib.rs
index 2d159b3c25..c506fcf699 100644
--- a/plugin/api/src/lib.rs
+++ b/plugin/api/src/lib.rs
@@ -31,6 +31,8 @@ pub enum RetrieveResult {
 
 pub trait Event: Serialize + DeserializeOwned + Send + Sync {
     type Response: Serialize + DeserializeOwned + Send + Sync;
+
+    fn get_event_name(&self) -> String;
 }
 
 pub mod event {
@@ -46,6 +48,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) }
     }
 
     #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
@@ -61,12 +65,14 @@ pub mod event {
 
     impl Event for PlayerJoinEvent {
         type Response = PlayerJoinResult;
+
+        fn get_event_name(&self) -> String { "on_join".to_owned() }
     }
 
     #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
     #[repr(u8)]
     pub enum PlayerJoinResult {
-        CloseConnection(String),
+        Kick(String),
         None,
     }
 
@@ -81,6 +87,8 @@ pub mod event {
 
     impl Event for PluginLoadEvent {
         type Response = ();
+
+        fn get_event_name(&self) -> String { "on_load".to_owned() }
     }
 
     // #[derive(Serialize, Deserialize, Debug)]
diff --git a/plugin/rt/examples/hello.rs b/plugin/rt/examples/hello.rs
index 752a6f32b8..f9009b6525 100644
--- a/plugin/rt/examples/hello.rs
+++ b/plugin/rt/examples/hello.rs
@@ -36,7 +36,7 @@ static COUNTER: AtomicBool = AtomicBool::new(false);
 #[event_handler]
 pub fn on_join(input: PlayerJoinEvent) -> PlayerJoinResult {
     if COUNTER.swap(!COUNTER.load(Ordering::SeqCst), Ordering::SeqCst) {
-        PlayerJoinResult::CloseConnection(format!("You are a cheater {:?}", input))
+        PlayerJoinResult::Kick(format!("You are a cheater {:?}", input))
     } else {
         PlayerJoinResult::None
     }
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 2f049e44c8..70e0d1995c 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -1037,7 +1037,6 @@ impl Server {
                 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(),
diff --git a/server/src/login_provider.rs b/server/src/login_provider.rs
index 6d736b7061..64007e4684 100644
--- a/server/src/login_provider.rs
+++ b/server/src/login_provider.rs
@@ -79,14 +79,14 @@ impl LoginProvider {
                     return Err(RegisterError::NotOnWhitelist);
                 }
 
-                match plugin_manager.execute_event(&world, "on_join", &PlayerJoinEvent {
+                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::CloseConnection(a) = i {
-                                return Err(RegisterError::KickedByPlugin(a));
+                            if let PlayerJoinResult::Kick(a) = i {
+                                return Err(RegisterError::Kicked(a));
                             }
                         }
                     },
diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs
index b5ee1da042..80de3e768c 100644
--- a/voxygen/src/menu/main/mod.rs
+++ b/voxygen/src/menu/main/mod.rs
@@ -134,7 +134,7 @@ impl PlayState for MainMenuState {
                                 localized_strings.get("main.login.authentication_error"),
                                 e
                             ),
-                            client::Error::KickedByPlugin(e) => e,
+                            client::Error::Kicked(e) => e,
                             client::Error::TooManyPlayers => {
                                 localized_strings.get("main.login.server_full").into()
                             },

From db9991ce6df81c8b34f7c737611d890b884aa5a1 Mon Sep 17 00:00:00 2001
From: ccgauche <gaucheron.laurent@gmail.com>
Date: Mon, 1 Mar 2021 22:24:02 +0100
Subject: [PATCH 4/7] Adressed all comments

---
 common/sys/src/plugin/module.rs |   4 +
 plugin/rt/src/lib.rs            |   4 +
 server/src/lib.rs               | 154 ++++++++++++++++++++++++++++--
 server/src/sys/mod.rs           |   1 -
 server/src/sys/msg/mod.rs       |   3 +-
 server/src/sys/msg/register.rs  | 160 --------------------------------
 6 files changed, 155 insertions(+), 171 deletions(-)
 delete mode 100644 server/src/sys/msg/register.rs

diff --git a/common/sys/src/plugin/module.rs b/common/sys/src/plugin/module.rs
index ecc27b07a1..2bf7ca3dec 100644
--- a/common/sys/src/plugin/module.rs
+++ b/common/sys/src/plugin/module.rs
@@ -157,6 +157,7 @@ impl<T: Event> PreparedEventQuery<T> {
     pub fn get_function_name(&self) -> &str { &self.function_name }
 }
 
+/// 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();
     (
@@ -165,14 +166,17 @@ pub fn from_u128(i: u128) -> (u64, u64) {
     )
 }
 
+/// 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();
     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
diff --git a/plugin/rt/src/lib.rs b/plugin/rt/src/lib.rs
index 78e1990395..82ef453eb4 100644
--- a/plugin/rt/src/lib.rs
+++ b/plugin/rt/src/lib.rs
@@ -61,6 +61,7 @@ where
     bincode::deserialize(slice).map_err(|_| "Failed to deserialize function input")
 }
 
+/// 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();
     (
@@ -69,14 +70,17 @@ pub fn from_u128(i: u128) -> (u64, u64) {
     )
 }
 
+/// 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();
     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![];
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 70e0d1995c..3a41445bca 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -58,7 +58,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,22 +68,30 @@ use common::{
 };
 use common_net::{
     msg::{
-        ClientType, DisconnectReason, ServerGeneral, ServerInfo, ServerInit, ServerMsg, WorldMapMsg,
+        CharacterInfo, ClientRegister, ClientType, DisconnectReason, PlayerInfo, PlayerListUpdate,
+        RegisterError, ServerGeneral, ServerInfo, ServerInit, ServerMsg, ServerRegisterAnswer,
+        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};
+use specs::{
+    join::Join,
+    shred::{Fetch, FetchMut},
+    Builder, Entity as EcsEntity, RunNow, SystemData, WorldExt, WriteStorage,
+};
 use std::{
     i32,
     ops::{Deref, DerefMut},
@@ -195,7 +203,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 +514,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::register_run(self.state_mut().ecs_mut());
+        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 +718,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 +923,139 @@ impl Server {
         self.state.cleanup();
     }
 
+    fn handle_register_msg(
+        world: &specs::World,
+        player_list: &HashMap<Uid, PlayerInfo>,
+        new_players: &mut Vec<specs::Entity>,
+        entity: specs::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(())
+    }
+
+    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| {
+                Server::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,7 +1172,6 @@ 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(),
diff --git a/server/src/sys/mod.rs b/server/src/sys/mod.rs
index d784ec5c18..649c52114b 100644
--- a/server/src/sys/mod.rs
+++ b/server/src/sys/mod.rs
@@ -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>;
diff --git a/server/src/sys/msg/mod.rs b/server/src/sys/msg/mod.rs
index 0242d83794..e50d09ca90 100644
--- a/server/src/sys/msg/mod.rs
+++ b/server/src/sys/msg/mod.rs
@@ -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,
diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs
deleted file mode 100644
index fc807662d6..0000000000
--- a/server/src/sys/msg/register.rs
+++ /dev/null
@@ -1,160 +0,0 @@
-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 common_sys::plugin::PluginMgr;
-use hashbrown::HashMap;
-use specs::{
-    shred::{Fetch, FetchMut},
-    Join, World, WorldExt, WriteStorage,
-};
-
-#[allow(clippy::too_many_arguments)]
-fn handle_register_msg(
-    world: &World,
-    player_list: &HashMap<Uid, PlayerInfo>,
-    new_players: &mut Vec<specs::Entity>,
-    entity: specs::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(())
-}
-
-/// This system will handle new messages from clients
-pub struct Sys;
-
-pub fn register_run(world: &mut World) {
-    let entities = world.entities();
-    let player_metrics = world.read_resource::<PlayerMetrics>();
-    //let mut timer = world.write_resource::<SysTimer<Self>>();
-    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>();
-
-    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| {
-            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));
-            }
-        }
-    }
-
-    //timer.end()
-}

From c28ac78e0362d6b3575db6c1f871c10b746a8ab3 Mon Sep 17 00:00:00 2001
From: ccgauche <gaucheron.laurent@gmail.com>
Date: Mon, 1 Mar 2021 22:35:22 +0100
Subject: [PATCH 5/7] Separated the registration system into two files

---
 server/src/lib.rs      | 78 +++-------------------------------------
 server/src/register.rs | 80 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 85 insertions(+), 73 deletions(-)
 create mode 100644 server/src/register.rs

diff --git a/server/src/lib.rs b/server/src/lib.rs
index 3a41445bca..82c69f5f7d 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -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;
@@ -68,9 +69,8 @@ use common::{
 };
 use common_net::{
     msg::{
-        CharacterInfo, ClientRegister, ClientType, DisconnectReason, PlayerInfo, PlayerListUpdate,
-        RegisterError, ServerGeneral, ServerInfo, ServerInit, ServerMsg, ServerRegisterAnswer,
-        WorldMapMsg,
+        CharacterInfo, ClientType, DisconnectReason, PlayerInfo, PlayerListUpdate, ServerGeneral,
+        ServerInfo, ServerInit, ServerMsg, WorldMapMsg,
     },
     sync::WorldSyncExt,
 };
@@ -87,11 +87,7 @@ use persistence::{
 use plugin_api::Uid;
 use prometheus::Registry;
 use prometheus_hyper::Server as PrometheusServer;
-use specs::{
-    join::Join,
-    shred::{Fetch, FetchMut},
-    Builder, Entity as EcsEntity, RunNow, SystemData, WorldExt, WriteStorage,
-};
+use specs::{join::Join, Builder, Entity as EcsEntity, RunNow, SystemData, WorldExt};
 use std::{
     i32,
     ops::{Deref, DerefMut},
@@ -923,70 +919,6 @@ impl Server {
         self.state.cleanup();
     }
 
-    fn handle_register_msg(
-        world: &specs::World,
-        player_list: &HashMap<Uid, PlayerInfo>,
-        new_players: &mut Vec<specs::Entity>,
-        entity: specs::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(())
-    }
-
     pub fn register_run(&mut self) {
         let world = self.state_mut().ecs_mut();
         let entities = world.entities();
@@ -1018,7 +950,7 @@ impl Server {
 
         for (entity, client) in (&entities, &clients).join() {
             let _ = sys::msg::try_recv_all(client, 0, |client, msg| {
-                Server::handle_register_msg(
+                register::handle_register_msg(
                     &world,
                     &player_list,
                     &mut new_players,
diff --git a/server/src/register.rs b/server/src/register.rs
new file mode 100644
index 0000000000..1f094a3e12
--- /dev/null
+++ b/server/src/register.rs
@@ -0,0 +1,80 @@
+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,
+};
+
+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(())
+}

From e327b18c4a9f4ab1237dcf3c0fb5f312c6125290 Mon Sep 17 00:00:00 2001
From: ccgauche <gaucheron.laurent@gmail.com>
Date: Mon, 1 Mar 2021 22:44:18 +0100
Subject: [PATCH 6/7] Fixed clippy warning

---
 server/src/register.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/server/src/register.rs b/server/src/register.rs
index 1f094a3e12..3f480bd5f7 100644
--- a/server/src/register.rs
+++ b/server/src/register.rs
@@ -15,6 +15,7 @@ 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>,

From 356f09d7807479d1b2a923ac95806db43a93a628 Mon Sep 17 00:00:00 2001
From: ccgauche <gaucheron.laurent@gmail.com>
Date: Tue, 2 Mar 2021 09:46:08 +0000
Subject: [PATCH 7/7] Update CHANGELOG.md

---
 CHANGELOG.md | 2 --
 1 file changed, 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98071688fe..a031024523 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,11 +38,9 @@ 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
-<<<<<<< CHANGELOG.md
 - New enemies in 5 lower dungeons
 - Added on join event in plugins
 - Item stacking and splitting
->>>>>>> CHANGELOG.md
 
 ### Changed