Index: /binary-improvements/7dtd-server-fixes/src/PersistentData/Inventory.cs
===================================================================
--- /binary-improvements/7dtd-server-fixes/src/PersistentData/Inventory.cs	(revision 244)
+++ /binary-improvements/7dtd-server-fixes/src/PersistentData/Inventory.cs	(revision 245)
@@ -7,38 +7,52 @@
 {
 	[Serializable]
-	public class Inventory
-	{
+	public class Inventory {
 		public List<InvItem> bag;
 		public List<InvItem> belt;
+		public InvItem[] equipment;
 
-		public Inventory ()
-		{
+		public Inventory () {
 			bag = new List<InvItem> ();
 			belt = new List<InvItem> ();
+			equipment = null;
 		}
 
-		public void Update (PlayerDataFile pdf)
-		{
-			//Log.Out ("Updating player inventory - player id: " + pdf.id);
-			ProcessInv (bag, pdf.bag, pdf.id);
-			ProcessInv (belt, pdf.inventory, pdf.id);
+		public void Update (PlayerDataFile pdf) {
+			lock (this) {
+				//Log.Out ("Updating player inventory - player id: " + pdf.id);
+				ProcessInv (bag, pdf.bag, pdf.id);
+				ProcessInv (belt, pdf.inventory, pdf.id);
+				ProcessEqu (pdf.equipment);
+			}
 		}
 
-		private void ProcessInv (List<InvItem> target, ItemStack[] sourceFields, int id)
-		{
-			lock (target) {
-				target.Clear ();
-				for (int i = 0; i < sourceFields.Length; i++) {
-					if (sourceFields [i].count > 0) {
-						int count = sourceFields [i].count;
-						int maxAllowed = ItemClass.list [sourceFields [i].itemValue.type].Stacknumber.Value;
-						string name = ItemClass.list [sourceFields [i].itemValue.type].GetItemName ();
+		private void ProcessInv (List<InvItem> target, ItemStack[] sourceFields, int id) {
+			target.Clear ();
+			for (int i = 0; i < sourceFields.Length; i++) {
+				if (sourceFields [i].count > 0) {
+					int count = sourceFields [i].count;
+					int maxAllowed = ItemClass.list [sourceFields [i].itemValue.type].Stacknumber.Value;
+					string name = ItemClass.list [sourceFields [i].itemValue.type].GetItemName ();
 
-						if (count > maxAllowed)
-							Log.Out ("Player with ID " + id + " has stack for \"" + name + "\" greater than allowed (" + count + " > " + maxAllowed + ")");
-						target.Add (new InvItem (name, count));
-					} else {
-						target.Add (null);
+					if (count > maxAllowed) {
+						Log.Out ("Player with ID " + id + " has stack for \"" + name + "\" greater than allowed (" + count + " > " + maxAllowed + ")");
 					}
+					target.Add (new InvItem (name, count));
+				} else {
+					target.Add (null);
+				}
+			}
+		}
+
+		private void ProcessEqu (Equipment sourceEquipment) {
+			equipment = new InvItem[sourceEquipment.GetSlotCount ()];
+			for (int i = 0; i < sourceEquipment.GetSlotCount (); i++) {
+				if (sourceEquipment.GetSlotItem (i) != null && !sourceEquipment.GetSlotItem (i).Equals (ItemValue.None)) {
+					int count = 1;
+					string name = ItemClass.list [sourceEquipment.GetSlotItem (i).type].GetItemName ();
+
+					equipment [i] = new InvItem (name, count);
+				} else {
+					equipment [i] = null;
 				}
 			}
Index: /binary-improvements/MapRendering/ModInfo.xml
===================================================================
--- /binary-improvements/MapRendering/ModInfo.xml	(revision 244)
+++ /binary-improvements/MapRendering/ModInfo.xml	(revision 245)
@@ -5,5 +5,5 @@
 		<Description value="Render the game map to image map tiles as it is uncovered" />
 		<Author value="Christian 'Alloc' Illy" />
-		<Version value="6" />
+		<Version value="7" />
 		<Website value="http://7dtd.illy.bz" />
 	</ModInfo>
Index: /binary-improvements/MapRendering/Web/API/ExecuteConsoleCommand.cs
===================================================================
--- /binary-improvements/MapRendering/Web/API/ExecuteConsoleCommand.cs	(revision 245)
+++ /binary-improvements/MapRendering/Web/API/ExecuteConsoleCommand.cs	(revision 245)
@@ -0,0 +1,87 @@
+using AllocsFixes.JSON;
+using AllocsFixes.PersistentData;
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+namespace AllocsFixes.NetConnections.Servers.Web.API
+{
+	public class ExecuteConsoleCommand : WebAPI
+	{
+		public override void HandleRequest (HttpListenerRequest req, HttpListenerResponse resp, WebConnection user, int permissionLevel) {
+			if (string.IsNullOrEmpty (req.QueryString ["command"])) {
+				resp.StatusCode = (int)HttpStatusCode.BadRequest;
+				Web.SetResponseTextContent (resp, "No command given");
+				return;
+			}
+
+			string commandName = req.QueryString ["command"];
+
+			IConsoleCommand command = SdtdConsole.Instance.GetCommand (commandName);
+
+			if (command == null) {
+				resp.StatusCode = (int)HttpStatusCode.NotImplemented;
+				Web.SetResponseTextContent (resp, "Unknown command");
+				return;
+			}
+
+			AdminToolsCommandPermissions atcp = GameManager.Instance.adminTools.GetAdminToolsCommandPermission (command.GetCommands ());
+
+			if (permissionLevel > atcp.PermissionLevel) {
+				resp.StatusCode = (int)HttpStatusCode.Forbidden;
+				Web.SetResponseTextContent (resp, "You are not allowed to execute this command");
+				return;
+			}
+
+			// TODO: Execute command (store resp as IConsoleConnection instance to deliver response to the single client?)
+
+//			JSONObject result = new JSONObject ();
+//			result.Add ("claimsize", new JSONNumber (GamePrefs.GetInt (EnumGamePrefs.LandClaimSize)));
+//
+//			JSONArray claimOwners = new JSONArray ();
+//			result.Add ("claimowners", claimOwners);
+//
+//			Dictionary<Vector3i, PersistentPlayerData> d = GameManager.Instance.GetPersistentPlayerList ().m_lpBlockMap;
+//			if (d != null) {
+//				World w = GameManager.Instance.World;
+//				Dictionary<PersistentPlayerData, List<Vector3i>> owners = new Dictionary<PersistentPlayerData, List<Vector3i>> ();
+//				foreach (KeyValuePair<Vector3i, PersistentPlayerData> kvp in d) {
+//					if (steamid.Length == 0 || kvp.Value.PlayerId.Equals (steamid)) {
+//						if (!owners.ContainsKey (kvp.Value)) {
+//							owners.Add (kvp.Value, new List<Vector3i> ());
+//						}
+//						owners [kvp.Value].Add (kvp.Key);
+//					}
+//				}
+//
+//				foreach (KeyValuePair<PersistentPlayerData, List<Vector3i>> kvp in owners) {
+//					if (steamid.Length == 0 || kvp.Key.PlayerId.Equals (steamid)) {
+//						string curID = kvp.Key.PlayerId;
+//						bool isActive = w.IsLandProtectionValidForPlayer (kvp.Key);
+//
+//						JSONObject owner = new JSONObject ();
+//						claimOwners.Add (owner);
+//
+//						owner.Add ("steamid", new JSONString (curID));
+//						owner.Add ("claimactive", new JSONBoolean (isActive));
+//
+//						JSONArray claims = new JSONArray ();
+//						owner.Add ("claims", claims);
+//
+//						foreach (Vector3i v in kvp.Value) {
+//							JSONObject claim = new JSONObject ();
+//							claim.Add ("x", new JSONNumber (v.x));
+//							claim.Add ("y", new JSONNumber (v.y));
+//							claim.Add ("z", new JSONNumber (v.z));
+//
+//							claims.Add (claim);
+//						}
+//					}
+//				}
+//			}
+//
+//			WriteJSON (resp, result);
+		}
+	}
+}
+
Index: /binary-improvements/MapRendering/Web/API/GetLandClaims.cs
===================================================================
--- /binary-improvements/MapRendering/Web/API/GetLandClaims.cs	(revision 244)
+++ /binary-improvements/MapRendering/Web/API/GetLandClaims.cs	(revision 245)
@@ -53,4 +53,10 @@
 						owner.Add ("claimactive", new JSONBoolean (isActive));
 
+						if (PersistentContainer.Instance.Players [curID, false] != null) {
+							owner.Add ("playername", new JSONString (PersistentContainer.Instance.Players [curID, false].Name));
+						} else {
+							owner.Add ("playername", new JSONNull ());
+						}
+
 						JSONArray claims = new JSONArray ();
 						owner.Add ("claims", claims);
Index: /binary-improvements/MapRendering/Web/API/GetPlayerInventory.cs
===================================================================
--- /binary-improvements/MapRendering/Web/API/GetPlayerInventory.cs	(revision 244)
+++ /binary-improvements/MapRendering/Web/API/GetPlayerInventory.cs	(revision 245)
@@ -7,8 +7,6 @@
 namespace AllocsFixes.NetConnections.Servers.Web.API
 {
-	public class GetPlayerInventory : WebAPI
-	{
-		public override void HandleRequest (HttpListenerRequest req, HttpListenerResponse resp, WebConnection user, int permissionLevel)
-		{
+	public class GetPlayerInventory : WebAPI {
+		public override void HandleRequest (HttpListenerRequest req, HttpListenerResponse resp, WebConnection user, int permissionLevel) {
 			if (req.QueryString ["steamid"] == null) {
 				resp.StatusCode = (int)HttpStatusCode.InternalServerError;
@@ -31,32 +29,60 @@
 			JSONArray bag = new JSONArray ();
 			JSONArray belt = new JSONArray ();
+			JSONObject equipment = new JSONObject ();
+			result.Add ("playername", new JSONString (p.Name));
 			result.Add ("bag", bag);
 			result.Add ("belt", belt);
+			result.Add ("equipment", equipment);
 
 			for (int i = 0; i < inv.belt.Count; i++) {
-				JSONObject item = new JSONObject();
+				JSONObject item = new JSONObject ();
 				if (inv.belt [i] != null) {
-					item.Add ("count", new JSONNumber(inv.belt[i].count));
-					item.Add ("name", new JSONString(inv.belt[i].itemName));
+					item.Add ("count", new JSONNumber (inv.belt [i].count));
+					item.Add ("name", new JSONString (inv.belt [i].itemName));
 				} else {
-					item.Add ("count", new JSONNumber(0));
-					item.Add ("name", new JSONString(string.Empty));
+					item.Add ("count", new JSONNumber (0));
+					item.Add ("name", new JSONString (string.Empty));
 				}
-				belt.Add(item);
+				belt.Add (item);
 			}
 			for (int i = 0; i < inv.bag.Count; i++) {
-				JSONObject item = new JSONObject();
+				JSONObject item = new JSONObject ();
 				if (inv.bag [i] != null) {
-					item.Add ("count", new JSONNumber(inv.bag[i].count));
-					item.Add ("name", new JSONString(inv.bag[i].itemName));
+					item.Add ("count", new JSONNumber (inv.bag [i].count));
+					item.Add ("name", new JSONString (inv.bag [i].itemName));
 				} else {
-					item.Add ("count", new JSONNumber(0));
-					item.Add ("name", new JSONString(string.Empty));
+					item.Add ("count", new JSONNumber (0));
+					item.Add ("name", new JSONString (string.Empty));
 				}
-				bag.Add(item);
+				bag.Add (item);
 			}
+
+			AddEquipment (equipment, "head", inv.equipment, XMLData.Item.EnumEquipmentSlot.Head, NGuiInvGridEquipment.EnumClothingLayer.Middle);
+			AddEquipment (equipment, "eyes", inv.equipment, XMLData.Item.EnumEquipmentSlot.Eyes, NGuiInvGridEquipment.EnumClothingLayer.Middle);
+			AddEquipment (equipment, "face", inv.equipment, XMLData.Item.EnumEquipmentSlot.Face, NGuiInvGridEquipment.EnumClothingLayer.Middle);
+
+			AddEquipment (equipment, "armor", inv.equipment, XMLData.Item.EnumEquipmentSlot.Chest, NGuiInvGridEquipment.EnumClothingLayer.Outer);
+			AddEquipment (equipment, "jacket", inv.equipment, XMLData.Item.EnumEquipmentSlot.Chest, NGuiInvGridEquipment.EnumClothingLayer.Middle);
+			AddEquipment (equipment, "shirt", inv.equipment, XMLData.Item.EnumEquipmentSlot.Chest, NGuiInvGridEquipment.EnumClothingLayer.Inner);
+
+			AddEquipment (equipment, "legarmor", inv.equipment, XMLData.Item.EnumEquipmentSlot.Legs, NGuiInvGridEquipment.EnumClothingLayer.Outer);
+			AddEquipment (equipment, "pants", inv.equipment, XMLData.Item.EnumEquipmentSlot.Legs, NGuiInvGridEquipment.EnumClothingLayer.Inner);
+			AddEquipment (equipment, "boots", inv.equipment, XMLData.Item.EnumEquipmentSlot.Feet, NGuiInvGridEquipment.EnumClothingLayer.Inner);
+
+			AddEquipment (equipment, "gloves", inv.equipment, XMLData.Item.EnumEquipmentSlot.Hands, NGuiInvGridEquipment.EnumClothingLayer.Inner);
+			AddEquipment (equipment, "backpack", inv.equipment, XMLData.Item.EnumEquipmentSlot.Back, NGuiInvGridEquipment.EnumClothingLayer.Outer);
 
 			WriteJSON (resp, result);
 		}
+
+		private void AddEquipment (JSONObject _eq, string _slotname, InvItem[] _items, XMLData.Item.EnumEquipmentSlot _slot, NGuiInvGridEquipment.EnumClothingLayer _layer) {
+			int index = (int)_slot + (int)_layer * (int)XMLData.Item.EnumEquipmentSlot.Count;
+			if (_items != null && _items [index] != null) {
+				_eq.Add (_slotname, new JSONString (_items [index].itemName));
+			} else {
+				_eq.Add (_slotname, new JSONBoolean (false));
+			}
+		}
+
 	}
 }
Index: /binary-improvements/MapRendering/Web/API/GetStats.cs
===================================================================
--- /binary-improvements/MapRendering/Web/API/GetStats.cs	(revision 244)
+++ /binary-improvements/MapRendering/Web/API/GetStats.cs	(revision 245)
@@ -19,4 +19,6 @@
 			result.Add ("gametime", time);
 
+			result.Add ("players", new JSONNumber (GameManager.Instance.World.Players.Count));
+
 			WriteJSON (resp, result);
 		}
Index: /binary-improvements/server-fixes.userprefs
===================================================================
--- /binary-improvements/server-fixes.userprefs	(revision 244)
+++ /binary-improvements/server-fixes.userprefs	(revision 245)
@@ -1,16 +1,14 @@
 ﻿<Properties>
   <MonoDevelop.Ide.Workspace ActiveConfiguration="Release_Version" />
-  <MonoDevelop.Ide.Workbench ActiveDocument="7dtd-server-fixes/src/AssemblyInfo.cs">
+  <MonoDevelop.Ide.Workbench ActiveDocument="MapRendering/Web/API/GetLandClaims.cs">
     <Files>
       <File FileName="MapRendering/MapRendering/Constants.cs" Line="1" Column="1" />
-      <File FileName="7dtd-server-fixes/src/AssemblyInfo.cs" Line="20" Column="35" />
       <File FileName="7dtd-server-fixes/ModInfo.xml" Line="2" Column="6" />
       <File FileName="AllocsCommands/ModInfo.xml" Line="7" Column="20" />
       <File FileName="MapRendering/ModInfo.xml" Line="7" Column="20" />
-      <File FileName="MapRendering/Web/Web.cs" Line="245" Column="3" />
-      <File FileName="MapRendering/Web/WebPermissions.cs" Line="156" Column="25" />
-      <File FileName="MapRendering/Web/Handlers/SessionHandler.cs" Line="16" Column="123" />
-      <File FileName="MapRendering/Web/Handlers/ApiHandler.cs" Line="34" Column="1" />
+      <File FileName="MapRendering/Web/Web.cs" Line="69" Column="19" />
       <File FileName="MapRendering/Web/API/ExecuteConsoleCommand.cs" Line="36" Column="114" />
+      <File FileName="MapRendering/Web/API/GetStats.cs" Line="21" Column="84" />
+      <File FileName="MapRendering/Web/API/GetLandClaims.cs" Line="59" Column="8" />
     </Files>
     <Pads>
Index: /binary-improvements/webserver/css/style.css
===================================================================
--- /binary-improvements/webserver/css/style.css	(revision 244)
+++ /binary-improvements/webserver/css/style.css	(revision 245)
@@ -1,3 +1,5 @@
-/* Generic page layout */
+/*========================================
+-   Generic page layout
+*/
 
 body {
@@ -12,5 +14,5 @@
 }
 
-.adminmenu,
+.adminnavbar,
 .admincontent {
 	position: absolute;
@@ -19,5 +21,10 @@
 }
 
-.adminmenu {
+
+/*========================================
+-   Menu bar
+*/
+
+.adminnavbar {
 	width: 200px;
 	left: 0;
@@ -25,28 +32,73 @@
 }
 
-.adminmenu .current_tab {
+.adminnavbar > div {
+	margin: 5px;
+}
+
+/*----------------------------------------
+-   Menu entries
+*/
+
+.adminnavbar ul {
+	margin-top: 5px;
+}
+
+.adminnavbar #adminmenu .menu_button {
+	display: none;
+}
+
+.adminnavbar #adminmenu .menu_button.allowed {
+	display: inline;
+}
+
+.adminnavbar #adminmenu .menu_button a {
+	color: #000;
+	text-decoration: none;
+}
+
+.adminnavbar #adminmenu .current_tab {
 	font-weight: bold;
 	text-transform: uppercase;
 }
 
-.adminmenu .menu_button a {
+/*----------------------------------------
+-   Server stats
+*/
+
+.adminnavbar #serverstats {
+	margin-bottom: 20px;
+	display: none;
 	color: #000;
-}
-
-.adminmenu #userstate {
-	position: absolute;
-	bottom: 5px;
-	left: 5px;
-	right: 5px;
+	text-decoration: none;
+}
+
+.adminnavbar #serverstats #stats_time {
+	white-space: nowrap;
+}
+
+/*----------------------------------------
+-   Session state box
+*/
+
+.adminnavbar #userstate {
+	position: absolute;
+	bottom: 0px;
+	left: 0px;
+	right: 0px;
 	background-color: green;
 }
 
-.adminmenu #userstate #username {
+.adminnavbar #userstate #username {
 	padding-left: 10px;
 }
 
-.adminmenu #userstate > div {
-	display: none;
-}
+.adminnavbar #userstate > div {
+	display: none;
+}
+
+
+/*========================================
+-   Content area
+*/
 
 .admincontent {
@@ -54,5 +106,9 @@
 	right: 0;
 	left: 200px;
-	background-color: #fff;
+	background-color: #a04;
+}
+
+.admincontent #nopermissionwarning {
+	margin: 20px 50px;
 }
 
@@ -78,5 +134,7 @@
 
 
-/* Inventory dialog overlay */
+/*========================================
+-   Inventory dialog
+*/
 
 #info {
@@ -91,5 +149,4 @@
 	margin: 0px;
 	border-collapse: collapse;
-	background-color: #303030;
 }
 .invField {
@@ -99,4 +156,5 @@
 	margin: 0px;
 	border: 1px solid gray;
+	background-color: #303030;
 	background-size: 58px;
 	background-repeat: no-repeat;
@@ -112,4 +170,9 @@
 		1px 1px 0 #000;
 }
+
+#equipmentTable .invFieldText {
+	display: none;
+}
+
 .inventoryButton {
 	color: #00ff00;
@@ -126,4 +189,5 @@
 	z-index:1010 !important;
 }
+
 
 
Index: /binary-improvements/webserver/index.html
===================================================================
--- /binary-improvements/webserver/index.html	(revision 244)
+++ /binary-improvements/webserver/index.html	(revision 245)
@@ -33,6 +33,11 @@
 	<script type="text/javascript" src="js/leaflet.control.coordinates.js"></script>
 	<script type="text/javascript" src="js/leaflet.control.reloadtiles.js"></script>
-	<script type="text/javascript" src="js/leaflet.control.gametime.js"></script>
+	<script type="text/javascript" src="js/leaflet.layer.landclaims.js"></script>
+	<script type="text/javascript" src="js/inventory_dialog.js"></script>
 	<script type="text/javascript" src="js/util.js"></script>
+	<script type="text/javascript" src="js/stats.js"></script>
+	<script type="text/javascript" src="js/tabs.js"></script>
+	<script type="text/javascript" src="js/permissions.js"></script>
+	<script type="text/javascript" src="js/map.js"></script>
 
 	<!-- Own stylesheet -->
@@ -44,9 +49,17 @@
 
 	<div class="adminwrapper">
-		<div class="adminmenu">
-			<ul>
-				<li><a href="#tab_map" data-permission="web.map">Map</a></li>
-				<li><a href="#tab_log" data-permission="web.log">Log</a></li>
-			</ul>
+		<div class="adminnavbar">
+			<div id="serverstats">
+				<span id="stats_time">-</span><br/>
+				<span id="stats_players">-</span> Players
+			</div>
+
+			<div id="adminmenu">
+				Menu
+				<ul>
+					<li><a href="#tab_map" data-permission="web.map">Map</a></li>
+					<li><a href="#tab_log" data-permission="web.log">Log</a></li>
+				</ul>
+			</div>
 			
 			<div id="userstate">
@@ -67,4 +80,5 @@
 		</div>
 		<div class="admincontent">
+			<h1 id="nopermissionwarning" style="display:none">An error occured or you do not have any permissions on this WebPanel. Log in with the link on the lower left!</h1>
 			<div id="tab_map" class="adminmap"></div>
 			<div id="tab_log" class="adminlog"></div>
@@ -74,10 +88,27 @@
 
 	<div id="playerInventoryDialog" title="Player inventory">
-		Player: <span id="invPlayerName"></span>
-		<table class="invTable" id="bagTable">
+		Player: <span id="invPlayerName"></span><br/>
+		<table>
+			<tr>
+				<td>
+					Inventory:<br/>
+					<table class="invTable" id="bagTable">
+					</table>
+				</td>
+				<td rowspan="2">
+					Equipment:<br/>
+					<table class="invTable" id="equipmentTable">
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td>
+					Belt:<br/>
+					<table class="invTable" id="beltTable">
+					</table>
+				</td>
+			</tr>
 		</table>
-		<br/>
-		<table class="invTable" id="beltTable">
-		</table>
+
 	</div>
 	
Index: /binary-improvements/webserver/js/index.js
===================================================================
--- /binary-improvements/webserver/js/index.js	(revision 244)
+++ /binary-improvements/webserver/js/index.js	(revision 245)
@@ -1,426 +1,4 @@
-// ===============================================================================================
-// Constants
+InitializeTabs ();
+SetupInventoryDialog ();
+InitPermissions ();
 
-var mapinfo = {
-	regionsize: 512,
-	chunksize: 16,
-	tilesize: 128,
-	maxzoom: 4
-}
-
-var BAG_COLS = 8;
-var BAG_ROWS = 4;
-var BELT_COLS = 8;
-var INV_ITEM_WIDTH = 58;
-var INV_ITEM_HEIGHT = 40;
-
-var userdata = false;
-
-
-function initMap() {
-	// ===============================================================================================
-	// 7dtd coordinate transformations
-
-	SDTD_Projection = {
-		project: function (latlng) {
-			return new L.Point(
-				(latlng.lat) / Math.pow(2, mapinfo.maxzoom),
-				(latlng.lng) / Math.pow(2, mapinfo.maxzoom) );
-		},
-		
-		unproject: function (point) {
-			return new L.LatLng(
-				point.x * Math.pow(2, mapinfo.maxzoom),
-				point.y * Math.pow(2, mapinfo.maxzoom) );
-		}
-	};
-
-	SDTD_CRS = L.extend({}, L.CRS.Simple, {
-		projection: SDTD_Projection,
-		transformation: new L.Transformation(1, 0, -1, 0),
-
-		scale: function (zoom) {
-			return Math.pow(2, zoom);
-		}
-	});
-
-
-
-
-	// ===============================================================================================
-	// Map and basic tile layers
-
-	var initTime = new Date().getTime();
-
-	map = L.map('tab_map', {
-		zoomControl: false, // Added by Zoomslider
-		zoomsliderControl: true,
-		attributionControl: false,
-		crs: SDTD_CRS
-	}).setView([0, 0], Math.max(0, mapinfo.maxzoom - 5));
-
-	var tileLayer = L.tileLayer('../map/{z}/{x}/{y}.png?t={time}', {
-		maxZoom: mapinfo.maxzoom + 1,
-		minZoom: Math.max(0, mapinfo.maxzoom - 5),
-		maxNativeZoom: mapinfo.maxzoom,
-		tileSize: mapinfo.tilesize,
-		continuousWorld: true,
-		tms: true,
-		unloadInvisibleTiles: false,
-		time: initTime
-	});
-	
-	// TileLayer w/ TMS=true fix for zoomlevel >= 8
-	tileLayer._getWrapTileNum = function () {
-		return L.point(0, 0);
-	};
-
-	var tileLayerMiniMap = L.tileLayer('../map/{z}/{x}/{y}.png?t={time}', {
-		maxZoom: mapinfo.maxzoom,
-		minZoom: 0,
-		maxNativeZoom: mapinfo.maxzoom,
-		tileSize: mapinfo.tilesize,
-		continuousWorld: true,
-		tms: true,
-		unloadInvisibleTiles: false,
-		time: initTime
-	});
-
-
-
-
-
-
-
-	// ===============================================================================================
-	// Overlays and controls
-
-	var playersOnlineMarkerGroup = L.layerGroup();
-	var playersOfflineMarkerGroup = L.markerClusterGroup({
-		maxClusterRadius: function(zoom) { return zoom == mapinfo.maxzoom ? 10 : 50; }
-	});
-
-
-	var landClaimsGroup = L.layerGroup();
-	var landClaimsClusterGroup = L.markerClusterGroup({
-		disableClusteringAtZoom: mapinfo.maxzoom,
-		singleMarkerMode: true,
-		maxClusterRadius: 50
-	});
-	var landClaimsRectGroup = L.layerGroup();
-	landClaimsGroup.addLayer(landClaimsClusterGroup);
-	landClaimsGroup.addLayer(landClaimsRectGroup);
-	var maxZoomForCluster = 4;
-
-	var baseLayers = {
-		//"Map": tileLayer
-	};
-
-	var layerControl = L.control.layers(baseLayers, null, {
-		collapsed: false
-	});
-	
-	var layerCount = 0;
-
-
-	if (hasPermission ("web.map")) {
-		tileLayer.addTo(map);
-		new L.Control.Coordinates({}).addTo(map);
-		new L.Control.ReloadTiles({layers: [tileLayer, tileLayerMiniMap]}).addTo(map);
-		layerControl.addOverlay (GetRegionLayer (mapinfo), "Region files");
-		var miniMap = new L.Control.MiniMap(tileLayerMiniMap, {
-			zoomLevelOffset: -6,
-			toggleDisplay: true
-		}).addTo(map);
-		
-		if (hasPermission ("webapi.getstats")) {
-			new L.Control.GameTime({}).addTo(map);
-		}
-	}
-	if (hasPermission ("webapi.getlandclaims")) {
-		layerControl.addOverlay (landClaimsGroup, "Land claims");
-		layerCount++;
-	}
-	if (hasPermission ("webapi.getplayerslocation")) {
-		layerControl.addOverlay (playersOfflineMarkerGroup, "Players (offline) (<span id='mapControlOfflineCount'>0</span>)");
-		layerControl.addOverlay (playersOnlineMarkerGroup, "Players (online) (<span id='mapControlOnlineCount'>0</span>)");
-		layerCount++;
-	}
-
-	if (layerCount > 0) {
-		layerControl.addTo(map);
-	}
-
-
-	var playersMappingList = {};
-
-
-
-	// ===============================================================================================
-	// Inventory dialog
-
-	var showInv = function(steamid) {
-		$.getJSON( "../api/getplayerinventory", { steamid: steamid  })
-		.done(function(data) {
-			$("#invPlayerName").text(playersMappingList[steamid].name);
-			for (var y = 0; y < BAG_ROWS; y++) {
-				for (var x = 0; x < BAG_COLS; x++) {
-					if (data.bag[y*BAG_COLS+x].count > 0) {
-						$("#bagField"+x+"_"+y).attr("style", "background-image: url(../itemicons/" + data.bag[y*BAG_COLS+x].name + ".png);").attr("title", data.bag[y*BAG_COLS+x].name);
-						$("#bagFieldText"+x+"_"+y).text(data.bag[y*BAG_COLS+x].count);
-					} else {
-						$("#bagField"+x+"_"+y).attr("style", "background-image: none;");
-						$("#bagFieldText"+x+"_"+y).text("");
-					}
-				}
-			}
-
-			for (var x = 0; x < BELT_COLS; x++) {
-				if (data.belt[x].count > 0) {
-					$("#beltField"+x).attr("style", "background-image: url(../itemicons/" + data.belt[x].name + ".png);").attr("title", data.belt[x].name);
-					$("#beltFieldText"+x).text(data.belt[x].count);
-				} else {
-					$("#beltField"+x).attr("style", "background-image: none;");
-					$("#beltFieldText"+x).text("");
-				}
-			}
-
-			$( "#playerInventoryDialog" ).css("z-index", "1010").dialog({
-				modal: true,
-				width: BAG_COLS*(INV_ITEM_WIDTH+14) + 20,
-				buttons: {
-					Ok: function() {
-						$( this ).dialog( "close" );
-					}
-				}
-			});
-		})
-		.fail(function(jqxhr, textStatus, error) {
-			console.log("Error fetching player inventory");
-		})
-		.always(function() {
-		});
-	};
-
-	for (var y = 0; y < BAG_ROWS; y++) {
-		$("#bagTable").append("<tr id=\"bagRow"+y+"\"></tr>");
-		for (var x = 0; x < BAG_COLS; x++) {
-			$("#bagRow"+y).append(
-				"<td class=\"invField\" id=\"bagField"+x+"_"+y+"\">" +
-				"<span class=\"invFieldText\" id=\"bagFieldText"+x+"_"+y+"\"></span>" +
-				"</td>");
-		}
-	}
-
-	$("#beltTable").append("<tr id=\"beltRow0\"></tr>");
-	for (var x = 0; x < BELT_COLS; x++) {
-		$("#beltRow0").append(
-			"<td class=\"invField\" id=\"beltField"+x+"\">" +
-			"<span class=\"invFieldText\" id=\"beltFieldText"+x+"\"></span>" +
-			"</td>");
-	}
-
-
-
-	// ===============================================================================================
-	// Player markers
-
-	$(".leaflet-popup-pane").on('click.action', '.inventoryButton', function(event) {
-		showInv($(this).data('steamid'));
-	});
-
-	var setPlayerMarkers = function(data) {
-		var online = 0;
-		var offline = 0;
-		$.each( data, function( key, val ) {
-			var marker;
-			if (playersMappingList.hasOwnProperty(val.steamid)) {
-				marker = playersMappingList[val.steamid].currentPosMarker;
-				marker.setLatLng([val.position.x, val.position.z]);
-			} else {
-				marker = L.marker([val.position.x, val.position.z]).bindPopup(
-					"Player: " + val.name +
-					(hasPermission ("webapi.getplayerinventory") ?
-						"<br/><a class='inventoryButton' data-steamid='"+val.steamid+"'>Show inventory</a>"
-						: "")
-				);
-				playersMappingList[val.steamid] = { online: !val.online };
-			}
-			if (playersMappingList[val.steamid].online != val.online) {
-				if (val.online) {
-					marker.setOpacity(1.0);
-					playersOfflineMarkerGroup.removeLayer(marker);
-					playersOnlineMarkerGroup.addLayer(marker);
-				} else {
-					marker.setOpacity(0.5);
-					playersOnlineMarkerGroup.removeLayer(marker);
-					playersOfflineMarkerGroup.addLayer(marker);
-				}
-			}
-			val.currentPosMarker = marker;
-			playersMappingList[val.steamid] = val;
-		
-			if (val.online)
-				online++;
-			else
-				offline++;
-		});
-		$( "#mapControlOnlineCount" ).text( online );
-		$( "#mapControlOfflineCount" ).text( offline );
-	}
-
-	var updatePlayerEvent = function() {
-		$.getJSON( "../api/getplayerslocation")
-		.done(setPlayerMarkers)
-		.fail(function(jqxhr, textStatus, error) {
-			console.log("Error fetching players list");
-		})
-		.always(function() {
-			window.setTimeout(updatePlayerEvent, 2000);
-		});
-	}
-
-	if (hasPermission ("webapi.getplayerslocation")) {
-		window.setTimeout(updatePlayerEvent, 0);
-	}
-
-
-
-	// ===============================================================================================
-	// Land claim markers
-
-	var setLandClaims = function(data) {
-		landClaimsClusterGroup.clearLayers();
-		landClaimsRectGroup.clearLayers();
-	
-		var claimPower = Math.floor(Math.log(data.claimsize) / Math.LN2);
-		var maxClusterZoomUnlimited = mapinfo.maxzoom - (claimPower - 3);
-		var maxClusterZoomLimitedMax = Math.min(maxClusterZoomUnlimited, mapinfo.maxzoom+1);
-		maxZoomForCluster = Math.max(maxClusterZoomLimitedMax, 0);
-	
-		checkClaimClustering({target: map});
-
-		var sizeHalf = Math.floor(data.claimsize / 2);
-
-		$.each( data.claimowners, function( key, val ) {
-			var steamid = val.steamid;
-			var active = val.claimactive;
-			var color = active ? "#55ff55" : "#ff0000";
-		
-			$.each( val.claims, function( key, val ) {
-				var pos = L.latLng(val.x, val.z);
-				var bounds = L.latLngBounds(L.latLng(val.x - sizeHalf, val.z - sizeHalf), L.latLng(val.x + sizeHalf, val.z + sizeHalf));
-				var r = L.rectangle(bounds, {color: color, weight: 1, opacity: 0.8, fillOpacity: 0.15});
-				var m = L.marker(pos, { clickable: false, keyboard: false, zIndexOffset:-1000, iconSize: [0,0], icon: L.divIcon({className: 'invisIcon', iconSize:[0,0]}) });
-				if (playersMappingList.hasOwnProperty(steamid)) {
-					var name = playersMappingList[steamid].name;
-				} else {
-					var name = "unknown"
-				}
-				r.bindPopup("Owner: " + name + " ("+steamid+")<br/>Position: " + val.x + " / " + val.y + " / " + val.z);
-				landClaimsRectGroup.addLayer(r);
-				landClaimsClusterGroup.addLayer(m);
-			});
-		});
-	}
-
-	var updateClaimsEvent = function() {
-		$.getJSON( "../api/getlandclaims")
-		.done(setLandClaims)
-		.fail(function(jqxhr, textStatus, error) {
-			console.log("Error fetching land claim list");
-		})
-		.always(function() {
-			//updateClaimTimer = window.setTimeout(updateClaimsEvent, 3000);
-		});
-	}
-
-
-
-	// ===============================================================================================
-	// Layer events
-
-	var updateClaimTimer;
-	map.on('overlayadd', function(e) {
-		if (e.layer == landClaimsGroup) {
-			updateClaimsEvent();
-		}
-	});
-
-	map.on('overlayremove', function(e) {
-		if (e.layer == landClaimsGroup) {
-			//window.clearTimeout(updateClaimTimer);
-		}
-	});
-
-	var checkClaimClustering = function(e) {
-		if (e.target._zoom >= maxZoomForCluster) {
-			landClaimsGroup.removeLayer(landClaimsClusterGroup);	
-		} else {
-			landClaimsGroup.addLayer(landClaimsClusterGroup);	
-		}
-	};
-
-	map.on('zoomend', checkClaimClustering);
-
-}
-
-
-
-
-function doTabs () {
-	$(".adminmenu > ul > li").addClass ("menu_button");
-	$(".admincontent > div").addClass ("contenttab");
-	$(".adminmenu .menu_button").first ().addClass ("current_tab");
-	$(".menu_button").on ('click.action', null, function (event) {
-		var menuElement = $(this);
-		var linkElement = menuElement.children ("a");
-		var linkName = linkElement.attr ("href");
-		
-		$("*").removeClass ("current_tab");
-		menuElement.addClass ("current_tab");
-		$(linkName).addClass ("current_tab");
-	});
-	
-	$(".adminmenu .menu_button").first ().click ();
-}
-
-function initMapInfo () {
-	$.getJSON( "../map/mapinfo.json")
-	.done(function(data) {
-		mapinfo.tilesize = data.blockSize;
-		mapinfo.maxzoom = data.maxZoom;
-	})
-	.fail(function(jqxhr, textStatus, error) {
-		console.log("Error fetching map information");
-	})
-	.always(function() {
-		initMap();
-	});
-}
-
-function initUser () {
-	$.getJSON( "../userstatus")
-	.done(function(data) {
-		userdata = data;
-		
-		var userdataDiv = $("#userstate");
-		if (userdata.loggedin == true) {
-			var data = userdataDiv.children ("#userstate_loggedin");
-			data.attr ("style", "display: block");
-			data.children ("#username").attr ("href", "http://steamcommunity.com/profiles/" + userdata.username);
-			data.children ("#username").html (userdata.username);
-		} else {
-			var data = userdataDiv.children ("#userstate_loggedout");
-			data.attr ("style", "display: block");
-		}
-
-		initMapInfo ();
-	})
-	.fail(function(jqxhr, textStatus, error) {
-		console.log("Error fetching user data");
-	})
-}
-
-doTabs ();
-initUser ();
Index: /binary-improvements/webserver/js/inventory_dialog.js
===================================================================
--- /binary-improvements/webserver/js/inventory_dialog.js	(revision 245)
+++ /binary-improvements/webserver/js/inventory_dialog.js	(revision 245)
@@ -0,0 +1,109 @@
+var ITEMICONBASEURL = "../itemicons/";
+
+var BAG_COLS = 8;
+var BAG_ROWS = 4;
+var BELT_COLS = 8;
+var INV_ITEM_WIDTH = 58;
+var INV_ITEM_HEIGHT = 40;
+
+function ShowInventoryDialog (steamid) {
+	var SetCellItem = function (containerTypeName, cellIdent, itemdata) {
+		var cell = $("#" + containerTypeName + "Field"+cellIdent);
+		var text = $("#" + containerTypeName + "FieldText"+cellIdent);
+		if (itemdata.count > 0) {
+			cell.attr("style", "background-image: url(" + ITEMICONBASEURL + itemdata.name + ".png);");
+			cell.attr("title", itemdata.name);
+			text.text(itemdata.count);
+		} else {
+			cell.attr("style", "background-image: none;");
+			cell.removeAttr("title");
+			text.text("");
+		}
+	}
+	
+	var SetEquipmentItem = function (data, name, cellIdent) {
+		if (data.equipment [name] == false) {
+			SetCellItem ("equipment", cellIdent, { count: 0, name: "" });
+		} else {
+			SetCellItem ("equipment", cellIdent, { count: 1, name: data.equipment [name] });
+		}
+	}
+
+	$.getJSON( "../api/getplayerinventory", { steamid: steamid  })
+	.done(function(data) {
+		$("#invPlayerName").text(data.playername);
+		
+		for (var y = 0; y < BAG_ROWS; y++) {
+			for (var x = 0; x < BAG_COLS; x++) {
+				SetCellItem ("bag", x + "_" + y, data.bag[y*BAG_COLS+x]);
+			}
+		}
+
+		for (var x = 0; x < BELT_COLS; x++) {
+			SetCellItem ("belt", x, data.belt[x]);
+		}
+		
+		SetEquipmentItem (data, "head", "0_0");
+		SetEquipmentItem (data, "eyes", "0_1");
+		SetEquipmentItem (data, "face", "0_2");
+		SetEquipmentItem (data, "armor", "1_0");
+		SetEquipmentItem (data, "jacket", "1_1");
+		SetEquipmentItem (data, "shirt", "1_2");
+		SetEquipmentItem (data, "legarmor", "2_0");
+		SetEquipmentItem (data, "pants", "2_1");
+		SetEquipmentItem (data, "boots", "2_2");
+		SetEquipmentItem (data, "gloves", "0_4");
+		SetEquipmentItem (data, "backpack", "2_4");
+
+		$( "#playerInventoryDialog" ).css("z-index", "1010").dialog({
+			modal: true,
+			width: BAG_COLS*(INV_ITEM_WIDTH+14) + 3*(INV_ITEM_WIDTH+14) + 20,
+			buttons: {
+				Ok: function() {
+					$( this ).dialog( "close" );
+				}
+			}
+		});
+	})
+	.fail(function(jqxhr, textStatus, error) {
+		console.log("Error fetching player inventory");
+	})
+	.always(function() {
+	});
+}
+
+function SetupInventoryDialog () {
+	var CreateInvCell = function (containerTypeName, cellIdent) {
+		return "<td class=\"invField\" id=\"" + containerTypeName + "Field"+cellIdent+"\">" +
+			"<span class=\"invFieldText\" id=\"" + containerTypeName + "FieldText"+cellIdent+"\"></span>" +
+			"</td>";
+	}
+
+	for (var y = 0; y < BAG_ROWS; y++) {
+		$("#bagTable").append("<tr id=\"bagRow"+y+"\"></tr>");
+		for (var x = 0; x < BAG_COLS; x++) {
+			$("#bagRow"+y).append(CreateInvCell ("bag", x + "_" + y));
+		}
+	}
+
+	$("#beltTable").append("<tr id=\"beltRow0\"></tr>");
+	for (var x = 0; x < BELT_COLS; x++) {
+		$("#beltRow0").append(CreateInvCell ("belt", x));
+	}
+	
+	for (var y = 0; y < 5; y++) {
+		$("#equipmentTable").append("<tr id=\"equipmentRow"+y+"\"></tr>");
+		if (y == 3) {
+			$("#equipmentRow"+y).append("<td colspan=\"3\"></td>");
+		} else {
+			for (var x = 0; x < 3; x++) {
+				if (y == 4 && x == 1) {
+					$("#equipmentRow"+y).append("<td></td>");
+				} else {
+					$("#equipmentRow"+y).append(CreateInvCell ("equipment", x + "_" + y));
+				}
+			}
+		}
+	}
+}
+
Index: nary-improvements/webserver/js/leaflet.control.gametime.js
===================================================================
--- /binary-improvements/webserver/js/leaflet.control.gametime.js	(revision 244)
+++ 	(revision )
@@ -1,47 +1,0 @@
-L.Control.GameTime = L.Control.extend({
-	options: {
-		position: 'bottomright'
-	},
-
-	onAdd: function (map) {
-		var name = 'control-gametime',
-		    container = L.DomUtil.create('div', name + ' webmap-control');
-	
-		container.innerHTML = "Loading ..."
-		L.DomEvent.on (container, 'mousemove', L.DomEvent.stopPropagation);
-
-		this._map = map;
-		this._div = container;
-
-		window.setTimeout($.proxy(this._updateGameTimeEvent, this), 0);
-
-		return container;
-	},
-
-	onRemove: function (map) {
-	},
-
-	_updateGameTimeEvent: function() {
-		var div = this._div;
-		$.getJSON( "../api/getstats")
-		.done(function(data) {
-			var time = "Day " + data.gametime.days + ", ";
-			if (data.gametime.hours < 10)
-				time += "0";
-			time += data.gametime.hours;
-			time += ":";
-			if (data.gametime.minutes < 10)
-				time += "0";
-			time += data.gametime.minutes;
-			div.innerHTML = time;
-		})
-		.fail(function(jqxhr, textStatus, error) {
-			console.log("Error fetching game stats");
-		})
-		.always(function() {
-		});
-		window.setTimeout($.proxy(this._updateGameTimeEvent, this), 2000);
-	}
-
-});
-
Index: /binary-improvements/webserver/js/leaflet.layer.landclaims.js
===================================================================
--- /binary-improvements/webserver/js/leaflet.layer.landclaims.js	(revision 245)
+++ /binary-improvements/webserver/js/leaflet.layer.landclaims.js	(revision 245)
@@ -0,0 +1,76 @@
+function GetLandClaimsLayer (map, mapinfo) {
+	var landClaimsGroup = L.layerGroup();
+	var landClaimsClusterGroup = L.markerClusterGroup({
+		disableClusteringAtZoom: mapinfo.maxzoom,
+		singleMarkerMode: true,
+		maxClusterRadius: 50
+	});
+	var landClaimsRectGroup = L.layerGroup();
+	landClaimsGroup.addLayer(landClaimsClusterGroup);
+	landClaimsGroup.addLayer(landClaimsRectGroup);
+	var maxZoomForCluster = -1;
+
+
+	var setLandClaims = function(data) {
+		landClaimsClusterGroup.clearLayers();
+		landClaimsRectGroup.clearLayers();
+	
+		var claimPower = Math.floor(Math.log(data.claimsize) / Math.LN2);
+		var maxClusterZoomUnlimited = mapinfo.maxzoom - (claimPower - 3);
+		var maxClusterZoomLimitedMax = Math.min(maxClusterZoomUnlimited, mapinfo.maxzoom+1);
+		maxZoomForCluster = Math.max(maxClusterZoomLimitedMax, 0);
+	
+		checkClaimClustering({target: map});
+
+		var sizeHalf = Math.floor(data.claimsize / 2);
+
+		$.each( data.claimowners, function( key, val ) {
+			var steamid = val.steamid;
+			var active = val.claimactive;
+			var color = active ? "#55ff55" : "#ff0000";
+			if (val.playername) {
+				var name = val.playername;
+			} else {
+				var name = "&lt;unknown&gt;"
+			}
+		
+			$.each( val.claims, function( key, val ) {
+				var pos = L.latLng(val.x, val.z);
+				var bounds = L.latLngBounds(L.latLng(val.x - sizeHalf, val.z - sizeHalf), L.latLng(val.x + sizeHalf, val.z + sizeHalf));
+				var r = L.rectangle(bounds, {color: color, weight: 1, opacity: 0.8, fillOpacity: 0.15});
+				var m = L.marker(pos, { clickable: false, keyboard: false, zIndexOffset:-1000, iconSize: [0,0], icon: L.divIcon({className: 'invisIcon', iconSize:[0,0]}) });
+				r.bindPopup("Owner: " + name + " ("+steamid+")<br/>Position: " + val.x + " / " + val.y + " / " + val.z);
+				landClaimsRectGroup.addLayer(r);
+				landClaimsClusterGroup.addLayer(m);
+			});
+		});
+	}
+
+	var updateClaimsEvent = function() {
+		$.getJSON( "../api/getlandclaims")
+		.done(setLandClaims)
+		.fail(function(jqxhr, textStatus, error) {
+			console.log("Error fetching land claim list");
+		})
+	}
+
+
+	var checkClaimClustering = function(e) {
+		if (e.target._zoom >= maxZoomForCluster) {
+			landClaimsGroup.removeLayer(landClaimsClusterGroup);	
+		} else {
+			landClaimsGroup.addLayer(landClaimsClusterGroup);	
+		}
+	};
+
+	map.on('zoomend', checkClaimClustering);
+	
+	map.on('overlayadd', function(e) {
+		if (e.layer == landClaimsGroup) {
+			updateClaimsEvent();
+		}
+	});
+
+	return landClaimsGroup;
+}
+
Index: /binary-improvements/webserver/js/map.js
===================================================================
--- /binary-improvements/webserver/js/map.js	(revision 245)
+++ /binary-improvements/webserver/js/map.js	(revision 245)
@@ -0,0 +1,219 @@
+var mapinfo = {
+	regionsize: 512,
+	chunksize: 16,
+	tilesize: 128,
+	maxzoom: 4
+}
+
+function InitMap() {
+	// ===============================================================================================
+	// 7dtd coordinate transformations
+
+	SDTD_Projection = {
+		project: function (latlng) {
+			return new L.Point(
+				(latlng.lat) / Math.pow(2, mapinfo.maxzoom),
+				(latlng.lng) / Math.pow(2, mapinfo.maxzoom) );
+		},
+		
+		unproject: function (point) {
+			return new L.LatLng(
+				point.x * Math.pow(2, mapinfo.maxzoom),
+				point.y * Math.pow(2, mapinfo.maxzoom) );
+		}
+	};
+
+	SDTD_CRS = L.extend({}, L.CRS.Simple, {
+		projection: SDTD_Projection,
+		transformation: new L.Transformation(1, 0, -1, 0),
+
+		scale: function (zoom) {
+			return Math.pow(2, zoom);
+		}
+	});
+
+
+
+
+	// ===============================================================================================
+	// Map and basic tile layers
+
+	var initTime = new Date().getTime();
+
+	map = L.map('tab_map', {
+		zoomControl: false, // Added by Zoomslider
+		zoomsliderControl: true,
+		attributionControl: false,
+		crs: SDTD_CRS
+	}).setView([0, 0], Math.max(0, mapinfo.maxzoom - 5));
+
+	var tileLayer = L.tileLayer('../map/{z}/{x}/{y}.png?t={time}', {
+		maxZoom: mapinfo.maxzoom + 1,
+		minZoom: Math.max(0, mapinfo.maxzoom - 5),
+		maxNativeZoom: mapinfo.maxzoom,
+		tileSize: mapinfo.tilesize,
+		continuousWorld: true,
+		tms: true,
+		unloadInvisibleTiles: false,
+		time: initTime
+	});
+	
+	// TileLayer w/ TMS=true fix for zoomlevel >= 8
+	tileLayer._getWrapTileNum = function () {
+		return L.point(0, 0);
+	};
+
+	var tileLayerMiniMap = L.tileLayer('../map/{z}/{x}/{y}.png?t={time}', {
+		maxZoom: mapinfo.maxzoom,
+		minZoom: 0,
+		maxNativeZoom: mapinfo.maxzoom,
+		tileSize: mapinfo.tilesize,
+		continuousWorld: true,
+		tms: true,
+		unloadInvisibleTiles: false,
+		time: initTime
+	});
+
+
+
+
+
+
+
+	// ===============================================================================================
+	// Overlays and controls
+
+	var playersOnlineMarkerGroup = L.markerClusterGroup({
+		maxClusterRadius: function(zoom) { return zoom == mapinfo.maxzoom ? 10 : 50; }
+	});
+	var playersOfflineMarkerGroup = L.markerClusterGroup({
+		maxClusterRadius: function(zoom) { return zoom == mapinfo.maxzoom ? 10 : 50; }
+	});
+
+
+
+	var baseLayers = {
+		//"Map": tileLayer
+	};
+
+	var layerControl = L.control.layers(baseLayers, null, {
+		collapsed: false
+	});
+	
+	var layerCount = 0;
+
+
+	tileLayer.addTo(map);
+	new L.Control.Coordinates({}).addTo(map);
+	new L.Control.ReloadTiles({layers: [tileLayer, tileLayerMiniMap]}).addTo(map);
+	layerControl.addOverlay (GetRegionLayer (mapinfo), "Region files");
+	var miniMap = new L.Control.MiniMap(tileLayerMiniMap, {
+		zoomLevelOffset: -6,
+		toggleDisplay: true
+	}).addTo(map);
+
+	if (HasPermission ("webapi.getlandclaims")) {
+		layerControl.addOverlay (GetLandClaimsLayer (map, mapinfo), "Land claims");
+		layerCount++;
+	}
+	if (HasPermission ("webapi.getplayerslocation")) {
+		layerControl.addOverlay (playersOfflineMarkerGroup, "Players (offline) (<span id='mapControlOfflineCount'>0</span>)");
+		layerControl.addOverlay (playersOnlineMarkerGroup, "Players (online) (<span id='mapControlOnlineCount'>0</span>)");
+		layerCount++;
+	}
+
+	if (layerCount > 0) {
+		layerControl.addTo(map);
+	}
+
+
+
+
+	var playersMappingList = {};
+
+
+
+	// ===============================================================================================
+	// Player markers
+
+	$(".leaflet-popup-pane").on('click.action', '.inventoryButton', function(event) {
+		ShowInventoryDialog ($(this).data('steamid'));
+	});
+
+	var setPlayerMarkers = function(data) {
+		var online = 0;
+		var offline = 0;
+		$.each( data, function( key, val ) {
+			var marker;
+			if (playersMappingList.hasOwnProperty(val.steamid)) {
+				marker = playersMappingList[val.steamid].currentPosMarker;
+				marker.setLatLng([val.position.x, val.position.z]);
+			} else {
+				marker = L.marker([val.position.x, val.position.z]).bindPopup(
+					"Player: " + val.name +
+					(HasPermission ("webapi.getplayerinventory") ?
+						"<br/><a class='inventoryButton' data-steamid='"+val.steamid+"'>Show inventory</a>"
+						: "")
+				);
+				playersMappingList[val.steamid] = { online: !val.online };
+			}
+			if (playersMappingList[val.steamid].online != val.online) {
+				if (val.online) {
+					marker.setOpacity(1.0);
+					playersOfflineMarkerGroup.removeLayer(marker);
+					playersOnlineMarkerGroup.addLayer(marker);
+				} else {
+					marker.setOpacity(0.5);
+					playersOnlineMarkerGroup.removeLayer(marker);
+					playersOfflineMarkerGroup.addLayer(marker);
+				}
+			}
+			val.currentPosMarker = marker;
+			playersMappingList[val.steamid] = val;
+		
+			if (val.online)
+				online++;
+			else
+				offline++;
+		});
+		$( "#mapControlOnlineCount" ).text( online );
+		$( "#mapControlOfflineCount" ).text( offline );
+	}
+
+	var updatePlayerEvent = function() {
+		$.getJSON( "../api/getplayerslocation")
+		.done(setPlayerMarkers)
+		.fail(function(jqxhr, textStatus, error) {
+			console.log("Error fetching players list");
+		})
+		.always(function() {
+			window.setTimeout(updatePlayerEvent, 2000);
+		});
+	}
+
+	if (HasPermission ("webapi.getplayerslocation")) {
+		window.setTimeout(updatePlayerEvent, 0);
+	}
+
+
+}
+
+
+
+
+
+function StartMapModule () {
+	$.getJSON( "../map/mapinfo.json")
+	.done(function(data) {
+		mapinfo.tilesize = data.blockSize;
+		mapinfo.maxzoom = data.maxZoom;
+	})
+	.fail(function(jqxhr, textStatus, error) {
+		console.log ("Error fetching map information");
+	})
+	.always(function() {
+		InitMap ();
+	});
+}
+
+
Index: /binary-improvements/webserver/js/permissions.js
===================================================================
--- /binary-improvements/webserver/js/permissions.js	(revision 245)
+++ /binary-improvements/webserver/js/permissions.js	(revision 245)
@@ -0,0 +1,50 @@
+var userdata = false;
+
+function InitPermissions () {
+	$.getJSON( "../userstatus")
+	.done(function(data) {
+		userdata = data;
+		
+		var userdataDiv = $("#userstate");
+		if (userdata.loggedin == true) {
+			var data = userdataDiv.children ("#userstate_loggedin");
+			data.attr ("style", "display: block");
+			data.children ("#username").attr ("href", "http://steamcommunity.com/profiles/" + userdata.username);
+			data.children ("#username").html (userdata.username);
+		} else {
+			var data = userdataDiv.children ("#userstate_loggedout");
+			data.attr ("style", "display: block");
+		}
+		
+		if (HasPermission ("webapi.getstats")) {
+			$("#serverstats").attr ("style", "display: block");
+		}
+		
+		ApplyTabPermissions ();
+
+		if (HasPermission ("web.map")) {
+			StartMapModule ();
+		}		
+		
+		if (HasPermission ("webapi.getstats")) {
+			StartStatsModule ();
+		}
+
+	})
+	.fail(function(jqxhr, textStatus, error) {
+		console.log("Error fetching user data");
+	})
+	.always(function () {
+		$("#nopermissionwarning").attr ("style", "display: block");
+	})
+}
+
+function HasPermission (modulename) {
+	for (var i = 0; i < userdata.permissions.length; i++) {
+		if (userdata.permissions [i].module == modulename) {
+			return userdata.permissions [i].allowed;
+		}
+	}
+	return false;
+}
+
Index: /binary-improvements/webserver/js/stats.js
===================================================================
--- /binary-improvements/webserver/js/stats.js	(revision 245)
+++ /binary-improvements/webserver/js/stats.js	(revision 245)
@@ -0,0 +1,26 @@
+function StartStatsModule () {
+	var updateGameTimeEvent = function() {
+		$.getJSON( "../api/getstats")
+		.done(function(data) {
+			var time = "Day " + data.gametime.days + ", ";
+			if (data.gametime.hours < 10)
+				time += "0";
+			time += data.gametime.hours;
+			time += ":";
+			if (data.gametime.minutes < 10)
+				time += "0";
+			time += data.gametime.minutes;
+
+			$("#stats_time").html (time);
+			$("#stats_players").html (data.players);
+		})
+		.fail(function(jqxhr, textStatus, error) {
+			console.log("Error fetching game stats");
+		})
+		.always(function() {
+		});
+		window.setTimeout(updateGameTimeEvent, 2000);
+	};
+	updateGameTimeEvent();
+}
+
Index: /binary-improvements/webserver/js/tabs.js
===================================================================
--- /binary-improvements/webserver/js/tabs.js	(revision 245)
+++ /binary-improvements/webserver/js/tabs.js	(revision 245)
@@ -0,0 +1,27 @@
+function InitializeTabs () {
+	$("#adminmenu > ul > li").addClass ("menu_button");
+	$(".admincontent > div").addClass ("contenttab");
+	$(".menu_button").on ('click.action', null, function (event) {
+		var menuElement = $(this);
+		var linkElement = menuElement.children ("a");
+		var linkName = linkElement.attr ("href");
+		
+		$("*").removeClass ("current_tab");
+		menuElement.addClass ("current_tab");
+		$(linkName).addClass ("current_tab");
+	});
+}
+
+function ApplyTabPermissions () {
+	$("#adminmenu .menu_button").each (function () {
+		if ($(this).children ("a").data ("permission")) {
+			var perm = $(this).children ("a").data ("permission");
+			if (HasPermission (perm)) {
+				$(this).addClass ("allowed");
+			}
+		}
+	});
+
+	$("#adminmenu .allowed").first ().click ();
+}
+
Index: /binary-improvements/webserver/js/util.js
===================================================================
--- /binary-improvements/webserver/js/util.js	(revision 244)
+++ /binary-improvements/webserver/js/util.js	(revision 245)
@@ -11,11 +11,2 @@
 }
 
-function hasPermission (modulename) {
-	for (var i = 0; i < userdata.permissions.length; i++) {
-		if (userdata.permissions [i].module == modulename) {
-			return userdata.permissions [i].allowed;
-		}
-	}
-	return false;
-}
-
