예제 #1
0
    def __init__(self, *arg, **kw):
        # +2 to allow server->master and master->server connection since enet
        # allocates peers for both clients and hosts. this is done at
        # enet-level, not application-level, so even for masterless-servers,
        # this should not allow additional players.
        self.max_connections = self.max_players + 2
        BaseProtocol.__init__(self, *arg, **kw)
        self.entities = []
        self.players = MultikeyDict()
        self.player_ids = IDPool()

        self._create_teams()

        self.world = world.World()
        self.set_master()

        # safe position LUT
        #
        # Generates a LUT to check for safe positions. The slighly weird
        # sorting is used to sort by increasing distance so the nearest spots
        # get chosen first
        # product(repeat=3) is the equivalent of 3 nested for loops
        self.pos_table = list(product(range(-5, 6), repeat=3))

        self.pos_table.sort(key=lambda vec: abs(vec[0] * 1.03) + abs(vec[
            1] * 1.02) + abs(vec[2] * 1.01))
예제 #2
0
    def test_misc_funcs(self):
        dic = MultikeyDict()
        dic[1, 'bar'] = 2
        dic[3, 'baz'] = 5

        self.assertEqual(len(dic), 2)
        del dic[1]
        self.assertEqual(len(dic), 1)

        dic.clear()
        self.assertEqual(len(dic), 0)
예제 #3
0
 def set_map(self, map_obj):
     self.map = map_obj
     self.world.map = map_obj
     self.on_map_change(map_obj)
     self.team_1.initialize()
     self.team_2.initialize()
     if self.game_mode == TC_MODE:
         self.reset_tc()
     self.players = MultikeyDict()
     if self.connections:
         data = ProgressiveMapGenerator(self.map, parent=True)
         for connection in list(self.connections.values()):
             if connection.player_id is None:
                 continue
             if connection.map_data is not None:
                 connection.disconnect()
                 continue
             connection.reset()
             connection._send_connection_data()
             connection.send_map(data.get_child())
     self.update_entities()
예제 #4
0
    def __init__(self, protocol):
        BaseConnection.__init__(self)
        self.protocol = protocol
        self.auth_val = random.randint(0, 0xFFFF)
        self.map = ByteWriter()
        self.connections = MultikeyDict()
        self.spammy = {
            Ping: 0,
            loaders.MapChunk: 0,
            loaders.OrientationData: 0,
            loaders.PositionData: 0,
            loaders.InputData: 0
        }

        connect_request = ConnectionRequest()
        connect_request.auth_val = self.auth_val
        connect_request.client = True
        connect_request.version = self.get_version()
        self.send_loader(connect_request, False, 255)
예제 #5
0
class ServerProtocol(BaseProtocol):
    connection_class = ServerConnection

    name = 'pyspades server'
    game_mode = CTF_MODE
    max_players = 32
    connections = None
    player_ids = None
    master = False
    max_score = 10
    map = None
    spade_teamkills_on_grief = False
    friendly_fire = False
    friendly_fire_time = 2
    server_prefix = '[*] '
    respawn_time = 5
    refill_interval = 20
    master_connection = None
    speedhack_detect = True
    fog_color = (128, 232, 255)
    winning_player = None
    world = None
    team_class = Team
    team1_color = (0, 0, 196)
    team2_color = (0, 196, 0)
    team1_name = 'Blue'
    team2_name = 'Green'
    spectator_name = 'Spectator'
    loop_count = 0
    melee_damage = 100
    version = GAME_VERSION
    respawn_waves = False

    def __init__(self, *arg, **kw):
        # +2 to allow server->master and master->server connection since enet
        # allocates peers for both clients and hosts. this is done at
        # enet-level, not application-level, so even for masterless-servers,
        # this should not allow additional players.
        self.max_connections = self.max_players + 2
        BaseProtocol.__init__(self, *arg, **kw)
        self.entities = []
        self.players = MultikeyDict()
        self.player_ids = IDPool()

        self._create_teams()

        self.world = world.World()
        self.set_master()

        # safe position LUT
        #
        # Generates a LUT to check for safe positions. The slighly weird
        # sorting is used to sort by increasing distance so the nearest spots
        # get chosen first
        # product(repeat=3) is the equivalent of 3 nested for loops
        self.pos_table = list(product(range(-5, 6), repeat=3))

        self.pos_table.sort(key=lambda vec: abs(vec[0] * 1.03) + abs(vec[
            1] * 1.02) + abs(vec[2] * 1.01))

    def _create_teams(self):
        """create the teams
        This Method is separate to simplify unit testing
        """
        self.team_spectator = self.team_class(-1, self.spectator_name,
                                              (0, 0, 0), True, self)
        self.team_1 = self.team_class(0, self.team1_name, self.team1_color,
                                      False, self)
        self.team_2 = self.team_class(1, self.team2_name, self.team2_color,
                                      False, self)
        self.teams = {-1: self.team_spectator, 0: self.team_1, 1: self.team_2}
        self.team_1.other = self.team_2
        self.team_2.other = self.team_1

    @property
    def blue_team(self):
        """alias to team_1 for backwards-compatibility"""
        return self.team_1

    @property
    def green_team(self):
        """alias to team_2 for backwards-compatibility"""
        return self.team_2

    @property
    def spectator_team(self):
        """alias to team_spectator for backwards-compatibility"""
        return self.team_spectator

    def broadcast_contained(self,
                            contained,
                            unsequenced=False,
                            sender=None,
                            team=None,
                            save=False,
                            rule=None):
        """send a Contained `Loader` to all or a selection of connected
        players

        Parameters:
            contained: the `Loader` object to send
            unsequenced: set the enet ``UNSEQUENCED`` flag on this packet
            sender: if set to a connection object, do not send this packet to
                that player, as they are the sender.
            team: if set to a team, only send the packet to that team
            save: if the player has not downloaded the map yet, save this
                packet and send it when the map transfer has completed
            rule: if set to a callable, this function is called with the player
                as parameter to determine if a given player should receive the
                packet
        """
        if unsequenced:
            flags = enet.PACKET_FLAG_UNSEQUENCED
        else:
            flags = enet.PACKET_FLAG_RELIABLE
        writer = ByteWriter()
        contained.write(writer)
        data = bytes(writer)
        packet = enet.Packet(data, flags)
        for player in self.connections.values():
            if player is sender or player.player_id is None:
                continue
            if team is not None and player.team is not team:
                continue
            if rule is not None and not rule(player):
                continue
            if player.saved_loaders is not None:
                if save:
                    player.saved_loaders.append(data)
            else:
                player.peer.send(0, packet)

    # backwards compatability
    send_contained = broadcast_contained

    def reset_tc(self):
        self.entities = self.get_cp_entities()
        for entity in self.entities:
            team = entity.team
            if team is None:
                entity.progress = 0.5
            else:
                team.score += 1
                entity.progress = float(team.id)
        tc_data.set_entities(self.entities)
        self.max_score = len(self.entities)

    def get_cp_entities(self):
        # cool algorithm number 1
        entities = []
        land_count = self.map.count_land(0, 0, 512, 512)
        territory_count = int((land_count / (512.0 * 512.0)) *
                              (MAX_TERRITORY_COUNT - MIN_TERRITORY_COUNT) +
                              MIN_TERRITORY_COUNT)
        j = 512.0 / territory_count
        for i in range(territory_count):
            x1 = i * j
            y1 = 512 / 4
            x2 = (i + 1) * j
            y2 = y1 * 3
            flag = Territory(i, self,
                             *self.get_random_location(zone=(x1, y1, x2, y2)))
            if i < territory_count / 2:
                team = self.team_1
            elif i > (territory_count - 1) / 2:
                team = self.team_2
            else:
                # odd number - neutral
                team = None
            flag.team = team
            entities.append(flag)
        return entities

    def update(self):
        self.loop_count += 1
        BaseProtocol.update(self)
        for player in self.connections.values():
            if (player.map_data is not None
                    and not player.peer.reliableDataInTransit):
                player.continue_map_transfer()
        self.world.update(UPDATE_FREQUENCY)
        self.on_world_update()
        if self.loop_count % int(UPDATE_FPS / NETWORK_FPS) == 0:
            self.update_network()

    def update_network(self):
        items = []
        highest_player_id = 0
        for i in range(32):
            position = orientation = None
            try:
                player = self.players[i]
                highest_player_id = i
                if (not player.filter_visibility_data
                        and not player.team.spectator):
                    world_object = player.world_object
                    position = world_object.position.get()
                    orientation = world_object.orientation.get()
            except (KeyError, TypeError, AttributeError):
                pass
            if position is None:
                position = (0.0, 0.0, 0.0)
                orientation = (0.0, 0.0, 0.0)
            items.append((position, orientation))
        world_update = loaders.WorldUpdate()
        # we only want to send as many items of the player list as needed, so
        # we slice it off at the highest player id
        world_update.items = items[:highest_player_id + 1]
        self.send_contained(world_update, unsequenced=True)

    def set_map(self, map_obj):
        self.map = map_obj
        self.world.map = map_obj
        self.on_map_change(map_obj)
        self.team_1.initialize()
        self.team_2.initialize()
        if self.game_mode == TC_MODE:
            self.reset_tc()
        self.players = MultikeyDict()
        if self.connections:
            data = ProgressiveMapGenerator(self.map, parent=True)
            for connection in list(self.connections.values()):
                if connection.player_id is None:
                    continue
                if connection.map_data is not None:
                    connection.disconnect()
                    continue
                connection.reset()
                connection._send_connection_data()
                connection.send_map(data.get_child())
        self.update_entities()

    def reset_game(self, player=None, territory=None):
        """reset the score of the game

        player is the player which should be awarded the necessary captures to
        end the game
        """
        self.team_1.initialize()
        self.team_2.initialize()
        if self.game_mode == CTF_MODE:
            if player is None:
                player = list(self.players.values())[0]
            intel_capture = loaders.IntelCapture()
            intel_capture.player_id = player.player_id
            intel_capture.winning = True
            self.send_contained(intel_capture, save=True)
        elif self.game_mode == TC_MODE:
            if territory is None:
                territory = self.entities[0]
            territory_capture = loaders.TerritoryCapture()
            territory_capture.object_index = territory.id
            territory_capture.winning = True
            territory_capture.state = territory.team.id
            self.send_contained(territory_capture)
            self.reset_tc()
        for entity in self.entities:
            entity.update()
        for player in self.players.values():
            if player.team is not None:
                player.spawn()

    def get_name(self, name):
        '''
        Sanitizes `name` and modifies it so that it doesn't collide with other names connected to the server.

        Returns the fixed name.
        '''
        name = name.replace('%', '')
        new_name = name
        names = [p.name.lower() for p in self.players.values()]
        i = 0
        while new_name.lower() in names:
            i += 1
            new_name = name + str(i)
        return new_name

    def get_mode_mode(self):
        if self.game_mode == CTF_MODE:
            return 'ctf'
        elif self.game_mode == TC_MODE:
            return 'tc'
        return 'unknown'

    def get_random_location(self, force_land=True, zone=(0, 0, 512, 512)):
        x1, y1, x2, y2 = zone
        if force_land:
            x, y = self.map.get_random_point(x1, y1, x2, y2)
        else:
            x = random.randrange(x1, x2)
            y = random.randrange(y1, y2)
        z = self.map.get_z(x, y)
        return x, y, z

    def set_master(self):
        if self.master:
            get_master_connection(self).addCallbacks(
                self.got_master_connection, self.master_disconnected)

    def got_master_connection(self, connection):
        self.master_connection = connection
        connection.disconnect_callback = self.master_disconnected
        self.update_master()

    def master_disconnected(self, client=None):
        self.master_connection = None

    def get_player_count(self):
        count = 0
        for connection in self.connections.values():
            if connection.player_id is not None:
                count += 1
        return count

    def update_master(self):
        if self.master_connection is None:
            return
        self.master_connection.set_count(self.get_player_count())

    def update_entities(self):
        map_obj = self.map
        for entity in self.entities:
            moved = False
            if map_obj.get_solid(entity.x, entity.y, entity.z - 1):
                moved = True
                entity.z -= 1
                # while solid in block above (ie. in the space in which the
                # entity is sitting), move entity up)
                while map_obj.get_solid(entity.x, entity.y, entity.z - 1):
                    entity.z -= 1
            else:
                # get_solid can return None, so a more specific check is used
                while map_obj.get_solid(entity.x, entity.y, entity.z) is False:
                    moved = True
                    entity.z += 1
            if moved or self.on_update_entity(entity):
                entity.update()

    def broadcast_chat(self,
                       message,
                       global_message=None,
                       sender=None,
                       team=None):
        for player in self.players.values():
            if player is sender:
                continue
            if player.deaf:
                continue
            if team is not None and player.team is not team:
                continue
            player.send_chat(message, global_message)

    # backwards compatability
    send_chat = broadcast_chat

    def broadcast_chat_warning(self, message, team=None):
        """
        Send a warning message. This gets displayed
        as a yellow popup with sound for OpenSpades
        clients
        """
        self.send_chat(self, "%% " + str(message), team=team)

    def broadcast_chat_notice(self, message, team=None):
        """
        Send a warning message. This gets displayed
        as a popup for OpenSpades
        clients
        """
        self.send_chat(self, "N% " + str(message), team=team)

    def broadcast_chat_error(self, message, team=None):
        """
        Send a warning message. This gets displayed
        as a red popup with sound for OpenSpades
        clients
        """
        self.send_chat(self, "!% " + str(message), team=team)

    def broadcast_chat_status(self, message, team=None):
        """
        Send a warning message. This gets displayed
        as a red popup with sound for OpenSpades
        clients
        """
        self.send_chat(self, "C% " + str(message), team=team)

    def set_fog_color(self, color):
        self.fog_color = color
        fog_color = loaders.FogColor()
        fog_color.color = make_color(*color)
        self.send_contained(fog_color, save=True)

    def get_fog_color(self):
        return self.fog_color

    # events

    def on_cp_capture(self, cp):
        pass

    def on_game_end(self):
        pass

    def on_world_update(self):
        pass

    def on_map_change(self, map_):
        pass

    def on_base_spawn(self, x, y, z, base, entity_id):
        pass

    def on_flag_spawn(self, x, y, z, flag, entity_id):
        pass

    def on_update_entity(self, entity):
        pass
예제 #6
0
 def test_get(self):
     dic = MultikeyDict()
     dic[7, 'egg'] = 42
     self.assertEqual(dic.get(7), 42)
     self.assertEqual(dic.get("egg"), 42)
     self.assertEqual(dic.get("spam", "def"), "def")
예제 #7
0
 def test_assign_multiple(self):
     dic = MultikeyDict()
     dic[1, 'bar'] = 2
     with self.assertRaises(KeyError):
         dic[3, 'bar'] = 5
예제 #8
0
 def test_identity(self):
     dic = MultikeyDict()
     lst = ("hi", )
     dic["key", ("tup", "le")] = lst
     self.assertIs(dic["key"], lst)
     self.assertIs(dic["tup", "le"], lst)
예제 #9
0
 def test_create(self):
     dic = MultikeyDict()
     dic[1, 'bar'] = 2
     self.assertEqual(dic[1], 2)
     self.assertIs(dic[1], dic['bar'])
 def __init__(self, *args, **kwargs):
     self._buttons = MultikeyDict()
     self._platforms = {}
     self._distance_triggers = set()
     self._autosave_loop = LoopingCall(self.dump_platform_json)
     protocol.__init__(self, *args, **kwargs)
    class PlatformProtocol(protocol):
        _next_id = 0

        def __init__(self, *args, **kwargs):
            self._buttons = MultikeyDict()
            self._platforms = {}
            self._distance_triggers = set()
            self._autosave_loop = LoopingCall(self.dump_platform_json)
            protocol.__init__(self, *args, **kwargs)

        def add_distance_trigger(self, trigger):
            self._distance_triggers.add(trigger)
            trigger.signal_remove.connect(self._distance_triggers.remove)

        def update_distance_triggers(self, player):
            for trigger in self._distance_triggers:
                trigger.update(player)

        def on_world_update(self):
            for player in self.players.values():
                self.update_distance_triggers(player)
            protocol.on_world_update(self)

        def assign_id(self):
            id_ = self._next_id
            self._next_id += 1
            return id_

        def add_button(self, button):
            self._buttons[(button.id, button.location)] = button

        def create_button(self, location, color, label):
            if self.is_platform_or_button(location):
                return None
            button = Button(self, self._next_id, location, color, label)
            button.add_trigger(PressTrigger(self, False, button))
            self._buttons[(self._next_id, location)] = button
            self._next_id += 1
            return button

        def destroy_button(self, button):
            button.destroy()
            del self._buttons[button]
            for player in self.players.values(
            ):  # clear last button memory from players
                if player.last_button is button:
                    player.last_button = None

        def create_platform(self, location1, location2, z, color, label):
            for location in prism_range(*location1, z, *location2, z + 1):
                if self.is_platform_or_button(location):
                    return None
            platform = Platform(self, self._next_id, location1, location2, z,
                                color, label)
            self._platforms[self._next_id] = platform
            self._next_id += 1
            return platform

        def destroy_platform(self, platform):
            platform.destroy()
            del self._platforms[platform.id]
            for player in self.players.values(
            ):  # clear last platform memory from players
                if player.last_platform is platform:
                    player.last_platform = None

        def get_platform(self, location_or_id):
            if location_or_id in self._platforms:
                return self._platforms[location_or_id]
            for platform in self._platforms.values():
                if platform.contains(location_or_id):
                    return platform

        def get_button(self, location_or_id):
            if location_or_id in self._buttons:
                return self._buttons[location_or_id]

        def is_platform_or_button(self, location):
            return self.get_platform(location) or location in self._buttons

        def on_map_change(self, map):
            self._next_id = 0
            self._platforms.clear()
            self._buttons.clear()
            self._distance_triggers.clear()
            self.load_platform_json()
            if AUTOSAVE_EVERY:
                self._autosave_loop.start(AUTOSAVE_EVERY * 60.0, now=False)
            protocol.on_map_change(self, map)

        def on_map_leave(self):
            if SAVE_ON_MAP_CHANGE:
                self.dump_platform_json()
            if self._autosave_loop.running:
                self._autosave_loop.stop()
            protocol.on_map_leave(self)

        def _get_platform_json_path(self):
            filename = self.map_info.rot_info.full_name + '_platform.txt'
            return os.path.join(DEFAULT_LOAD_DIR, filename)

        def load_platform_json(self):
            path = self._get_platform_json_path()
            if not os.path.isfile(path):
                return
            with open(path, 'r') as file:
                data = json.load(file)
            ids = []
            for platform_data in data['platforms']:
                platform = Platform.unserialize(self, platform_data)
                self._platforms[platform.id] = platform
                ids.append(platform.id)
            for button_data in data['buttons']:
                button = Button.unserialize(self, button_data)
                self._buttons[(button.id, button.location)] = button
                ids.append(button.id)
            self._next_id = max(ids) + 1 if ids else 0
            for button in self._buttons.values():
                button.trigger_check()

        def dump_platform_json(self):
            data = {
                'platforms': [
                    platform.serialize()
                    for platform in self._platforms.values()
                ],
                'buttons':
                [button.serialize() for button in self._buttons.values()]
            }
            path = self.get_platform_json_path()
            with open(path, 'w') as file:
                json.dump(data, file, indent=4)