diff --git a/Cargo.lock b/Cargo.lock
index 2e2c673031..d624eeb828 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1552,6 +1552,14 @@ dependencies = [
  "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "scan_fmt"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "scoped_threadpool"
 version = "0.1.9"
@@ -1907,6 +1915,8 @@ dependencies = [
 name = "veloren-server"
 version = "0.2.0"
 dependencies = [
+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "scan_fmt 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "specs 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "vek 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2329,6 +2339,7 @@ dependencies = [
 "checksum rusttype 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "25951e85bb2647960969f72c559392245a5bd07446a589390bf427dda31cdc4a"
 "checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
 "checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267"
+"checksum scan_fmt 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8b87497427f9fbe539ee6b9626f5a5e899331fdf1c1d62f14c637a462969db30"
 "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
 "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
 "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 4189be7693..a3eb8aa8f1 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -11,3 +11,5 @@ world = { package = "veloren-world", path = "../world" }
 specs = "0.14"
 vek = "0.9"
 threadpool = "1.7"
+lazy_static = "1.3.0"
+scan_fmt = "0.1.3"
\ No newline at end of file
diff --git a/server/src/cmd.rs b/server/src/cmd.rs
new file mode 100644
index 0000000000..529b09f0a8
--- /dev/null
+++ b/server/src/cmd.rs
@@ -0,0 +1,178 @@
+//! # Implementing new commands
+//! To implement a new command, add an instance of `ChatCommand` to `CHAT_COMMANDS`
+//! and provide a handler function.
+
+use crate::Server;
+use common::{comp, msg::ServerMsg};
+use specs::{join::Join, Entity as EcsEntity};
+use vek::*;
+
+
+use lazy_static::lazy_static;
+use scan_fmt::scan_fmt;
+/// Struct representing a command that a user can run from server chat
+pub struct ChatCommand {
+    /// The keyword used to invoke the command, omitting the leading '/'
+    pub keyword: &'static str,
+    /// A format string for parsing arguments
+    arg_fmt: &'static str,
+    /// message to explain how the command is used
+    help_string: &'static str,
+    /// Handler function called when the command is executed
+    /// # Arguments
+    /// * `&mut Server` - the `Server` instance executing the command
+    /// * `EcsEntity` - an `Entity` corresponding to the player that invoked the command
+    /// * `String` - a `String` containing the part of the command after the keyword
+    /// * `&ChatCommand` - the command to execute with the above arguments
+    /// Handler functions must parse arguments from the the given `String` (`scan_fmt!` is included for this purpose)
+    handler: fn(&mut Server, EcsEntity, String, &ChatCommand),
+}
+
+impl ChatCommand {
+    /// Creates a new chat command
+    pub fn new(
+        keyword: &'static str,
+        arg_fmt: &'static str,
+        help_string: &'static str,
+        handler: fn(&mut Server, EcsEntity, String, &ChatCommand),
+    ) -> Self {
+        Self {
+            keyword,
+            arg_fmt,
+            help_string,
+            handler,
+        }
+    }
+    /// Calls the contained handler function, passing `&self` as the last argument
+    pub fn execute(&self, server: &mut Server, entity: EcsEntity, args: String) {
+        (self.handler)(server, entity, args, self);
+    }
+}
+
+lazy_static! {
+    /// Static list of chat commands available to the server
+    pub static ref CHAT_COMMANDS: Vec<ChatCommand> = vec![
+        ChatCommand::new(
+            "jump",
+            "{d} {d} {d}",
+            "jump: offset your current position by a vector\n
+                Usage: /jump [x] [y] [z]",
+            handle_jump
+        ),
+        ChatCommand::new(
+            "goto",
+            "{d} {d} {d}",
+            "goto: teleport to a given position\n
+                Usage: /goto [x] [y] [z]",
+            handle_goto
+        ),
+        ChatCommand::new(
+            "alias",
+            "{}",
+            "alias: change your player name (cannot contain spaces)\n
+                Usage: /alias [name]",
+            handle_alias
+        ),
+        ChatCommand::new(
+            "tp",
+            "{}",
+            "tp: teleport to a named player\n
+                Usage: /tp [name]",
+            handle_tp
+        ),
+        ChatCommand::new("help", "", "help: display this message", handle_help)
+    ];
+}
+
+fn handle_jump(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
+    let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32);
+    match (opt_x, opt_y, opt_z) {
+        (Some(x), Some(y), Some(z)) => {
+            match server
+                .state
+                .read_component_cloned::<comp::phys::Pos>(entity)
+            {
+                Some(current_pos) => server
+                    .state
+                    .write_component(entity, comp::phys::Pos(current_pos.0 + Vec3::new(x, y, z))),
+                None => server.clients.notify(
+                    entity,
+                    ServerMsg::Chat(String::from("Command 'jump' invalid in current state")),
+                ),
+            }
+        }
+        _ => server
+            .clients
+            .notify(entity, ServerMsg::Chat(String::from(action.help_string))),
+    }
+}
+
+fn handle_goto(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
+    let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32);
+    match (opt_x, opt_y, opt_z) {
+        (Some(x), Some(y), Some(z)) => server
+            .state
+            .write_component(entity, comp::phys::Pos(Vec3::new(x, y, z))),
+        _ => server
+            .clients
+            .notify(entity, ServerMsg::Chat(String::from(action.help_string))),
+    }
+}
+
+fn handle_alias(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
+    let opt_alias = scan_fmt!(&args, action.arg_fmt, String);
+    match opt_alias {
+        Some(alias) => server
+            .state
+            .write_component(entity, comp::player::Player { alias }),
+        None => server
+            .clients
+            .notify(entity, ServerMsg::Chat(String::from(action.help_string))),
+    }
+}
+
+fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
+    let opt_alias = scan_fmt!(&args, action.arg_fmt, String);
+    match opt_alias {
+        Some(alias) => {
+            let ecs = server.state.ecs().internal();
+            let opt_player = (&ecs.entities(), &ecs.read_storage::<comp::player::Player>())
+                .join()
+                .find(|(_, player)| player.alias == alias)
+                .map(|(entity, _)| entity);
+            match opt_player {
+                Some(player) => match server
+                    .state
+                    .read_component_cloned::<comp::phys::Pos>(player)
+                {
+                    Some(pos) => server.state.write_component(entity, pos),
+                    None => server.clients.notify(
+                        entity,
+                        ServerMsg::Chat(format!("Unable to teleport to player '{}'", alias)),
+                    ),
+                },
+
+                None => {
+                    server.clients.notify(
+                        entity,
+                        ServerMsg::Chat(format!("Player '{}' not found!", alias)),
+                    );
+                    server
+                        .clients
+                        .notify(entity, ServerMsg::Chat(String::from(action.help_string)));
+                }
+            }
+        }
+        None => server
+            .clients
+            .notify(entity, ServerMsg::Chat(String::from(action.help_string))),
+    }
+}
+
+fn handle_help(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) {
+    for cmd in CHAT_COMMANDS.iter() {
+        server
+            .clients
+            .notify(entity, ServerMsg::Chat(String::from(cmd.help_string)));
+    }
+}
\ No newline at end of file
diff --git a/server/src/lib.rs b/server/src/lib.rs
index dd71df87cc..a1eb2fa86c 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -3,55 +3,34 @@
 pub mod client;
 pub mod error;
 pub mod input;
+pub mod cmd;
 
 // Reexports
-pub use crate::{
-    error::Error,
-    input::Input,
-};
+pub use crate::{error::Error, input::Input};
 
-use std::{
-    time::Duration,
-    net::SocketAddr,
-    sync::mpsc,
-    collections::HashSet,
-};
-use specs::{
-    Entity as EcsEntity,
-    world::EntityBuilder as EcsEntityBuilder,
-    Builder,
-    join::Join,
-    saveload::MarkedBuilder,
-};
-use vek::*;
-use threadpool::ThreadPool;
+use crate::{client::{Client, ClientState, Clients}, cmd::CHAT_COMMANDS};
 use common::{
     comp,
-    state::{State, Uid},
+    msg::{ClientMsg, ServerMsg},
     net::PostOffice,
-    msg::{ServerMsg, ClientMsg},
+    state::State,
     terrain::TerrainChunk,
 };
-use world::World;
-use crate::client::{
-    ClientState,
-    Client,
-    Clients,
+use specs::{
+    join::Join, saveload::MarkedBuilder, world::EntityBuilder as EcsEntityBuilder, Builder,
+    Entity as EcsEntity,
 };
+use std::{collections::HashSet, net::SocketAddr, sync::mpsc, time::Duration};
+use threadpool::ThreadPool;
+use vek::*;
+use world::World;
 
 const CLIENT_TIMEOUT: f64 = 5.0; // Seconds
 
 pub enum Event {
-    ClientConnected {
-        entity: EcsEntity,
-    },
-    ClientDisconnected {
-        entity: EcsEntity,
-    },
-    Chat {
-        entity: EcsEntity,
-        msg: String,
-    },
+    ClientConnected { entity: EcsEntity },
+    ClientDisconnected { entity: EcsEntity },
+    Chat { entity: EcsEntity, msg: String },
 }
 
 pub struct Server {
@@ -73,11 +52,8 @@ impl Server {
     pub fn new() -> Result<Self, Error> {
         let (chunk_tx, chunk_rx) = mpsc::channel();
 
-        let mut state = State::new();
-        state.ecs_mut().internal_mut().register::<comp::phys::ForceUpdate>();
-
         Ok(Self {
-            state,
+            state: State::new(),
             world: World::new(),
 
             postoffice: PostOffice::bind(SocketAddr::from(([0; 4], 59003)))?,
@@ -94,17 +70,25 @@ impl Server {
 
     /// Get a reference to the server's game state.
     #[allow(dead_code)]
-    pub fn state(&self) -> &State { &self.state }
+    pub fn state(&self) -> &State {
+        &self.state
+    }
     /// Get a mutable reference to the server's game state.
     #[allow(dead_code)]
-    pub fn state_mut(&mut self) -> &mut State { &mut self.state }
+    pub fn state_mut(&mut self) -> &mut State {
+        &mut self.state
+    }
 
     /// Get a reference to the server's world.
     #[allow(dead_code)]
-    pub fn world(&self) -> &World { &self.world }
+    pub fn world(&self) -> &World {
+        &self.world
+    }
     /// Get a mutable reference to the server's world.
     #[allow(dead_code)]
-    pub fn world_mut(&mut self) -> &mut World { &mut self.world }
+    pub fn world_mut(&mut self) -> &mut World {
+        &mut self.world
+    }
 
     /// Execute a single server tick, handle input and update the game state by the given duration
     #[allow(dead_code)]
@@ -147,8 +131,14 @@ impl Server {
             for (entity, player, pos) in (
                 &self.state.ecs().internal().entities(),
                 &self.state.ecs().internal().read_storage::<comp::Player>(),
-                &self.state.ecs().internal().read_storage::<comp::phys::Pos>(),
-            ).join() {
+                &self
+                    .state
+                    .ecs()
+                    .internal()
+                    .read_storage::<comp::phys::Pos>(),
+            )
+                .join()
+            {
                 // TODO: Distance check
                 // if self.state.terrain().key_pos(key)
 
@@ -182,23 +172,18 @@ impl Server {
         let mut frontend_events = Vec::new();
 
         for mut postbox in self.postoffice.new_postboxes() {
-            let entity = self.state
-                .ecs_mut()
-                .create_entity_synced()
-                .build();
+            let entity = self.state.ecs_mut().create_entity_synced().build();
 
-            // Make sure the entity gets properly created
-            self.state.ecs_mut().internal_mut().maintain();
-
-            self.clients.add(entity, Client {
-                state: ClientState::Connecting,
-                postbox,
-                last_ping: self.state.get_time(),
-            });
-
-            frontend_events.push(Event::ClientConnected {
+            self.clients.add(
                 entity,
-            });
+                Client {
+                    state: ClientState::Connecting,
+                    postbox,
+                    last_ping: self.state.get_time(),
+                },
+            );
+
+            frontend_events.push(Event::ClientConnected { entity });
         }
 
         Ok(frontend_events)
@@ -236,7 +221,6 @@ impl Server {
                                     state.write_component(entity, character);
                                     
                                 }
-                                state.write_component(entity, comp::phys::ForceUpdate);
 
                                 client.state = ClientState::Connected;
 
@@ -249,34 +233,36 @@ impl Server {
                                         .unwrap()
                                         .into(),
                                 });
-                            },
+                            }
                             _ => disconnect = true,
                         },
                         ClientState::Connected => match msg {
                             ClientMsg::Connect { .. } => disconnect = true, // Not allowed when already connected
                             ClientMsg::Disconnect => disconnect = true,
                             ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong),
-                            ClientMsg::Pong => {},
+                            ClientMsg::Pong => {}
                             ClientMsg::Chat(msg) => new_chat_msgs.push((entity, msg)),
                             ClientMsg::PlayerAnimation(animation) => state.write_component(entity, animation),
                             ClientMsg::PlayerPhysics { pos, vel, dir } => {
                                 state.write_component(entity, pos);
                                 state.write_component(entity, vel);
                                 state.write_component(entity, dir);
-                            },
-                            ClientMsg::TerrainChunkRequest { key } => match state.terrain().get_key(key) {
-                                Some(chunk) => {}, /*client.postbox.send_message(ServerMsg::TerrainChunkUpdate {
+                            }
+                            ClientMsg::TerrainChunkRequest { key } => {
+                                match state.terrain().get_key(key) {
+                                    Some(chunk) => {} /*client.postbox.send_message(ServerMsg::TerrainChunkUpdate {
                                     key,
                                     chunk: Box::new(chunk.clone()),
-                                }),*/
-                                None => requested_chunks.push(key),
-                            },
+                                    }),*/
+                                    None => requested_chunks.push(key),
+                                }
+                            }
                         },
                     }
                 }
-            } else if
-                state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout
-                client.postbox.error().is_some() // Postbox error
+            } else if state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout
+                client.postbox.error().is_some()
+            // Postbox error
             {
                 disconnect = true;
             } else if state.get_time() - client.last_ping > CLIENT_TIMEOUT * 0.5 {
@@ -294,29 +280,33 @@ impl Server {
 
         // Handle new chat messages
         for (entity, msg) in new_chat_msgs {
-            self.clients.notify_connected(ServerMsg::Chat(match state
-                .ecs()
-                .internal()
-                .read_storage::<comp::Player>()
-                .get(entity)
-            {
-                Some(player) => format!("[{}] {}", &player.alias, msg),
-                None => format!("[<anon>] {}", msg),
-            }));
+            // Handle chat commands
+            if msg.starts_with("/") && msg.len() > 1 {
+                let argv = String::from(&msg[1..]);
+                self.process_chat_cmd(entity, argv);
+            } else {
+                self.clients.notify_connected(ServerMsg::Chat(
+                    match self
+                        .state
+                        .ecs()
+                        .internal()
+                        .read_storage::<comp::Player>()
+                        .get(entity)
+                    {
+                        Some(player) => format!("[{}] {}", &player.alias, msg),
+                        None => format!("[<anon>] {}", msg),
+                    },
+                ));
 
-            frontend_events.push(Event::Chat {
-                entity,
-                msg,
-            });
+                frontend_events.push(Event::Chat { entity, msg });
+            }
         }
 
         // Handle client disconnects
         for entity in disconnected_clients {
-            state.ecs_mut().delete_entity_synced(entity).unwrap();
+            self.state.ecs_mut().delete_entity_synced(entity);
 
-            frontend_events.push(Event::ClientDisconnected {
-                entity,
-            });
+            frontend_events.push(Event::ClientDisconnected { entity });
         }
 
         // Generate requested chunks
@@ -375,7 +365,33 @@ impl Server {
     pub fn generate_chunk(&mut self, key: Vec3<i32>) {
         if self.pending_chunks.insert(key) {
             let chunk_tx = self.chunk_tx.clone();
-            self.thread_pool.execute(move || chunk_tx.send((key, World::generate_chunk(key))).unwrap());
+            self.thread_pool
+                .execute(move || chunk_tx.send((key, World::generate_chunk(key))).unwrap());
+        }
+    }
+
+    fn process_chat_cmd(&mut self, entity: EcsEntity, cmd: String) {
+        // separate string into keyword and arguments
+        let sep = cmd.find(' ');
+        let (kwd, args) = match sep {
+            Some(i) => (cmd[..i].to_string(), cmd[(i + 1)..].to_string()),
+            None => (cmd, "".to_string()),
+        };
+
+        // find command object and run its handler
+        let action_opt = CHAT_COMMANDS.iter().find(|x| x.keyword == kwd);
+        match action_opt {
+            Some(action) => action.execute(self, entity, args),
+            // unknown command
+            None => {
+                self.clients.notify(
+                    entity,
+                    ServerMsg::Chat(format!(
+                        "Unrecognised command: '/{}'\ntype '/help' for a list of available commands",
+                        kwd
+                    )),
+                );
+            }
         }
     }
 }