Example #1
0
    def load_world(self):
        try:
            save = open(self._save_file, "rb").read()
            self._world = World.from_save(save)
            return
        except FileNotFoundError:
            logging.info("Save file not found, creating a new one")
        except (IOError, ValueError) as ex:
            logging.error("Error during loading save file: %s" % repr(ex))
            logging.error(traceback.format_exc())

        self._world = World()
Example #2
0
    def load_world(self):
        try:
            save = open(self._save_file, "rb").read()
            self._world = World.from_save(save)
            return
        except FileNotFoundError:
            logging.info("Save file not found, creating a new one")
        except (IOError, ValueError) as ex:
            logging.error("Error during loading save file: %s" % repr(ex))
            logging.error(traceback.format_exc())

        self._world = World()
Example #3
0
class ClassicServer(object):
    MTU = 1024

    _bind_address = None
    _running = None
    _sock = None

    _packet_handler = None

    _connections = {}

    _players = {}
    _players_by_address = {}

    _connections_lock = None
    _players_lock = None

    _player_id = 0

    _server_name = ""
    _motd = ""

    _save_file = ""
    _heartbeat_url = ""
    _salt = ""

    _op_players = []
    _max_players = -1

    _world = None

    def __init__(self, config):
        # bind_address, server_name="", motd="", save_file="", heartbeat_url="", op_players=None, max_players=32
        self._bind_address = ("0.0.0.0", int(config["server"]["port"]))
        self._running = False
        self._server_name = config["server"]["name"]
        self._motd = config["server"]["motd"]
        self._save_file = config["save"]["file"]
        self._heartbeat_url = config["heartbeat_url"]
        self._op_players = config["server"]["ops"]
        self._max_players = config["server"]["max_players"]

        if self._max_players > 255:
            raise ValueError("The player limit is up to 255 excluding the admin slot.")

        logging.basicConfig(level=logging.DEBUG)

        self._connections_lock = threading.RLock()
        self._players_lock = threading.RLock()

        self._packet_handler = PacketHandler(self)

        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self._sock.bind(self._bind_address)

        self._sock.listen(1)
        self._start()

    def data_hook(self, connection, data):
        """
        Data hook, to report received data.
        :param connection: The connection from which the data originates from.
        :type connection: Connection
        :param data: The received data.
        :type data: buffer
        """

        try:
            self._packet_handler.handle_packet(connection, data)
        except Exception as ex:
            logging.error("Error in packet handler: %s" % repr(ex))
            logging.debug(traceback.format_exc())

    def _heartbeat_thread(self):
        while self._running:
            try:
                f = urllib.request.urlopen(self._heartbeat_url + (
                    "?port=%d&max=%d&name=%s&public=True&version=7&salt=%s&users=%d" % (
                        self._bind_address[1], self._max_players,
                        urllib.parse.quote(self._server_name, safe=""), self._salt, len(self._players))
                ))

                data = f.read()

                logging.debug("Heartbeat sent, json response: %s" % data.decode("utf-8"))

            except BaseException as ex:
                logging.error("Heartbeat failed: %s" % repr(ex))
                logging.debug(traceback.format_exc())
            time.sleep(45)

    def _save_thread(self):
        while self._running:
            try:
                self.broadcast(MessagePacket.make({
                    "player_id": 0,
                    "message": "Autosaving the world..."
                }))

                self.save_world()
                time.sleep(120)
            except:
                logging.error("Autosaving failed")
                logging.debug(traceback.format_exc())

        self.save_world()

    def _keep_alive_thread(self):
        while self._running:
            with self._connections_lock:
                for connection in self._connections.values():
                    try:
                            connection.send(PingPacket.make())
                    except (IOError, BrokenPipeError):
                            self._disconnect(connection)
            time.sleep(30)

    def _connection_thread(self):
        while self._running:
            sock, addr = self._sock.accept()

            with self._connections_lock:
                self._connections[addr] = Connection(self, addr, sock)

    def _flush_thread(self):
        while self._running:
            with self._connections_lock:
                for connection in self._connections.copy().values():
                    try:
                        connection.flush()
                    except (IOError, BrokenPipeError):
                        self._disconnect(connection)

    def broadcast(self, data, ignore=None):
        """
        Broadcasts the data to all of the connected clients, except those listed in the ignore parameter.
        A client is considered connected if it has been associated with a Player object.

        :param data: The data to be sent
        :type data: buffer
        :param ignore: The addresses to ignore
        :type ignore: list
        """
        if not ignore:
            ignore = []
            
        with self._players_lock:
            for player in self._players.copy().values():
                connection = player.connection
                if connection.get_address() not in ignore:
                    try:
                        connection.send(data)
                    except (IOError, BrokenPipeError):
                        self._disconnect(connection)

    def _disconnect(self, connection):

        address = connection.get_address()
        player = None

        if not address:
            logging.debug("Invalid connection, ignoring")
            return

        logging.debug("Disconnecting connection %s" % connection.get_address())

        try:
            connection.close()
        except IOError:
            pass

        if address in self._players_by_address:
            player = self.get_player_by_address(address)

        with self._connections_lock:
            del self._connections[address]

        if player:
            logging.info("Player %s has quit" % player.name)
            del self._players_by_address[address]
            del self._players[player.player_id]
            self.broadcast(DespawnPlayerPacket.make({"player_id": player.player_id}))
            self.broadcast(MessagePacket.make({"player_id": 0, "message": "&e%s&f has quit!" % player.name}))

    def _start(self):
        self.generate_salt()
        self.load_world()
        self._running = True
        threading.Thread(target=self._save_thread).start()
        threading.Thread(target=self._connection_thread).start()
        threading.Thread(target=self._flush_thread).start()
        threading.Thread(target=self._keep_alive_thread).start()
        if self._heartbeat_url:
            threading.Thread(target=self._heartbeat_thread).start()

    def _stop(self):
        self._running = False
        self._sock.close()

    def load_world(self):
        try:
            save = open(self._save_file, "rb").read()
            self._world = World.from_save(save)
            return
        except FileNotFoundError:
            logging.info("Save file not found, creating a new one")
        except (IOError, ValueError) as ex:
            logging.error("Error during loading save file: %s" % repr(ex))
            logging.error(traceback.format_exc())

        self._world = World()

    def save_world(self):
        logging.info("Saving the world...")
        save_file = open(self._save_file, "wb")
        save_file.write(self._world.encode())
        save_file.flush()
        save_file.close()

    def generate_salt(self):
        base_62 = string.ascii_letters + string.digits
        # generate a 16-char salt
        salt = "".join([random.choice(base_62) for _ in range(16)])
        self._salt = salt

    def add_player(self, connection, coordinates, name):
        """
        Adds a player to the server.
        :param connection: The connection of the player
        :type connection: Connection
        :param coordinates: The coordinates the player is located at in the world.
        :type coordinates: list
        :param name: The name of the player.
        :type name: str
        :return: The ID of the newly-created player.
        :rtype: int
        """

        if len(self._players) < self._max_players or self.is_op(name):
            player_id = self._player_id
            if self._player_id in self._players:
                for i in range(256):
                    if i not in self._players:
                        self._player_id = i
                        player_id = i
                        break
                else:
                    raise ValueError("No more ID's left")

            else:
                self._player_id += 1

            player = Player(player_id, connection, coordinates, name, 0x64 if self.is_op(name) else 0x00)
            with self._players_lock:
                self._players[player_id] = player
                self._players_by_address[connection.get_address()] = player
            return player_id
        else:
            logging.warning("Disconnecting player %s because no free slots left." % name)
            connection.send(DisconnectPlayerPacket.make({"reason": "Server full"}))

    def kick_player(self, player_id, reason):
        """
        Kicks the player given an ID.
        :param player_id: The ID of the target player.
        :type player_id: int
        :param reason: The reason to be reported to the player.
        :type reason: str
        """

        player = self._players[player_id]
        logging.info("Kicking player %s for %s" % (player.name, reason))
        player.connection.send(DisconnectPlayerPacket.make({"reason": reason}))
        with self._players_lock:
            del self._players[player_id]
        self.broadcast(MessagePacket.make({"player_id": 0, "message": "Player %s kicked, %s" % (player.name, reason)}))
        self._disconnect(player.connection)

    def is_op(self, player_name):
        """
        Check the op privileges of a given player.
        :param player_name: The name of the player
        :type player_name: str
        :return: True if player is op, otherwise False
        :rtype: bool
        """

        if player_name in self._op_players:
            return True
        else:
            return False

    def get_name(self):
        """
        Gets the name parameter of the server.
        :return: The name of the server.
        :rtype: str
        """
        return self._server_name

    def get_motd(self):
        """
        Gets the MOTD (message of the day) parameter of the server.
        :return: The message of the day.
        :rtype: str
        """
        return self._motd

    def get_player(self, player_id):
        """
        Gets a player by ID.

        :param player_id: The ID of the player.
        :type player_id: int
        :return: The player
        :rtype: Player
        """

        return self._players[player_id]

    def get_player_by_address(self, address):
        """
        Gets a player by address.

        :param address: The address of the player.
        :type address: tuple
        :return: The player
        :rtype: Player
        """
        return self._players_by_address[address]

    def get_players(self):
        """
        Returns a copy of the internal players array.
        :return: The copy of the players array.
        :rtype: list
        """
        with self._players_lock:
            players_copy = self._players.copy()
        return players_copy

    def get_world(self):
        """
        Returns the current server world.
        :return: The server world.
        :rtype: World
        """

        return self._world

    def get_salt(self):
        """
        Returns the server salt.

        :return: The server salt.
        :rtype: str
        """
        return self._salt

    def __exit__(self):
        self._stop()
Example #4
0
class ClassicServer(object):
    MTU = 1024

    _bind_address = None
    _running = None
    _sock = None

    _packet_handler = None

    _connections = {}

    _players = {}
    _players_by_address = {}

    _connections_lock = None
    _players_lock = None

    _player_id = 0

    _server_name = ""
    _motd = ""

    _save_file = ""
    _heartbeat_url = ""
    _salt = ""

    _op_players = []
    _max_players = -1

    _world = None

    def __init__(self, config):
        # bind_address, server_name="", motd="", save_file="", heartbeat_url="", op_players=None, max_players=32
        self._bind_address = ("0.0.0.0", int(config["server"]["port"]))
        self._running = False
        self._server_name = config["server"]["name"]
        self._motd = config["server"]["motd"]
        self._save_file = config["save"]["file"]
        self._heartbeat_url = config["heartbeat_url"]
        self._op_players = config["server"]["ops"]
        self._max_players = config["server"]["max_players"]

        if self._max_players > 255:
            raise ValueError(
                "The player limit is up to 255 excluding the admin slot.")

        logging.basicConfig(level=logging.DEBUG)

        self._connections_lock = threading.RLock()
        self._players_lock = threading.RLock()

        self._packet_handler = PacketHandler(self)

        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self._sock.bind(self._bind_address)

        self._sock.listen(1)
        self._start()

    def data_hook(self, connection, data):
        """
        Data hook, to report received data.
        :param connection: The connection from which the data originates from.
        :type connection: Connection
        :param data: The received data.
        :type data: buffer
        """

        try:
            self._packet_handler.handle_packet(connection, data)
        except Exception as ex:
            logging.error("Error in packet handler: %s" % repr(ex))
            logging.debug(traceback.format_exc())

    def _heartbeat_thread(self):
        while self._running:
            try:
                f = urllib.request.urlopen(self._heartbeat_url + (
                    "?port=%d&max=%d&name=%s&public=True&version=7&salt=%s&users=%d"
                    % (self._bind_address[1], self._max_players,
                       urllib.parse.quote(self._server_name, safe=""),
                       self._salt, len(self._players))))

                data = f.read()

                logging.debug("Heartbeat sent, json response: %s" %
                              data.decode("utf-8"))

            except BaseException as ex:
                logging.error("Heartbeat failed: %s" % repr(ex))
                logging.debug(traceback.format_exc())
            time.sleep(45)

    def _save_thread(self):
        while self._running:
            try:
                self.broadcast(
                    MessagePacket.make({
                        "player_id": 0,
                        "message": "Autosaving the world..."
                    }))

                self.save_world()
                time.sleep(120)
            except:
                logging.error("Autosaving failed")
                logging.debug(traceback.format_exc())

        self.save_world()

    def _keep_alive_thread(self):
        while self._running:
            with self._connections_lock:
                for connection in self._connections.values():
                    try:
                        connection.send(PingPacket.make())
                    except (IOError, BrokenPipeError):
                        self._disconnect(connection)
            time.sleep(30)

    def _connection_thread(self):
        while self._running:
            sock, addr = self._sock.accept()

            with self._connections_lock:
                self._connections[addr] = Connection(self, addr, sock)

    def _flush_thread(self):
        while self._running:
            with self._connections_lock:
                for connection in self._connections.copy().values():
                    try:
                        connection.flush()
                    except (IOError, BrokenPipeError):
                        self._disconnect(connection)

    def broadcast(self, data, ignore=None):
        """
        Broadcasts the data to all of the connected clients, except those listed in the ignore parameter.
        A client is considered connected if it has been associated with a Player object.

        :param data: The data to be sent
        :type data: buffer
        :param ignore: The addresses to ignore
        :type ignore: list
        """
        if not ignore:
            ignore = []

        with self._players_lock:
            for player in self._players.copy().values():
                connection = player.connection
                if connection.get_address() not in ignore:
                    try:
                        connection.send(data)
                    except (IOError, BrokenPipeError):
                        self._disconnect(connection)

    def _disconnect(self, connection):

        address = connection.get_address()
        player = None

        if not address:
            logging.debug("Invalid connection, ignoring")
            return

        logging.debug("Disconnecting connection %s" % connection.get_address())

        try:
            connection.close()
        except IOError:
            pass

        if address in self._players_by_address:
            player = self.get_player_by_address(address)

        with self._connections_lock:
            del self._connections[address]

        if player:
            logging.info("Player %s has quit" % player.name)
            del self._players_by_address[address]
            del self._players[player.player_id]
            self.broadcast(
                DespawnPlayerPacket.make({"player_id": player.player_id}))
            self.broadcast(
                MessagePacket.make({
                    "player_id": 0,
                    "message": "&e%s&f has quit!" % player.name
                }))

    def _start(self):
        self.generate_salt()
        self.load_world()
        self._running = True
        threading.Thread(target=self._save_thread).start()
        threading.Thread(target=self._connection_thread).start()
        threading.Thread(target=self._flush_thread).start()
        threading.Thread(target=self._keep_alive_thread).start()
        if self._heartbeat_url:
            threading.Thread(target=self._heartbeat_thread).start()

    def _stop(self):
        self._running = False
        self._sock.close()

    def load_world(self):
        try:
            save = open(self._save_file, "rb").read()
            self._world = World.from_save(save)
            return
        except FileNotFoundError:
            logging.info("Save file not found, creating a new one")
        except (IOError, ValueError) as ex:
            logging.error("Error during loading save file: %s" % repr(ex))
            logging.error(traceback.format_exc())

        self._world = World()

    def save_world(self):
        logging.info("Saving the world...")
        save_file = open(self._save_file, "wb")
        save_file.write(self._world.encode())
        save_file.flush()
        save_file.close()

    def generate_salt(self):
        base_62 = string.ascii_letters + string.digits
        # generate a 16-char salt
        salt = "".join([random.choice(base_62) for _ in range(16)])
        self._salt = salt

    def add_player(self, connection, coordinates, name):
        """
        Adds a player to the server.
        :param connection: The connection of the player
        :type connection: Connection
        :param coordinates: The coordinates the player is located at in the world.
        :type coordinates: list
        :param name: The name of the player.
        :type name: str
        :return: The ID of the newly-created player.
        :rtype: int
        """

        if len(self._players) < self._max_players or self.is_op(name):
            player_id = self._player_id
            if self._player_id in self._players:
                for i in range(256):
                    if i not in self._players:
                        self._player_id = i
                        player_id = i
                        break
                else:
                    raise ValueError("No more ID's left")

            else:
                self._player_id += 1

            player = Player(player_id, connection, coordinates, name,
                            0x64 if self.is_op(name) else 0x00)
            with self._players_lock:
                self._players[player_id] = player
                self._players_by_address[connection.get_address()] = player
            return player_id
        else:
            logging.warning(
                "Disconnecting player %s because no free slots left." % name)
            connection.send(
                DisconnectPlayerPacket.make({"reason": "Server full"}))

    def kick_player(self, player_id, reason):
        """
        Kicks the player given an ID.
        :param player_id: The ID of the target player.
        :type player_id: int
        :param reason: The reason to be reported to the player.
        :type reason: str
        """

        player = self._players[player_id]
        logging.info("Kicking player %s for %s" % (player.name, reason))
        player.connection.send(DisconnectPlayerPacket.make({"reason": reason}))
        with self._players_lock:
            del self._players[player_id]
        self.broadcast(
            MessagePacket.make({
                "player_id":
                0,
                "message":
                "Player %s kicked, %s" % (player.name, reason)
            }))
        self._disconnect(player.connection)

    def is_op(self, player_name):
        """
        Check the op privileges of a given player.
        :param player_name: The name of the player
        :type player_name: str
        :return: True if player is op, otherwise False
        :rtype: bool
        """

        if player_name in self._op_players:
            return True
        else:
            return False

    def get_name(self):
        """
        Gets the name parameter of the server.
        :return: The name of the server.
        :rtype: str
        """
        return self._server_name

    def get_motd(self):
        """
        Gets the MOTD (message of the day) parameter of the server.
        :return: The message of the day.
        :rtype: str
        """
        return self._motd

    def get_player(self, player_id):
        """
        Gets a player by ID.

        :param player_id: The ID of the player.
        :type player_id: int
        :return: The player
        :rtype: Player
        """

        return self._players[player_id]

    def get_player_by_address(self, address):
        """
        Gets a player by address.

        :param address: The address of the player.
        :type address: tuple
        :return: The player
        :rtype: Player
        """
        return self._players_by_address[address]

    def get_players(self):
        """
        Returns a copy of the internal players array.
        :return: The copy of the players array.
        :rtype: list
        """
        with self._players_lock:
            players_copy = self._players.copy()
        return players_copy

    def get_world(self):
        """
        Returns the current server world.
        :return: The server world.
        :rtype: World
        """

        return self._world

    def get_salt(self):
        """
        Returns the server salt.

        :return: The server salt.
        :rtype: str
        """
        return self._salt

    def __exit__(self):
        self._stop()
Example #5
0
class ClassicServer(object):
    MTU = 1024

    _bind_address = None
    _running = None
    _sock = None

    _packet_handler = None

    _connections = {}

    _players = {}
    _players_by_address = {}

    _connections_lock = None
    _players_lock = None

    _player_id = 0

    _server_name = ""
    _motd = ""

    _save_file = ""
    _heartbeat_url = ""
    _salt = ""

    _op_players = []
    _max_players = -1

    _world = None

    def __init__(self, config):
        # bind_address, server_name="", motd="", save_file="", heartbeat_url="", op_players=None, max_players=32
        self._bind_address = ("0.0.0.0", config["server"]["port"])
        self._running = False
        self._server_name = config["server"]["name"]
        self._motd = config["server"]["motd"]
        self._save_file = config["save"]["file"]
        self._heartbeat_url = config["heartbeat_url"]
        self._op_players = config["server"]["ops"]
        self._max_players = config["server"]["max_players"]

        if self._max_players > 255:
            raise ValueError("The player limit is up to 255 excluding the admin slot.")

        logging.basicConfig(level=logging.DEBUG)

        self._connections_lock = threading.RLock()
        self._players_lock = threading.RLock()

        self._packet_handler = PacketHandler(self)

        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._sock.bind(self._bind_address)

        self._sock.listen(1)
        self._start()

    def data_hook(self, client, data):
        try:
            self._packet_handler.handle_packet(client, data)
        except Exception as ex:
            logging.error("Error in packet handler: %s" % repr(ex))
            logging.debug(traceback.format_exc())

    def _heartbeat_thread(self):
        while self._running:
            try:
                f = urllib.request.urlopen(self._heartbeat_url + (
                    "?port=%d&max=%d&name=%s&public=True&version=7&salt=%s&users=%d" % (
                        self._bind_address[1], self._max_players,
                        urllib.parse.quote(self._server_name, safe=""), self._salt, len(self._players))
                ))

                data = f.read()

                logging.debug("Heartbeat sent, json response: %s" % data.decode("utf-8"))

            except BaseException as ex:
                logging.error("Heartbeat failed: %s" % repr(ex))
                logging.debug(traceback.format_exc())
            time.sleep(45)

    def _save_thread(self):
        while self._running:
            try:
                self.broadcast(MessagePacket.make({
                    "player_id": 0,
                    "message": "Autosaving the world..."
                }))
                self.save_world()
                time.sleep(120)
            except BaseException as ex:
                logging.error("Autosaving failed: %s" % repr(ex))
                logging.debug(traceback.format_exc())

        self.save_world()

    def _keep_alive_thread(self):
        while self._running:
            logging.info("KEEP ALIVE THREAD: Acquiring connection lock")
            with self._connections_lock:
                logging.info("KEEP ALIVE: connection lock acquired")
                for connection in self._connections.values():
                    try:
                            connection.send(PingPacket.make())
                    except (IOError, BrokenPipeError):
                            self._disconnect(connection)
            logging.info("KEEP ALIVE: connection lock released")
            time.sleep(30)

    def _connection_thread(self):
        while self._running:
            sock, addr = self._sock.accept()

            logging.info("CONNECTION THREAD: Acquiring connection lock")
            with self._connections_lock:
                logging.info("CONNECTION THREAD: connection lock acquired")
                self._connections[addr] = Connection(self, addr, sock)
            logging.info("CONNECTION THREAD: connection lock released")

    def _flush_thread(self):
        while self._running:
            logging.info("FLUSH THREAD: Acquiring connection lock")
            with self._connections_lock:
                logging.info("FLUSH THREAD: connection lock acquired")
                for connection in self._connections.copy().values():
                    try:
                        connection.flush()
                    except (IOError, BrokenPipeError):
                        self._disconnect(connection)
            logging.info("FLUSH THREAD: connection lock released")
            time.sleep(0.3)

    def broadcast(self, data, ignore=None):
        if not ignore:
            ignore = []

        logging.info("BROADCAST: Acquiring player lock")
        with self._players_lock:
            logging.info("BROADCAST: player lock acquired")
            for player in self._players.values():
                connection = player.connection
                if connection.get_address() not in ignore:
                    try:
                        connection.send(data)
                    except (IOError, BrokenPipeError):
                        self._disconnect(connection)
        logging.info("BROADCAST: player lock released")

    def _disconnect(self, connection):
        player = None

        if connection.get_address() in self._players_by_address:
            player = self.get_player_by_address(connection.get_address())

        logging.info("DISCONNECT: Acquiring connection lock")
        with self._connections_lock:
            logging.info("DISCONNECT: connection lock acquired")
            del self._connections[connection.get_address()]
        logging.info("DISCONNECT: connection lock released")

        if player:
            logging.info("Player %s has quit" % player.name)
            del self._players_by_address[connection.get_address()]
            del self._players[player.player_id]
            self.broadcast(DespawnPlayerPacket.make({"player_id": player.player_id}))
            self.broadcast(MessagePacket.make({"player_id": 0, "message": "&e%s&f has quit!" % player.name}))

    def _start(self):
        self.generate_salt()
        self.load_world()
        self._running = True
        threading.Thread(target=self._save_thread).start()
        threading.Thread(target=self._connection_thread).start()
        threading.Thread(target=self._flush_thread).start()
        threading.Thread(target=self._keep_alive_thread).start()
        if self._heartbeat_url:
            threading.Thread(target=self._heartbeat_thread).start()

    def _stop(self):
        self._running = False
        self._sock.close()

    def load_world(self):
        try:
            save = open(self._save_file, "rb").read()
            self._world = World.from_save(save)
            return
        except FileNotFoundError:
            logging.info("Save file not found, creating a new one")
        except IOError as ex:
            logging.error("Error during loading save file: %s" % repr(ex))
            logging.error(traceback.format_exc())

        self._world = World()

    def save_world(self):
        logging.info("Saving the world...")
        save_file = open(self._save_file, "wb")
        save_file.write(self._world.encode())
        save_file.flush()
        save_file.close()

    def generate_salt(self):
        base_62 = string.ascii_letters + string.digits
        # generate a 16-char salt
        salt = "".join([random.choice(base_62) for _ in range(16)])
        self._salt = salt

    def add_player(self, connection, coordinates, name):
        """

        :type connection: Connection
        """
        if len(self._players) < self._max_players or self.is_op(name):
            player_id = self._player_id
            if self._player_id in self._players:
                for i in range(256):
                    if i not in self._players:
                        self._player_id = i
                        player_id = i
                        break
                else:
                    raise ValueError("No more ID's left")

            else:
                self._player_id += 1

            player = Player(player_id, connection, coordinates, name, 0x64 if self.is_op(name) else 0x00)
            logging.info("ADD PLAYER: acquiring player lock")
            with self._players_lock:
                logging.info("ADD PLAYER: player lock acquired")
                self._players[player_id] = player
                self._players_by_address[connection.get_address()] = player
            logging.info("ADD PLAYER: player lock released")
            return player_id
        else:
            logging.warning("Disconnecting player %s because no free slots left." % name)
            connection.send(DisconnectPlayerPacket.make({"reason": "Server full"}))

    def kick_player(self, player_id, reason):
        player = self._players[player_id]
        logging.info("Kicking player %s for %s" % (player.name, reason))
        player.connection.send(DisconnectPlayerPacket.make({"reason": reason}))

        logging.info("KICK PLAYER: acquiring player lock")
        with self._players_lock:
            logging.info("KICK PLAYER: player lock acquired")
            del self._players[player_id]
        logging.info("KICK PLAYER: player lock released")

        self.broadcast(MessagePacket.make({"player_id": 0, "message": "Player %s kicked, %s" % (player.name, reason)}))
        self._disconnect(player.connection)

    def is_op(self, player_name):
        if player_name in self._op_players:
            return True
        else:
            return False

    def get_name(self):
        return self._server_name

    def get_motd(self):
        return self._motd

    def get_player(self, player_id):
        return self._players[player_id]

    def get_player_by_address(self, address):
        return self._players_by_address[address]

    def get_players(self):
        logging.info("GET PLAYERS: acquiring player lock")
        with self._players_lock:
            logging.info("GET PLAYERS: player lock acquired")
            players_copy = self._players.copy()
        logging.info("GET PLAYERS: player lock released")
        return players_copy

    def get_world(self):
        return self._world

    def get_salt(self):
        return self._salt

    def __exit__(self):
        self._stop()