public Node(Node parent, Position position)
			{
				Parent = parent;
				Position = position;
				DistanceFromStart = 0;
				DistanceToGoal = 0;
			}
		public Position GetClosestWalkablePositionWithUnknownNeighbour(Position fromPos, Func<Position, bool> predicate)
		{
			return AllPositions.Where(IsWalkable)
			                   .Where(x => x.GetNeighbours().Any(n => GetPositionValue(n) == uint.MaxValue))
			                   .OrderBy(fromPos.Distance)
			                   .FirstOrDefault(predicate);
		}
		public bool IsWalkable(Position pos)
		{
			var value = (TileFlags)GetPositionValue(pos);
			if (value == TileFlags.UNKNOWN) return true;
			if (value == TileFlags.NOTHING) return false;
			if ((value & (TileFlags.PERIMETER | TileFlags.BLOCKED)) > 0) return false;
			return true;
		}
		public static IEnumerable<Position> CalculatePath(Position start, Position end, Func<Position, bool> isWalkable)
		{
			var startNode = new Node(null, start);

			var aStar = new HashSet<Position>();
			var open = new List<Node> { startNode };

			var result = new LinkedList<Position>();

			while (open.Any())
			{
				var closestNode = open.OrderBy(node => node.DistanceFromStart).First();
				open.Remove(closestNode);

				if (closestNode.Position.Equals(end))
				{
					while (true)
					{
						result.AddFirst(closestNode.Position);

						if (closestNode.Parent == null)
						{
							return result;
						}

						closestNode = closestNode.Parent;
					}
				}

				var walkableNeighbours = closestNode.Position.GetNeighbours().Where(isWalkable);

				foreach (var neighbour in walkableNeighbours)
				{
					if (aStar.Contains(neighbour)) continue;

					var distanceToGoal = closestNode.DistanceToGoal + neighbour.Distance(closestNode.Position);
					var distanceFromStart = distanceToGoal + neighbour.Distance(end);

					open.Add(new Node(closestNode, neighbour)
					{
						DistanceToGoal = distanceToGoal,
						DistanceFromStart = distanceFromStart
					});

					aStar.Add(neighbour);
				}
			}

			return result;
		}
		private Direction GetDirection(Position from, Position to)
		{
			if (from == null || to == null) return Direction.None;

			if (to.X > from.X)
			{
				if(to.Y > from.Y) return Direction.DownRight;
				if(to.Y < from.Y) return Direction.UpRight;
				return Direction.Right;
			}

			if (to.X < from.X)
			{
				if(to.Y > from.Y) return Direction.DownLeft;
				if(to.Y < from.Y) return Direction.UpLeft;
				return Direction.Left;
			}

			if (to.Y > from.Y) return Direction.Down;
			if (to.Y < from.Y) return Direction.Up;

			return Direction.None;
		}
		private bool PlayerCanWalkHere(Character player, Map map, Position pos)
		{
			var friends = GetFriends(player);
			var enemyCharacters = GetEnemyCharacters(player);
			var monsters = GetMonsters(player);
			var boulders = player.VisibleItems.Where(item => GetInfoFor(item.Id).SubType == "boulder");

			var blockingItems = friends.Concat(boulders);

			if (!PlayerHasPvPMode(player.Id))
			{
				blockingItems = blockingItems.Concat(enemyCharacters);
			}

			if (!PlayerHasAttackMode(player.Id))
			{
				blockingItems = blockingItems.Concat(monsters);
			}

			var blockedPositions = blockingItems.Select(item => item.Position);

			return !blockedPositions.Any(x => x.Equals(pos)) && (PlayerIsGaseous(player.Id) || map.IsWalkable(pos));
		}
		private bool GaseousPlayerCanWalkHere(Character player, Position pos)
		{
			var entities = player.VisibleEntities.Where(item => item.Id != player.Id);
			var boulders = player.VisibleItems.Where(item => GetInfoFor(item.Id).SubType == "boulder");
			var blocked = entities.Concat(boulders).Select(item => new Position(item.XPos, item.YPos));
			return !blocked.Any(x => x.Equals(pos));
		}
		private void SetTempGoalForPlayer(string playerId, Position goal)
		{
			_tmpGoals[playerId] = goal;
		}
		public void SetGoalForPlayer(string playerId, Position goal)
		{
			if (goal == null || playerId == null) return;

			var player = GetPlayer(playerId);
			_goals[player.Id] = goal;
			AddMessage(String.Format("New goal for player {0} [{1}] set to ({2},{3})", player.Name, player.Id, goal.X, goal.Y));
		}
		private Position GetNextPosition(Position pos, Direction dir)
		{
			return pos.GetNeighbours().FirstOrDefault(next => GetDirection(pos, next) == dir);
		}
		public int Distance(Position to)
		{
			return Math.Max(Math.Abs(X - to.X), Math.Abs(Y - to.Y));
		}
		private Position SelectPosition(Map map, Position start, string title)
		{
			var mapArea = CreateMapArea(map);
			var messageArea = CreateMessageArea();
			mapArea.SetTitle(title);
			messageArea.SetTitle(title);

			var position = start;

			while (true)
			{
				var previousPosition = position;

				mapArea.Write("S", start.X, start.Y, ConsoleColor.Blue, ConsoleColor.DarkBlue);
				mapArea.Write("X", position.X, position.Y, ConsoleColor.Red, ConsoleColor.DarkRed);
				mapArea.CenterOffset(position.X, position.Y);

				messageArea.Clear();
				messageArea.Write(string.Format("Select position and press [ENTER] ([Escape] to abort)"), 1, 1);
				messageArea.Write(string.Format("Current position: {0}, {1}", position.X, position.Y), 1, 2);

				_console2.DrawArea(mapArea, 0, 0);
				_console2.DrawArea(messageArea, 0, mapArea.Height);

				switch (Console.ReadKey(true).Key)
				{
					case ConsoleKey.UpArrow:
						position = new Position(position.X, position.Y - 1);
						break;
					case ConsoleKey.DownArrow:
						position = new Position(position.X, position.Y + 1);
						break;
					case ConsoleKey.LeftArrow:
						position = new Position(position.X - 1, position.Y);
						break;
					case ConsoleKey.RightArrow:
						position = new Position(position.X + 1, position.Y);
						break;
					case ConsoleKey.Enter:
						return position;
					case ConsoleKey.Escape:
						return null;
				}

				FillArea(map, mapArea, new[] { previousPosition });
			}
		}
		private void SetGoal(IEnumerable<string> args)
		{
			Position posArg1 = null;
			if (args != null && args.Any())
			{
				posArg1 = TryParsePosition(args.FirstOrDefault());
			}

			var player = _context.GetPlayer(_currentPlayerId);
			var map = _context.GetMap(player.CurrentMap);
			var mapArea = CreateMapArea(map);

			_console2.DrawArea(CreatePlayerArea(player), mapArea.Width, 0);

			var startPos = new Position(player.XPos, player.YPos);

			var endPos = posArg1 ?? SelectPosition(map, startPos, "Select goal for player");
			if (endPos == null) return;

			_context.SetGoalForPlayer(player.Id, endPos);
		}
		public uint GetPositionValue(Position position)
		{
			return _positions.ContainsKey(position) ? _positions[position] : uint.MaxValue;
		}
		public void SetPositionValue(Position position, uint value)
		{
			UpdatePosition(position, value);
		}
		private void UpdatePosition(Position pos, uint val)
		{
			if (pos == null || (_positions.ContainsKey(pos) && GetPositionValue(pos) == val)) return;
			_hasChanges = true;
			_positions[pos] = val;
		}