示例#1
0
        def add_to_shoutlog(self, client: ClientManager.Client, msg: str):
            """
            Add a shout message to the shout log of the area.

            Parameters
            ----------
            client: ClientManager.Client
                Client to record.
            msg: str
                Shout message to record.
            """

            if len(self.shoutlog) >= 20:
                self.shoutlog = self.shoutlog[1:]

            info = '{} | [{}] {} ({}) {}'.format(Constants.get_time(),
                                                 client.id, client.displayname,
                                                 client.get_ip(), msg)
            self.shoutlog.append(info)
示例#2
0
        def add_to_judgelog(self, client, msg):
            """
            Add a judge action to the judge log of the area.

            Parameters
            ----------
            client: server.ClientManager.Client
                Client to record.
            msg: str
                Judge action to record.
            """

            if len(self.judgelog) >= 20:
                self.judgelog = self.judgelog[1:]

            info = '{} | [{}] {} ({}) {}'.format(Constants.get_time(),
                                                 client.id, client.displayname,
                                                 client.get_ip(), msg)
            self.judgelog.append(info)
示例#3
0
    def net_cmd_rt(self, args):
        """ Plays the Testimony/CE animation.

        RT#<type:string>#%

        """
        if self.client.is_muted:  # Checks to see if the client has been muted by a mod
            self.client.send_ooc('You have been muted by a moderator.')
            return
        if not self.validate_net_cmd(args, ArgType.STR):
            return
        if not args[0].startswith('testimony'):
            return
        self.client.area.send_command('RT', args[0])
        self.client.area.add_to_judgelog(
            self.client, 'used judge button {}.'.format(args[0]))
        logger.log_server(
            '[{}]{} used judge button {}.'.format(self.client.area.id,
                                                  self.client.get_char_name(),
                                                  args[0]), self.client)
        self.client.last_active = Constants.get_time()
示例#4
0
        def _continue_timestep(self):
            if self._was_terminated:
                # This code should only run if it takes longer for the timer to be terminated than
                # the firing interval.
                return

            if self._timer_value <= self._min_timer_value:
                self._timer_value = self._min_timer_value
                if self._timestep_length < 0:
                    self._was_terminated = True
                    self._on_min_end()
                    return
            elif self._timer_value >= self._max_timer_value:
                self._timer_value = self._max_timer_value
                if self._timestep_length > 0:
                    self._was_terminated = True
                    self._on_max_end()
                    return

            # This moment represents the instant a timestep resumes from pausing/refreshing
            # or the very beginning of one.
            self._just_paused = False
            self._just_unpaused = False

            # If the timer is paused, wait _firing_interval seconds again
            if self._is_paused:
                return

            # Else, if a timestep just finished without any interruptions
            # Reset time spent in timestep and last update to timestep
            if not self._due_continue_timestep_progress:
                self._reset_subtimestep_elapsed()
                self._on_timestep_end()

            adapted_interval = self._firing_interval-self._time_spent_in_timestep
            if adapted_interval < 0:
                adapted_interval = 0

            self._due_continue_timestep_progress = False
            self._task = Constants.create_fragile_task(self._wait_timestep_end(adapted_interval))
示例#5
0
    def net_cmd_hp(self, args):
        """ Sets the penalty bar.

        HP#<type:int>#<new_value:int>#%

        """
        if self.client.is_muted:  # Checks to see if the client has been muted by a mod
            self.client.send_ooc("You have been muted by a moderator")
            return
        if not self.validate_net_cmd(args, ArgType.INT, ArgType.INT):
            return
        try:
            self.client.area.change_hp(args[0], args[1])
            info = 'changed penalty bar {} to {}.'.format(args[0], args[1])
            self.client.area.add_to_judgelog(self.client, info)
            logger.log_server(
                '[{}]{} changed HP ({}) to {}'.format(
                    self.client.area.id, self.client.get_char_name(), args[0],
                    args[1]), self.client)
        except AreaError:
            return
        self.client.last_active = Constants.get_time()
示例#6
0
    def create_task(self, client, args):
        """
        Create a new task for given client with given arguments.

        Parameters
        ----------
        client: ClientManager.Client
            Client associated to the task.
        args: list
            Arguments of the task.
        """

        # Abort old task if it exists
        try:
            old_task = self.get_task(client, args)
            if not old_task.done() and not old_task.cancelled():
                self.cancel_task(old_task)
        except KeyError:
            pass

        async_function = getattr(self, args[0])(client, args[1:])
        async_future = Constants.create_fragile_task(async_function)
        self.client_tasks[client.id][args[0]] = (async_future, args[1:],
                                                 dict())
示例#7
0
 def convert_symbol_to_word(mes):
     if mes is None:
         return None
     return Constants.encode_ao_packet([mes])[0]
示例#8
0
        def play_track(self,
                       name: str,
                       client: ClientManager.Client,
                       raise_if_not_found: bool = False,
                       reveal_sneaked: bool = False,
                       pargs: Dict[str, Any] = None):
            """
            Wrapper function to play a music track in an area.

            Parameters
            ----------
            name : str
                Name of the track to play
            client : ClientManager.Client
                Client who initiated the track change request.
            effect : int, optional
                Accompanying effect to the track (only used by AO 2.8.4+). Defaults to 0.
            raise_if_not_found : bool, optional
                If True, it will raise ServerError if the track name is not in the server's music
                list nor the client's music list. If False, it will not care about it. Defaults to
                False.
            reveal_sneaked : bool, optional
                If True, it will change the visibility status of the sender client to True (reveal
                them). If False, it will keep their visibility as it was. Defaults to False.
            pargs : dict of str to Any
                If given, they are arguments to an MC packet that was given when the track was
                requested, and will override any other arguments given. If not, this is ignored.
                Defaults to None (and converted to an empty dictionary).

            Raises
            ------
            ServerError.FileInvalidNameError:
                If `name` references parent or current directories (e.g. "../hi.mp3")
            ServerError.MusicNotFoundError:
                If `name` is not a music track in the server or client's music list and
                `raise_if_not_found` is True.
            ServerError (with code 'FileInvalidName')
                If `name` references parent or current directories (e.g. "../hi.mp3")
            """

            if not pargs:
                pargs = dict()
            if Constants.includes_relative_directories(name):
                info = f'Music names may not reference parent or current directories: {name}'
                raise ServerError.FileInvalidNameError(info)

            try:
                name, length, source = self.server.get_song_data(name,
                                                                 c=client)
            except ServerError.MusicNotFoundError:
                if raise_if_not_found:
                    raise
                name, length, source = name, -1, ''

            if 'name' not in pargs:
                pargs['name'] = name
            if 'char_id' not in pargs:
                pargs['char_id'] = client.char_id
            pargs['showname'] = client.showname  # Ignore AO shownames
            if 'loop' not in pargs:
                pargs['loop'] = -1
            if 'channel' not in pargs:
                pargs['channel'] = 0
            if 'effects' not in pargs:
                pargs['effects'] = 0

            def loop(char_id):
                for client in self.clients:
                    loop_pargs = pargs.copy()
                    # Overwrite in case char_id changed (e.g., server looping)
                    loop_pargs['char_id'] = char_id
                    client.send_music(**loop_pargs)

                if self.music_looper:
                    self.music_looper.cancel()
                if length > 0:
                    f = lambda: loop(-1)  # Server should loop now
                    self.music_looper = asyncio.get_event_loop().call_later(
                        length, f)

            loop(pargs['char_id'])

            # Record the character name and the track they played.
            self.current_music_player = client.displayname
            self.current_music = name
            self.current_music_source = source

            logger.log_server(
                '[{}][{}]Changed music to {}.'.format(self.id,
                                                      client.get_char_name(),
                                                      name), client)

            # Changing music reveals sneaked players, so do that if requested
            if not client.is_staff(
            ) and not client.is_visible and reveal_sneaked:
                client.change_visibility(True)
                client.send_ooc_others(
                    '(X) {} [{}] revealed themselves by playing music ({}).'.
                    format(client.displayname, client.id, client.area.id),
                    is_zstaff=True)
示例#9
0
    def validate_contents(self,
                          contents,
                          extra_parameters=None) -> List[Dict[str, Any]]:
        # Check music list contents is indeed a list
        if not isinstance(contents, list):
            msg = (f'Expected the music list to be a list, got a '
                   f'{type(contents).__name__}: {contents}.')
            raise ServerError.FileSyntaxError(msg)

        # Check top level description is ok
        for (i, item) in enumerate(contents.copy()):
            if item is None:
                msg = (
                    f'Expected all music list items to be defined, but item {i} was not.'
                )
                raise ServerError.FileSyntaxError(msg)
            if not isinstance(item, dict):
                msg = (
                    f'Expected all music list items to be dictionaries, but item '
                    f'{i}: {item} was not a dictionary.')
                raise ServerError.FileSyntaxError(msg)
            if set(item.keys()) != {'category', 'songs'}:
                msg = (
                    f'Expected all music list items to have exactly two keys: category and '
                    f'songs, but item {i} had keys {set(item.keys())}')
                raise ServerError.FileSyntaxError(msg)

            category, songs = item['category'], item['songs']
            if category is None:
                msg = (
                    f'Expected all music list categories to be defined, but category {i} was '
                    f'not.')
                raise ServerError.FileSyntaxError(msg)
            if songs is None:
                msg = (
                    f'Expected all music list song descriptions to be defined, but song '
                    f'description {i} was not.')
                raise ServerError.FileSyntaxError(msg)

            if not isinstance(category, (str, float, int, bool, complex)):
                msg = (
                    f'Expected all music list category names to be strings or numbers, but '
                    f'category {i}: {category} was not a string or number.')
                raise ServerError.FileSyntaxError(msg)
            if not isinstance(songs, list):
                msg = (
                    f'Expected all music list song descriptions to be a list, but '
                    f'description {i}: {songs} was not a list.')
                raise ServerError.FileSyntaxError(msg)

        # Check each song description dictionary is ok
        for (i, item) in enumerate(contents.copy()):
            category = item['category']
            songs = item['songs']
            for (j, song) in enumerate(songs):
                if song is None:
                    msg = (
                        f'Expected all music list song descriptions to be defined, but song '
                        f'description {j} in category {i}: {category} was not defined.'
                    )
                    raise ServerError.FileSyntaxError(msg)
                if not isinstance(song, dict):
                    msg = (
                        f'Expected all music list song descriptions to be dictionaries: but '
                        f'song description {j} in category {i}: {category} was not a '
                        f'dictionary: {song}.')
                    raise ServerError.FileSyntaxError(msg)
                if 'name' not in song.keys():
                    msg = (
                        f'Expected all music lists song descriptions to have a name as key, but '
                        f'song description {j} in category {i}: {category} '
                        f'had keys {set(song.keys())}.')
                    raise ServerError.FileSyntaxError(msg)
                if not set(song.keys()).issubset({'name', 'length', 'source'}):
                    msg = (
                        f'Expected all music list song description keys be contained in the set '
                        f"{{'name', 'length', 'source'}} but song description {j} in category "
                        f'{i}: {category} had keys {set(song.keys())}.')
                    raise ServerError.FileSyntaxError(msg)

                name = song['name']
                length = song['length'] if 'length' in song else -1
                source = song['source'] if 'source' in song else ''

                if not isinstance(name, (str, float, int, bool, complex)):
                    msg = (
                        f'Expected all music list song names to be strings or numbers, but '
                        f'song {j}: {name} in category {i}: {category} was not a string or '
                        f'number.')
                    raise ServerError.FileSyntaxError(msg)
                if not isinstance(length, (int, float)):
                    msg = (
                        f'Expected all music list song lengths to be numbers, but song {j}: '
                        f'{name} in category {i}: {category} had non-numerical length {length}.'
                    )
                    raise ServerError.FileSyntaxError(msg)
                if not isinstance(source, (str, float, int, bool, complex)):
                    msg = (
                        f'Expected all music list song sources to be strings or numbers, but '
                        f'song {j}: {name} in category {i}: {category} was not a string or '
                        f'number.')

                # Prevent names that may be interpreted as a directory with . or ..
                # This prevents sending the client an entry to their music list which may be read as
                # including a relative directory
                if Constants.includes_relative_directories(name):
                    info = (
                        f'Music {name} could be interpreted as referencing current or '
                        f'parent directories, so it is invalid.')
                    raise ServerError.FileSyntaxError(info)
        return contents
示例#10
0
    def net_cmd_ct(self, args):
        """ OOC Message

        CT#<name:string>#<message:string>#%

        """
        if self.client.is_ooc_muted:  # Checks to see if the client has been muted by a mod
            self.client.send_ooc("You have been muted by a moderator.")
            return
        if not self.validate_net_cmd(
                args, ArgType.STR, ArgType.STR, needs_auth=False):
            return
        if self.client.name != args[0] and self.client.fake_name != args[0]:
            if self.client.is_valid_name(args[0]):
                self.client.name = args[0]
                self.client.fake_name = args[0]
            else:
                self.client.fake_name = args[0]
                self.client.name = ''
        if self.client.name == '':
            self.client.send_ooc(
                'You must insert a name with at least one letter.')
            return
        if self.client.name.startswith(' '):
            self.client.send_ooc(
                'You must insert a name that starts with a letter.')
            return
        if self.server.config[
                'hostname'] in self.client.name or '<dollar>G' in self.client.name:
            self.client.send_ooc('That name is reserved.')
            return
        if args[1].startswith('/'):
            spl = args[1][1:].split(' ', 1)
            cmd = spl[0]
            arg = ''
            if len(spl) == 2:
                arg = spl[1][:1024]
            try:
                called_function = 'ooc_cmd_{}'.format(cmd)
                function = None  # Double assignment to check if it matched to a function later
                function = getattr(self.server.commands, called_function)
            except AttributeError:
                try:
                    function = getattr(self.server.commands_alt,
                                       called_function)
                except AttributeError:
                    logger.log_print('Attribute error with ' + called_function)
                    self.client.send_ooc('Invalid command.')

            if function:
                try:
                    function(self.client, arg)
                except TsuserverException as ex:
                    self.client.send_ooc(ex)
        else:
            # Censor passwords if accidentally said without a slash in OOC
            for password in self.server.all_passwords:
                for login in ['login ', 'logincm ', 'loginrp ', 'logingm ']:
                    if login + password in args[1]:
                        args[1] = args[1].replace(password, '[CENSORED]')
            if self.client.disemvowel:  #If you are disemvoweled, replace string.
                args[1] = Constants.disemvowel_message(args[1])
            if self.client.disemconsonant:  #If you are disemconsonanted, replace string.
                args[1] = Constants.disemconsonant_message(args[1])
            if self.client.remove_h:  #If h is removed, replace string.
                args[1] = Constants.remove_h_message(args[1])

            self.client.area.send_command('CT', self.client.name, args[1])
            self.client.last_ooc_message = args[1]
            logger.log_server(
                '[OOC][{}][{}][{}]{}'.format(self.client.area.id,
                                             self.client.get_char_name(),
                                             self.client.name, args[1]),
                self.client)
        self.client.last_active = Constants.get_time()
示例#11
0
    def load_areas(self, area_list_file='config/areas.yaml'):
        """
        Load an area list.

        Parameters
        ----------
        area_list_file: str, optional
            Location of the area list to load. Defaults to 'config/areas.yaml'.

        Raises
        ------
        AreaError
            If any one of the following conditions are met:
            * An area has no 'area' or no 'background' tag.
            * An area uses the deprecated 'sound_proof' tag.
            * Two areas have the same name.
            * An area parameter was left deliberately blank as opposed to fully erased.
            * An area has a passage to an undefined area.

        FileNotFound
            If the area list could not be found.
        """

        self.area_names = set()
        current_area_id = 0
        temp_areas = list()
        temp_area_names = set()
        temp_reachable_area_names = set()

        # Check if valid area list file
        with Constants.fopen(area_list_file, 'r') as chars:
            areas = Constants.yaml_load(chars)

        def_param = {
            'afk_delay': 0,
            'afk_sendto': 0,
            'bglock': False,
            'bullet': True,
            'cbg_allowed': False,
            'change_reachability_allowed': True,
            'default_description':
            self.server.config['default_area_description'],
            'evidence_mod': 'FFA',
            'gm_iclock_allowed': True,
            'has_lights': True,
            'iniswap_allowed': False,
            'lobby_area': False,
            'locking_allowed': False,
            'private_area': False,
            'reachable_areas': '<ALL>',
            'restricted_chars': '',
            'rollp_allowed': True,
            'rp_getarea_allowed': True,
            'rp_getareas_allowed': True,
            'scream_range': '',
            'song_switch_allowed': False,
        }

        # Create the areas
        for item in areas:
            # Check required parameters
            if 'area' not in item:
                info = 'Area {} has no name.'.format(current_area_id)
                raise AreaError(info)
            if 'background' not in item:
                info = 'Area {} has no background.'.format(item['area'])
                raise AreaError(info)

            # Check unset optional parameters
            for param in def_param:
                if param not in item:
                    item[param] = def_param[param]

            # Prevent names that may be interpreted as a directory with . or ..
            # This prevents sending the client an entry to their music list which may be read as
            # including a relative directory
            if False:  # Constants.includes_relative_directories(item['area']):
                info = (
                    f'Area {item["area"]} could be interpreted as referencing the current or '
                    f'parent directories, so it is invalid. Please rename the area and try '
                    f'again.')
                raise AreaError(info)

            # Check use of backwards incompatible parameters
            if 'sound_proof' in item:
                info = (
                    'The sound_proof property was defined for area {}. '
                    'Support for sound_proof was removed in favor of scream_range. '
                    'Please replace the sound_proof tag with scream_range in '
                    'your area list and try again.'.format(item['area']))
                raise AreaError(info)

            # Avoid having areas with the same name
            if item['area'] in temp_area_names:
                info = (
                    'Two areas have the same name in area list: {}. '
                    'Please rename the duplicated areas and try again.'.format(
                        item['area']))
                raise AreaError(info)

            # Check if any of the items were interpreted as Python Nones (maybe due to empty lines)
            for parameter in item:
                if item[parameter] is None:
                    info = (
                        'Parameter {} is manually undefined for area {}. This can be the case '
                        'due to having an empty parameter line in your area list. '
                        'Please fix or remove the parameter from the area definition and try '
                        'again.'.format(parameter, item['area']))
                    raise AreaError(info)

            temp_areas.append(self.Area(current_area_id, self.server, item))
            temp_area_names.add(item['area'])
            temp_reachable_area_names |= temp_areas[-1].reachable_areas
            current_area_id += 1

        # Check if a reachable area is not an area name
        # Can only be done once all areas are created

        unrecognized_areas = temp_reachable_area_names - temp_area_names - {
            '<ALL>'
        }
        if unrecognized_areas != set():
            info = (
                'The following areas were defined as reachable areas of some areas in the '
                'area list file, but were not actually defined as areas: {}. Please rename the '
                'affected areas and try again.'.format(unrecognized_areas))
            raise AreaError(info)

        # Only once all areas have been created, actually set the corresponding values
        # Helps avoiding junk area lists if there was an error
        # But first, remove all zones

        backup_zones = self.server.zone_manager.get_zones()
        for (zone_id, zone) in backup_zones.items():
            self.server.zone_manager.delete_zone(zone_id)
            for client in zone.get_watchers():
                client.send_ooc('Your zone has been automatically deleted.')

        old_areas = self.areas
        self.areas = temp_areas
        self.area_names = temp_area_names

        # And cancel all existing day cycles
        for client in self.server.client_manager.clients:
            try:
                client.server.tasker.remove_task(client, ['as_day_cycle'])
            except KeyError:
                pass

        # And remove all global IC and global IC prefixes
        for client in self.server.client_manager.clients:
            if client.multi_ic:
                client.send_ooc(
                    'Due to an area list reload, your global IC was turned off. You '
                    'may turn it on again manually.')
                client.multi_ic = None
            if client.multi_ic_pre:
                client.send_ooc(
                    'Due to an area list reload, your global IC prefix was removed. '
                    'You may set it again manually.')
                client.multi_ic_pre = ''

        # If the default area ID is now past the number of available areas, reset it back to zero
        if self.server.default_area >= len(self.areas):
            self.server.default_area = 0

        for area in old_areas:
            # Decide whether the area still exists or not
            try:
                new_area = self.get_area_by_name(area.name)
                remains = True
            except AreaError:
                new_area = self.default_area()
                remains = False

            # Move existing clients to new corresponding area (or to default area if their previous
            # area no longer exists).
            for client in area.clients.copy():
                # Check if current char is available
                if new_area.is_char_available(client.char_id):
                    new_char_id = client.char_id
                else:
                    try:
                        new_char_id = new_area.get_rand_avail_char_id()
                    except AreaError:
                        new_char_id = -1

                if remains:
                    message = 'Area list reload. Moving you to the new {}.'
                else:
                    message = (
                        'Area list reload. Your previous area no longer exists. Moving you '
                        'to the server default area {}.')

                client.send_ooc(message.format(new_area.name))
                client.change_area(new_area,
                                   ignore_checks=True,
                                   change_to=new_char_id,
                                   ignore_notifications=True)

            # Move parties (independently)
            for party in area.parties.copy():
                party.area = new_area
                new_area.add_party(party)

        # Update the server's area list only once everything is successful
        self.server.old_area_list = self.server.area_list
        self.server.area_list = area_list_file
示例#12
0
    def notify_me_status(self, area: AreaManager.Area, changed_visibility: bool = True,
                         changed_hearing: bool = True) -> bool:
        client = self.client
        normal_visibility = changed_visibility and area.lights and not client.is_blind
        info = ''
        vis_info = ''
        sne_info = ''
        # While we always notify in OOC if someone has a custom status, we only ping IC if the
        # status has changed from the last time the player has seen it.
        # This prevents ping spam if two players with custom statuses move together.
        found_something = False

        status_visible = [c for c in area.clients if c.status and c != client and c.is_visible]
        status_sneaking = [c for c in area.clients if c.status and c != client and not c.is_visible]
        staff_privileged = not normal_visibility
        if status_visible:
            if client.is_staff() or normal_visibility:
                mark = '(X) ' if staff_privileged else ''
                players = Constants.cjoin([c.displayname for c in status_visible])
                verb = 'was' if len(status_visible) == 1 else 'were'
                vis_info = (f'{mark}You note something about {players} who {verb} in the area '
                            f'already')

                for player in status_visible:
                    remembered_status = client.remembered_statuses.get(player.id, '')
                    if player.status != remembered_status:
                        # Found someone whose status has changed
                        # Only for these situations do we want to ping
                        found_something = True
                    client.remembered_statuses[player.id] = player.status

            elif changed_visibility and not client.is_deaf:
                # Give nerfed notifications if the lights are out or the player is blind, but NOT
                # if the player is deaf.
                vis_info = 'You think something is unusual about someone in the area'

        # To prepare message with sneaked bleeding, you must be staff.
        # Otherwise, prepare faint drops of blood if you are not deaf.
        # Otherwise, just prepare 'smell' if lights turned off or you are blind

        if status_sneaking:
            if client.is_staff():
                players = Constants.cjoin([c.displayname for c in status_sneaking])
                verb = 'was' if len(status_sneaking) == 1 else 'were'
                sne_info = (f'(X) You note something about {players}, who {verb} in the area '
                            f'already and also sneaking')

        if vis_info and sne_info:
            # Remove marks and capital letters. Use 'count=1' explicitly to prevent more
            # replacements that could happen with player displaynames.
            # Then readd the mark manually (mark would have been present because sne_info)
            vis_info = vis_info[4:] if vis_info.startswith('(X)') else vis_info
            sne_info = sne_info.replace("(X) You", "you", 1)
            info = f'(X) {vis_info}, and {sne_info}'
        elif vis_info:
            info = vis_info
        elif sne_info:
            info = sne_info

        if info:
            client.send_ooc(info + '.')

        return found_something
示例#13
0
    def data_received(self, data):
        """ Handles any data received from the network.

        Receives data, parses them into a command and passes it
        to the command handler.

        :param data: bytes of data
        """
        buf = data
        if buf is None:
            buf = b''

        # try to decode as utf-8, ignore any erroneous characters
        self.buffer += buf.decode('utf-8', 'ignore')
        self.buffer = self.buffer.translate({ord(c): None for c in '\0'})

        if len(self.buffer) > 8192:
            msg = self.buffer if len(self.buffer) < 512 else self.buffer[:512] + '...'
            logger.log_server('Terminated {} (packet too long): sent {} ({} bytes)'
                              .format(self.client.get_ipreal(), msg, len(self.buffer)))
            self.client.disconnect()
            return

        found_message = False
        for msg in self.get_messages():
            found_message = True
            if len(msg) < 2:
                # This immediatelly kills any client that does not even try to follow the proper
                # client protocol
                msg = self.buffer if len(self.buffer) < 512 else self.buffer[:512] + '...'
                logger.log_server('Terminated {} (packet too short): sent {} ({} bytes)'
                                  .format(self.client.get_ipreal(), msg, len(self.buffer)))
                self.client.disconnect()
                return
            # general netcode structure is not great
            if msg[0] in ('#', '3', '4'):
                if msg[0] == '#':
                    msg = msg[1:]
                raw_parameters = msg.split('#')
                raw_parameters[0] = fanta_decrypt(raw_parameters[0])
                msg = '#'.join(raw_parameters)

            logger.log_debug('[INC][RAW]{}'.format(msg), self.client)
            try:
                if self.server.print_packets:
                    print(f'> {self.client.id}: {msg}')
                self.server.log_packet(self.client, msg, True)
                # Decode AO clients' encoding
                cmd, *args = Constants.decode_ao_packet(msg.split('#'))
                try:
                    dispatched = self.net_cmd_dispatcher[cmd]
                except KeyError:
                    logger.log_pserver(f'Client {self.client.id} sent abnormal packet {msg} '
                                       f'(client version: {self.client.version}).')
                else:
                    dispatched(self, args)
            except AOProtocolError.InvalidInboundPacketArguments:
                pass
            except Exception as ex:
                self.server.send_error_report(self.client, cmd, args, ex)
        if not found_message:
            # This immediatelly kills any client that does not even try to follow the proper
            # client protocol
            msg = self.buffer if len(self.buffer) < 512 else self.buffer[:512] + '...'
            logger.log_server('Terminated {} (packet syntax unrecognized): sent {} ({} bytes)'
                              .format(self.client.get_ipreal(), msg, len(self.buffer)))
            self.client.disconnect()
示例#14
0
def log_error(msg, server, errortype='P') -> str:
    # errortype "C" if server raised an error as a result of a client packet.
    # errortype "D" if player manually requested an error dump
    # errortype "P" if server raised an error for any other reason
    error_log = logging.getLogger('error')

    file = 'logs/{}{}.log'.format(Constants.get_time_iso(), errortype)
    file = file.replace(':', '')
    error_handler = logging.FileHandler(file, encoding='utf-8')

    error_handler.setLevel(logging.ERROR)
    error_handler.setFormatter(
        logging.Formatter('[%(asctime)s UTC]%(message)s'))
    error_log.addHandler(error_handler)

    if server:
        # Add list of most recent packets
        msg += f'\n\n\n= {server.logged_packet_limit} most recent packets dump ='
        if not server.logged_packets:
            msg += '\nNo logged packets.'
        else:
            for logged_packet in server.logged_packets:
                str_logged_packet = ' '.join(logged_packet)
                msg += f'\n{str_logged_packet}'

        # Add list of clients to error log
        try:
            msg += '\n\n\n= Client dump. ='
            msg += '\n*Number of clients: {}'.format(len(server.get_clients()))
            msg += '\n*Current clients'
            clients = sorted(server.get_clients(), key=lambda c: c.id)
            for c in clients:
                msg += '\n\n{}'.format(c.get_info(as_mod=True))
        except Exception:
            etype, evalue, etraceback = sys.exc_info()
            msg += '\nError generating client dump.'
            msg += '\n{}'.format("".join(
                traceback.format_exception(etype, evalue, etraceback)))

        # Add list of areas to error log
        try:
            msg += '\n\n\n= Area dump ='
            msg += '\n*Current area list: {}'.format(server.area_list)
            msg += '\n*Old area list: {}'.format(server.old_area_list)
            msg += '\n*Current areas:'

            for area in server.area_manager.areas:
                msg += '\n**{}'.format(area)
                for c in area.clients:
                    msg += '\n***{}'.format(c)
        except Exception:
            etype, evalue, etraceback = sys.exc_info()
            msg += '\nError generating area dump.'
            msg += '\n{}'.format("".join(
                traceback.format_exception(etype, evalue, etraceback)))
    else:
        # Case server was not initialized properly, so areas and clients are not set
        msg += (
            '\nServer was not initialized, so packet, client and area dumps could not be '
            'generated.')

    # Write and log
    error_log.error(msg)
    error_log.removeHandler(error_handler)

    log_pserver('Successfully created server dump file {}'.format(file))
    return file
示例#15
0
def log_print(msg, client=None):
    msg = f'{parse_client_info(client)}{msg}'
    current_time = Constants.get_time_iso()
    print('{}: {}'.format(current_time, msg))
示例#16
0
    def net_cmd_ms(self, args):
        """ IC message.

        Refer to the implementation for details.

        """
        if self.client.is_muted:  # Checks to see if the client has been muted by a mod
            self.client.send_ooc("You have been muted by a moderator.")
            return
        if self.client.area.ic_lock and not self.client.is_staff():
            self.client.send_ooc(
                "IC chat in this area has been locked by a moderator.")
            return
        if not self.client.area.can_send_message():
            return

        pargs = self.process_arguments('ms', args)
        if not pargs:
            return

        if not self.client.area.iniswap_allowed:
            if self.client.area.is_iniswap(self.client, pargs['pre'],
                                           pargs['anim'], pargs['folder']):
                self.client.send_ooc("Iniswap is blocked in this area.")
                return
        if pargs[
                'folder'] in self.client.area.restricted_chars and not self.client.is_staff(
                ):
            self.client.send_ooc('Your character is restricted in this area.')
            return
        if pargs['msg_type'] not in ('chat', '0', '1'):
            return
        if pargs['anim_type'] not in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10):
            return
        if pargs['cid'] != self.client.char_id:
            return
        if pargs['sfx_delay'] < 0:
            return
        if pargs['button'] not in (0, 1, 2, 3, 4, 5, 6, 7):  # Shouts
            return
        if pargs[
                'button'] > 0 and not self.client.area.bullet and not self.client.is_staff(
                ):
            self.client.send_ooc('Bullets are disabled in this area.')
            return
        if pargs['evidence'] < 0:
            return
        if pargs['ding'] not in (0, 1, 2, 3, 4, 5, 6, 7):  # Effects
            return
        if pargs['color'] not in (0, 1, 2, 3, 4, 5, 6):
            return
        if pargs[
                'color'] == 5 and not self.client.is_mod and not self.client.is_cm:
            pargs['color'] = 0
        if pargs['color'] == 6:
            # Remove all unicode to prevent now yellow text abuse
            pargs['text'] = re.sub(r'[^\x00-\x7F]+', ' ', pargs['text'])
            if len(pargs['text'].strip(' ')) == 1:
                pargs['color'] = 0
            else:
                if pargs['text'].strip(' ') in ('<num>', '<percent>',
                                                '<dollar>', '<and>'):
                    pargs['color'] = 0
        if self.client.pos:
            pargs['pos'] = self.client.pos
        else:
            if pargs['pos'] not in ('def', 'pro', 'hld', 'hlp', 'jud', 'wit'):
                return
        self.client.pos = pargs['pos']

        # Truncate and alter message if message effect is in place
        raw_msg = pargs['text'][:256]
        msg = raw_msg
        if self.client.gimp:  #If you are gimped, gimp message.
            msg = Constants.gimp_message()
        if self.client.disemvowel:  #If you are disemvoweled, replace string.
            msg = Constants.disemvowel_message(msg)
        if self.client.disemconsonant:  #If you are disemconsonanted, replace string.
            msg = Constants.disemconsonant_message(msg)
        if self.client.remove_h:  #If h is removed, replace string.
            msg = Constants.remove_h_message(msg)

        gag_replaced = False
        if self.client.is_gagged:
            allowed_starters = ('(', '*', '[')
            if msg != ' ' and not msg.startswith(allowed_starters):
                gag_replaced = True
                msg = Constants.gagged_message()
            if msg != raw_msg:
                self.client.send_ooc_others(
                    '(X) {} tried to say `{}` but is currently gagged.'.format(
                        self.client.displayname, raw_msg),
                    is_zstaff_flex=True,
                    in_area=True)

        if pargs['evidence']:
            evidence_position = self.client.evi_list[pargs['evidence']] - 1
            if self.client.area.evi_list.evidences[
                    evidence_position].pos != 'all':
                self.client.area.evi_list.evidences[
                    evidence_position].pos = 'all'
                self.client.area.broadcast_evidence_list()

        # If client has GlobalIC enabled, set area range target to intended range and remove
        # GlobalIC prefix if needed.
        if self.client.multi_ic is None or not msg.startswith(
                self.client.multi_ic_pre):
            area_range = range(self.client.area.id, self.client.area.id + 1)
        else:
            # As msg.startswith('') is True, this also accounts for having no required prefix.
            start, end = self.client.multi_ic[
                0].id, self.client.multi_ic[1].id + 1
            start_area = self.server.area_manager.get_area_by_id(start)
            end_area = self.server.area_manager.get_area_by_id(end - 1)
            area_range = range(start, end)

            msg = msg.replace(self.client.multi_ic_pre, '', 1)
            if start != end - 1:
                self.client.send_ooc(
                    'Sent global IC message "{}" to areas {} through {}.'.
                    format(msg, start_area.name, end_area.name))
            else:
                self.client.send_ooc(
                    'Sent global IC message "{}" to area {}.'.format(
                        msg, start_area.name))

        pargs['msg'] = msg
        pargs['evidence'] = self.client.evi_list[pargs['evidence']]
        pargs[
            'showname'] = ''  # Dummy value, actual showname is computed later

        # Compute pairs
        # Based on tsuserver3.3 code
        # Only do this if character is paired, which would only happen for AO 2.6 clients

        self.client.charid_pair = pargs[
            'charid_pair'] if 'charid_pair' in pargs else -1
        self.client.offset_pair = pargs[
            'offset_pair'] if 'offset_pair' in pargs else 0
        self.client.flip = pargs['flip']
        self.client.char_folder = pargs['folder']

        if pargs['anim_type'] not in (5, 6):
            self.client.last_sprite = pargs['anim']

        pargs['other_offset'] = 0
        pargs['other_emote'] = 0
        pargs['other_flip'] = 0
        pargs['other_folder'] = ''
        if 'charid_pair' not in pargs or pargs['charid_pair'] < -1:
            pargs['charid_pair'] = -1

        if pargs['charid_pair'] > -1:
            for target in self.client.area.clients:
                if target == self.client:
                    continue
                # Check pair has accepted pair
                if target.char_id != self.client.charid_pair:
                    continue
                if target.charid_pair != self.client.char_id:
                    continue
                # Check pair is in same position
                if target.pos != self.client.pos:
                    continue

                pargs['other_offset'] = target.offset_pair
                pargs['other_emote'] = target.last_sprite
                pargs['other_flip'] = target.flip
                pargs['other_folder'] = target.char_folder
                break
            else:
                # There are no clients who want to pair with this client
                pargs['charid_pair'] = -1

        for area_id in area_range:
            target_area = self.server.area_manager.get_area_by_id(area_id)
            for c in target_area.clients:
                c.send_ic(params=pargs,
                          sender=self.client,
                          gag_replaced=gag_replaced)

            target_area.set_next_msg_delay(len(msg))

            # Deal with shoutlog
            if pargs['button'] > 0:
                info = 'used shout {} with the message: {}'.format(
                    pargs['button'], msg)
                target_area.add_to_shoutlog(self.client, info)

        self.client.area.set_next_msg_delay(len(msg))
        logger.log_server(
            '[IC][{}][{}]{}'.format(self.client.area.id,
                                    self.client.get_char_name(), msg),
            self.client)

        # Sending IC messages reveals sneaked players
        if not self.client.is_staff() and not self.client.is_visible:
            self.client.change_visibility(True)
            self.client.send_ooc_others(
                '(X) {} revealed themselves by talking ({}).'.format(
                    self.client.displayname, self.client.area.id),
                is_zstaff=True)

        self.server.tasker.create_task(self.client, [
            'as_afk_kick', self.client.area.afk_delay,
            self.client.area.afk_sendto
        ])
        if self.client.area.is_recording:
            self.client.area.recorded_messages.append(args)

        self.client.last_ic_message = msg
        self.client.last_active = Constants.get_time()
示例#17
0
 def convert_word_to_symbol(mes):
     if mes is None:
         return None
     return Constants.decode_ao_packet([mes])[0]
示例#18
0
    def validate_contents(self, contents, extra_parameters=None):
        if extra_parameters is None:
            extra_parameters = dict()
        server_character_list = extra_parameters.get('server_character_list',
                                                     None)
        server_default_area_description = extra_parameters.get(
            'server_default_area_description', '')

        default_area_parameters = {
            'afk_delay': 0,
            'afk_sendto': 0,
            'background_tod': dict(),
            'bglock': False,
            'bullet': True,
            'cbg_allowed': False,
            'change_reachability_allowed': True,
            'default_description': server_default_area_description,
            'evidence_mod': 'FFA',
            'gm_iclock_allowed': True,
            'has_lights': True,
            'iniswap_allowed': False,
            'global_allowed': True,
            'lobby_area': False,
            'locking_allowed': False,
            'private_area': False,
            'reachable_areas': '<ALL>',
            'restricted_chars': '',
            'rollp_allowed': True,
            'rp_getarea_allowed': True,
            'rp_getareas_allowed': True,
            'scream_range': '',
            'song_switch_allowed': False,
        }

        current_area_id = 0
        area_parameters = list()
        temp_area_names = set()
        found_uncheckable_restricted_chars = False

        # Create the areas
        for item in contents:
            # Check required parameters
            if 'area' not in item:
                info = 'Area {} has no name.'.format(current_area_id)
                raise AreaError(info)
            if 'background' not in item:
                info = 'Area {} has no background.'.format(item['area'])
                raise AreaError(info)

            # Prevent reserved area names (it has a special meaning)
            reserved_names = {
                '<ALL>',
                '<REACHABLE_AREAS>',
            }

            for name in reserved_names:
                if item['area'] == name:
                    info = (
                        'An area in your area list is called "{name}". This is a reserved '
                        'name, so it is not a valid area name. Please change its name and try '
                        'again.')
                    raise AreaError(info)

            # Prevent names that may be interpreted as a directory with . or ..
            # This prevents sending the client an entry to their music list which may be read as
            # including a relative directory
            if Constants.includes_relative_directories(item['area']):
                info = (
                    f'Area {item["area"]} could be interpreted as referencing the current or '
                    f'parent directories, so it is invalid. Please rename the area and try '
                    f'again.')
                raise AreaError(info)

            # Check unset optional parameters
            for param in default_area_parameters:
                if param not in item:
                    item[param] = default_area_parameters[param]

            # Check use of backwards incompatible parameters
            if 'sound_proof' in item:
                info = (
                    'The sound_proof property was defined for area {}. '
                    'Support for sound_proof was removed in favor of scream_range. '
                    'Please replace the sound_proof tag with scream_range in '
                    'your area list and try again.'.format(item['area']))
                raise AreaError(info)

            # Avoid having areas with the same name
            if item['area'] in temp_area_names:
                info = (
                    'Two areas have the same name in area list: {}. '
                    'Please rename the duplicated areas and try again.'.format(
                        item['area']))
                raise AreaError(info)

            # Check if any of the items were interpreted as Python Nones (maybe due to empty lines)
            for parameter in item:
                if item[parameter] is None:
                    info = (
                        'Parameter {} is manually undefined for area {}. This can be the case '
                        'due to having an empty parameter line in your area list. '
                        'Please fix or remove the parameter from the area definition and try '
                        'again.'.format(parameter, item['area']))
                    raise AreaError(info)

            # Check and fix background tods if needed, as YAML parses this as a list of
            if item['background_tod'] != dict():
                raw_background_tod_map = item['background_tod']
                if not isinstance(raw_background_tod_map, dict):
                    info = (
                        f'Expected background TOD for area {item["area"]} be '
                        f'one dictionary, found it was of type '
                        f'{type(raw_background_tod_map).__name__}: {raw_background_tod_map}'
                    )
                    raise AreaError(info)

                new_background_tod = dict()
                if not isinstance(raw_background_tod_map, dict):
                    info = (
                        f'Expected background TOD for area {item["area"]} be a dictionary, '
                        f'found it was of type {type(raw_background_tod_map).__name__}: '
                        f'{raw_background_tod_map}')
                    raise AreaError(info)

                for (key, value) in raw_background_tod_map.items():
                    tod_name = str(key)
                    tod_background = str(value)
                    if not tod_name.strip():
                        info = (
                            f'TOD name `{tod_name}` invalid for area {item["area"]}. '
                            f'Make sure the TOD name has non-space characters and try '
                            f'again.')
                        raise AreaError(info)
                    if ' ' in tod_name:
                        info = (
                            f'TOD name `{tod_name}` invalid for area {item["area"]}. '
                            f'Make sure the TOD name has no space characters and try '
                            f'again.')
                        raise AreaError(info)
                    if '|' in tod_name:
                        info = (
                            f'TOD name `{tod_name}` contains invalid character |.'
                            f'Make sure the TOD name does not have that character and '
                            f'try again.')
                        raise AreaError(info)
                    if '|' in tod_background:
                        info = (
                            f'TOD background `{tod_background}` contains invalid '
                            f'character |. Make sure the TOD name does not have that '
                            f'character and try again.')
                        raise AreaError(tod_background)

                    new_background_tod[tod_name] = tod_background
                item['background_tod'] = new_background_tod

            area_parameters.append(item.copy())
            temp_area_names.add(item['area'])
            current_area_id += 1

        # Check if a reachable area is not an area name
        # Can only be done once all areas are created
        for area_item in area_parameters:
            name = area_item['area']

            reachable_areas = Constants.fix_and_setify(
                area_item['reachable_areas'])
            scream_range = Constants.fix_and_setify(area_item['scream_range'])
            restricted_chars = Constants.fix_and_setify(
                area_item['restricted_chars'])

            if reachable_areas == {'<ALL>'}:
                reachable_areas = temp_area_names.copy()
            if scream_range == {'<ALL>'}:
                scream_range = temp_area_names.copy()
            elif scream_range == {'<REACHABLE_AREAS>'}:
                scream_range = reachable_areas.copy()

            area_item['reachable_areas'] = reachable_areas
            area_item['scream_range'] = scream_range
            area_item['restricted_chars'] = restricted_chars

            # Make sure no weird areas were set as reachable by players or by screams
            unrecognized_areas = reachable_areas - temp_area_names
            if unrecognized_areas:
                info = (
                    f'Area {name} has unrecognized areas {unrecognized_areas} defined as '
                    f'areas a player can reach to. Please rename the affected areas and try '
                    f'again.')
                raise AreaError(info)

            unrecognized_areas = scream_range - temp_area_names
            if unrecognized_areas:
                info = (
                    f'Area {name} has unrecognized areas {unrecognized_areas} defined as '
                    f'areas screams can reach to. Please rename the affected areas and try '
                    f'again.')
                raise AreaError(info)

            # Make sure only characters that exist are part of the restricted char set
            if server_character_list is not None:
                unrecognized_characters = restricted_chars - set(
                    server_character_list)
                if unrecognized_characters:
                    info = (
                        f'Area {name} has unrecognized characters {unrecognized_characters} '
                        f'defined as restricted characters. Please make sure the characters '
                        f'exist and try again.')
                    raise AreaError(info)
            elif restricted_chars:
                found_uncheckable_restricted_chars = True

        if found_uncheckable_restricted_chars:
            info = (
                'WARNING: Some areas provided default restricted characters. However, no '
                'server character list was provided, so no checks whether restricted '
                'characters were in the character list of the server were performed.'
            )
            print(info)

        return area_parameters
示例#19
0
    def new_group(self, playergroup_type=None, creator=None, player_limit=None,
                  player_concurrent_limit=1, require_invitations=False, require_players=True,
                  require_leaders=True):
        """
        Create a new player group managed by this manager.

        Parameters
        ----------
        playergroup_type : PlayerGroup
            Class of player group that will be produced. Defaults to None (and converted to the
            default player group created by this player group manager).
        creator : ClientManager.Client, optional
            The player who created this group. If set, they will also be added to the group.
            Defaults to None.
        player_limit : int or None, optional
            If an int, it is the maximum number of players the player group supports. If None, it
            indicates the player group has no player limit. Defaults to None.
        player_concurrent_limit : int or None, optional
            If an int, it is the maximum number of player groups managed by `self` that any player
            of this group to create may belong to, including this group to create. If None, it
            indicates that this group does not care about how many other player groups managed by
            `self` each of its players belongs to. Defaults to 1 (a player may not be in
            another group managed by `self` while in this new group).
        require_invitations : bool, optional
            If True, users can only be added to the group if they were previously invited. If
            False, no checking for invitations is performed. Defaults to False.
        require_players : bool, optional
            If True, if at any point the group loses all its players, the group will automatically
            be deleted. If False, no such automatic deletion will happen. Defaults to True.
        require_leaders : bool, optional
            If True, if at any point the group has no leaders left, the group will choose a leader
            among any remaining players left; if no players are left, the next player added will
            be made leader. If False, no such automatic assignment will happen. Defaults to True.

        Returns
        -------
        PlayerGroup
            The created player group.

        Raises
        ------
        PlayerGroupError.ManagerTooManyGroupsError
            If the manager is already managing its maximum number of groups.
        PlayerGroupError.UserHitGroupConcurrentLimitError.
            If `creator` has reached the concurrent player membership limit of any of the groups it
            belongs to managed by this manager, or by virtue of joining this group the creator
            they will violate this group's concurrent player membership limit.

        """

        if self._playergroup_limit is not None:
            if len(self._id_to_group) >= self._playergroup_limit:
                raise PlayerGroupError.ManagerTooManyGroupsError
        if creator:
            # Check if adding the creator to this new group would cause any concurrent
            # membership limits being reached.
            if self.find_player_concurrent_limiting_group(creator):
                raise PlayerGroupError.UserHitGroupConcurrentLimitError
            groups_of_user = self._user_to_groups.get(creator, None)
            if groups_of_user is not None and len(groups_of_user) >= player_concurrent_limit:
                raise PlayerGroupError.UserHitGroupConcurrentLimitError

        # At this point, we are committed to creating this player group.
        # Generate a playergroup ID and the new group

        def_args = (
            self._server,
            self,
            self.get_available_group_id(),
            )
        def_kwargs = {
            'player_limit': player_limit,
            'player_concurrent_limit': player_concurrent_limit,
            'require_invitations': require_invitations,
            'require_players': require_players,
            'require_leaders': require_leaders,
            }

        new_playergroup_type = Constants.make_partial_from(playergroup_type,
                                                           self._default_playergroup_type,
                                                           *def_args, **def_kwargs)
        playergroup = new_playergroup_type()
        playergroup_id = playergroup.get_id()
        self._id_to_group[playergroup_id] = playergroup

        if creator:
            playergroup.add_player(creator)

        self._check_structure()
        return playergroup
示例#20
0
    def notify_me_blood(self,
                        area,
                        changed_visibility=True,
                        changed_hearing=True):
        client = self.client
        changed_area = (client.area != area)

        ###########
        # If someone else is bleeding in the new area, notify the person moving
        bleeding_visible = [
            c for c in area.clients
            if c.is_visible and c.is_bleeding and c != client
        ]
        bleeding_sneaking = [
            c for c in area.clients
            if not c.is_visible and c.is_bleeding and c != client
        ]
        info = ''
        vis_info = ''
        sne_info = ''

        # To prepare message with players bleeding, one of these must be true:
        # 1. You are staff
        # 2. Lights are on and you are not blind
        # Otherwise, prepare faint drops of blood if you are not deaf.
        # Otherwise, just prepare 'smell' if lights turned off or you are blind

        if bleeding_visible:
            normal_visibility = changed_visibility and area.lights and not client.is_blind
            if client.is_staff() or normal_visibility:
                vis_info = ('{}You see {} {} bleeding'.format(
                    '(X) ' if not normal_visibility else '',
                    Constants.cjoin([c.displayname for c in bleeding_visible]),
                    'is' if len(bleeding_visible) == 1 else 'are'))
            elif not client.is_deaf and changed_hearing:
                vis_info = 'You hear faint drops of blood'
            elif client.is_blind and client.is_deaf and changed_area:
                vis_info = 'You smell blood'

        # To prepare message with sneaked bleeding, you must be staff.
        # Otherwise, prepare faint drops of blood if you are not deaf.
        # Otherwise, just prepare 'smell' if lights turned off or you are blind

        if bleeding_sneaking:
            if client.is_staff():
                sne_info = ('(X) You see {} {} bleeding while sneaking'.format(
                    Constants.cjoin([c.displayname
                                     for c in bleeding_sneaking]),
                    'is' if len(bleeding_visible) == 1 else 'are'))
            elif not client.is_deaf and changed_hearing:
                sne_info = 'You hear faint drops of blood'
            elif not area.lights or client.is_blind and changed_area:
                sne_info = 'You smell blood'

        # If there is visible info, merge it with sneak info if the following is true
        # 1. There is sneak info
        # 2. Sneak info is not 'You smell blood' (as that would be true anyway)
        # 3. It is not the same as the visible info (To avoid double 'hear faint drops')
        if vis_info:
            if sne_info and sne_info != 'You smell blood' and vis_info != sne_info:
                info = '{}, and {}'.format(info, sne_info.lower())
            else:
                info = vis_info
        else:
            info = sne_info

        if info:
            client.send_ooc(info + '.')

        ###########
        # If there are blood trails in the area, send notification if one of the following is true
        ## 1. You are staff
        ## 2. Lights are on and you are not blind.
        ## If the blood in the area is smeared, just indicate there is smeared blood for non-staff
        ## and the regular blood trail message with extra text for staff.
        # If neither is true, send 'smell' notification as long as the following is true:
        # 1. Lights turned off or you are blind
        # 2. A notification was not sent in the previous part

        normal_visibility = changed_visibility and area.lights and not client.is_blind
        if client.is_staff() or normal_visibility:
            start_connector = '(X) ' if not normal_visibility else ''
            smeared_connector = 'smeared ' if client.is_staff(
            ) and area.blood_smeared else ''

            if not client.is_staff() and area.blood_smeared:
                client.send_ooc(
                    '{}You spot some smeared blood in the area.'.format(
                        start_connector))
            elif area.bleeds_to == set([area.name]):
                client.send_ooc('{}You spot some {}blood in the area.'.format(
                    start_connector, smeared_connector))
            elif len(area.bleeds_to) > 1:
                bleed_to_areas = list(area.bleeds_to - set([area.name]))
                if client.is_staff() and area.blood_smeared:
                    start_connector = '(X) '  # Force staff indication

                info = ('{}You spot a {}blood trail leading to {}.'.format(
                    start_connector, smeared_connector,
                    Constants.cjoin(bleed_to_areas, the=True)))
                client.send_ooc(info)
        elif not client.is_staff() and (area.bleeds_to or
                                        area.blood_smeared) and changed_area:
            if not info:
                client.send_ooc('You smell blood.')
示例#21
0
    def check_change_area(self,
                          area,
                          override_passages=False,
                          override_effects=False,
                          more_unavail_chars=None):
        """
        Perform all checks that would prevent an area change.
        Right now there is, (in this order)
        * In target area already.
        * If existing handicap has not expired.
        * If moving while sneaking to lobby/private area.
        * If target area has some lock player has no perms for.
        * If target area is unreachable from the current one.
        * If no available characters in the new area.
        ** In this check a new character is selected if there is a character conflict too.
           However, the change is not performed in this portion of code.

        No send_oocs commands are meant to be put here, so as to avoid unnecessary
        notifications. Append any intended messages to the captured_messages list and then
        manually send them out outside this function.
        """

        client = self.client
        captured_messages = list()

        # Obvious check first
        if client.area == area:
            raise ClientError('User is already in target area.',
                              code='ChArInArea')

        # Check if player has waited a non-zero movement delay
        if not client.is_staff(
        ) and client.is_movement_handicapped and not override_effects:
            start, length, name, _ = client.server.tasker.get_task_args(
                client, ['as_handicap'])
            _, remain_text = Constants.time_remaining(start, length)
            raise ClientError(
                "You are still under the effects of movement handicap '{}'. "
                "Please wait {} before changing areas.".format(
                    name, remain_text),
                code='ChArHandicap')

        # Check if trying to move to a lobby/private area while sneaking
        if area.lobby_area and not client.is_visible and not client.is_mod and not client.is_cm:
            raise ClientError(
                'Lobby areas do not let non-authorized users remain sneaking. Please '
                'change music, speak IC or ask a staff member to reveal you.',
                code='ChArSneakLobby')
        if area.private_area and not client.is_visible:
            raise ClientError(
                'Private areas do not let sneaked users in. Please change the '
                'music, speak IC or ask a staff member to reveal you.',
                code='ChArSneakPrivate')

        # Check if area has some sort of lock
        if not client.ipid in area.invite_list:
            if area.is_locked and not client.is_staff():
                raise ClientError('That area is locked.', code='ChArLocked')
            if area.is_gmlocked and not client.is_mod and not client.is_gm:
                raise ClientError('That area is gm-locked.',
                                  code='ChArGMLocked')
            if area.is_modlocked and not client.is_mod:
                raise ClientError('That area is mod-locked.',
                                  code='ChArModLocked')

        # Check if trying to reach an unreachable area
        if not (client.is_staff() or client.is_transient or override_passages
                or area.name in client.area.reachable_areas
                or '<ALL>' in client.area.reachable_areas):
            info = (
                'Selected area cannot be reached from your area without authorization. '
                'Try one of the following areas instead: ')
            if client.area.reachable_areas == {client.area.name}:
                info += '\r\n*No areas available.'
            else:
                get_name = client.server.area_manager.get_area_by_name
                try:
                    sorted_areas = sorted(client.area.reachable_areas,
                                          key=lambda x: get_name(x).id)
                    for reachable_area in sorted_areas:
                        if reachable_area != client.area.name:
                            area_id = client.server.area_manager.get_area_by_name(
                                reachable_area).id
                            info += '\r\n*({}) {}'.format(
                                area_id, reachable_area)
                except AreaError:
                    #When would you ever execute this piece of code is beyond me, but meh
                    info += '\r\n<ALL>'
            raise ClientError(info, code='ChArUnreachable')

        # Check if current character is taken in the new area
        new_char_id = client.char_id
        if not area.is_char_available(client.char_id,
                                      allow_restricted=client.is_staff(),
                                      more_unavail_chars=more_unavail_chars):
            try:
                new_char_id = area.get_rand_avail_char_id(
                    allow_restricted=client.is_staff(),
                    more_unavail_chars=more_unavail_chars)
            except AreaError:
                raise ClientError('No available characters in that area.',
                                  code='ChArNoCharacters')

        return new_char_id, captured_messages
示例#22
0
 def load_backgrounds(self):
     with Constants.fopen('config/backgrounds.yaml', 'r', encoding='utf-8') as bgs:
         self.backgrounds = Constants.yaml_load(bgs)
示例#23
0
    def net_cmd_ct(self, args: List[str]):
        """ OOC Message

        CT#<name:string>#<message:string>#%

        """

        pargs = self.process_arguments('CT', args)
        username, message = pargs['username'], pargs['message']

        # Trim out any leading/trailing whitespace characters up to a chain of spaces
        username = Constants.trim_extra_whitespace(username)
        message = Constants.trim_extra_whitespace(message)

        if self.client.is_ooc_muted:  # Checks to see if the client has been muted by a mod
            self.client.send_ooc("You have been muted by a moderator.")
            return
        if username == '' or not self.client.is_valid_name(username):
            self.client.send_ooc('You must insert a name with at least one letter.')
            return
        if username.startswith(' '):
            self.client.send_ooc('You must insert a name that starts with a letter.')
            return
        if Constants.contains_illegal_characters(username):
            self.client.send_ooc('Your name contains an illegal character.')
            return
        if (Constants.decode_ao_packet([self.server.config['hostname']])[0] in username
            or '$G' in username):
            self.client.send_ooc('That name is reserved.')
            return

        # After this the name is validated
        self.client.publish_inbound_command('CT', pargs)
        self.client.name = username

        if message.startswith('/'):
            spl = message[1:].split(' ', 1)
            cmd = spl[0]
            arg = ''
            if len(spl) == 2:
                arg = spl[1][:1024]
            arg = Constants.trim_extra_whitespace(arg)  # Do it again because args may be weird
            try:
                called_function = 'ooc_cmd_{}'.format(cmd)
                function = None  # Double assignment to check if it matched to a function later
                function = getattr(self.server.commands, called_function)
            except AttributeError:
                try:
                    function = getattr(self.server.commands_alt, called_function)
                except AttributeError:
                    self.client.send_ooc(f'Invalid command `{cmd}`.')

            if function:
                try:
                    function(self.client, arg)
                except TsuserverException as ex:
                    if ex.message:
                        self.client.send_ooc(ex)
                    else:
                        self.client.send_ooc(type(ex).__name__)
        else:
            # Censor passwords if accidentally said without a slash in OOC
            for password in self.server.all_passwords:
                for login in ['login ', 'logincm ', 'loginrp ', 'logingm ']:
                    if login + password in args[1]:
                        message = message.replace(password, '[CENSORED]')
            if self.client.disemvowel:  # If you are disemvoweled, replace string.
                message = Constants.disemvowel_message(message)
            if self.client.disemconsonant:  # If you are disemconsonanted, replace string.
                message = Constants.disemconsonant_message(message)
            if self.client.remove_h:  # If h is removed, replace string.
                message = Constants.remove_h_message(message)

            for client in self.client.area.clients:
                client.send_ooc(message, username=self.client.name)
            self.client.last_ooc_message = args[1]
            logger.log_server('[OOC][{}][{}][{}]{}'
                              .format(self.client.area.id, self.client.get_char_name(),
                                      self.client.name, message), self.client)
        self.client.last_active = Constants.get_time()
示例#24
0
    def load_config(self):
        with Constants.fopen('config/config.yaml', 'r', encoding='utf-8') as cfg:
            self.config = Constants.yaml_load(cfg)
            self.config['motd'] = self.config['motd'].replace('\\n', ' \n')
            self.all_passwords = list()
            # Mandatory passwords must be present in the configuration file. If they are not,
            # a server error will be raised.
            mandatory_passwords = ['modpass', 'cmpass', 'gmpass']
            for password in mandatory_passwords:
                if not (password not in self.config or not str(self.config[password])):
                    self.all_passwords.append(self.config[password])
                else:
                    err = (f'Password "{password}" is not defined in server/config.yaml. Please '
                           f'make sure it is set and try again.')
                    raise ServerError(err)

            # Daily (and guard) passwords are handled differently. They may optionally be left
            # blank or be not available. What this means is the server does not want a daily
            # password for that day (or a guard password)
            optional_passwords = ['guardpass'] + [f'gmpass{i}' for i in range(1, 8)]
            for password in optional_passwords:
                if not (password not in self.config or not str(self.config[password])):
                    self.all_passwords.append(self.config[password])
                else:
                    self.config[password] = None

        # Default values to fill in config.yaml if not present
        defaults_for_tags = {
            'utc_offset': 'local',
            'discord_link': None,
            'max_numdice': 20,
            'max_numfaces': 11037,
            'max_modifier_length': 12,
            'max_acceptable_term': 22074,
            'def_numdice': 1,
            'def_numfaces': 6,
            'def_modifier': '',
            'blackout_background': 'Blackout_HD',
            'default_area_description': 'No description.',
            'party_lights_timeout': 10,
            'show_ms2-prober': True,
            'showname_max_length': 30,
            'sneak_handicap': 5,
            'spectator_name': 'SPECTATOR',
            'music_change_floodguard': {'times_per_interval': 1,
                                        'interval_length': 0,
                                        'mute_length': 0}}

        for (tag, value) in defaults_for_tags.items():
            if tag not in self.config:
                self.config[tag] = value

        # Check that all passwords were generated are unique
        passwords = ['guardpass',
                     'modpass',
                     'cmpass',
                     'gmpass',
                     'gmpass1',
                     'gmpass2',
                     'gmpass3',
                     'gmpass4',
                     'gmpass5',
                     'gmpass6',
                     'gmpass7']

        for (i, password1) in enumerate(passwords):
            for (j, password2) in enumerate(passwords):
                if i != j and self.config[password1] == self.config[password2] != None:
                    info = ('Passwords "{}" and "{}" in server/config.yaml match. '
                            'Please change them so they are different and try again.'
                            .format(password1, password2))
                    raise ServerError(info)
示例#25
0
        def __init__(self, area_id, server, parameters):
            """
            Parameters
            ----------
            area_id: int
                The area ID.
            server: server.TsuserverDR
                The server this area belongs to.
            parameters: dict
                Area parameters as specified in the loaded area list.
            """

            self.clients = set()
            self.invite_list = {}
            self.id = area_id
            self.server = server
            self.music_looper = None
            self.next_message_time = 0
            self.hp_def = 10
            self.hp_pro = 10
            self.doc = 'No document.'
            self.status = 'IDLE'
            self.judgelog = []
            self.shoutlog = []
            self.current_music = ''
            self.current_music_player = ''
            self.evi_list = EvidenceList()
            self.is_recording = False
            self.recorded_messages = []
            self.owned = False
            self.ic_lock = False
            self.is_locked = False
            self.is_gmlocked = False
            self.is_modlocked = False
            self.bleeds_to = set()
            self.blood_smeared = False
            self.lights = True
            self.last_ic_messages = list()
            self.parties = set()
            self.dicelog = list()
            self._in_zone = None

            self.name = parameters['area']
            self.background = parameters['background']
            self.bg_lock = parameters['bglock']
            self.evidence_mod = parameters['evidence_mod']
            self.locking_allowed = parameters['locking_allowed']
            self.iniswap_allowed = parameters['iniswap_allowed']
            self.rp_getarea_allowed = parameters['rp_getarea_allowed']
            self.rp_getareas_allowed = parameters['rp_getareas_allowed']
            self.rollp_allowed = parameters['rollp_allowed']
            self.reachable_areas = parameters['reachable_areas']
            self.change_reachability_allowed = parameters[
                'change_reachability_allowed']
            self.default_change_reachability_allowed = parameters[
                'change_reachability_allowed']
            self.gm_iclock_allowed = parameters['gm_iclock_allowed']
            self.afk_delay = parameters['afk_delay']
            self.afk_sendto = parameters['afk_sendto']
            self.lobby_area = parameters['lobby_area']
            self.private_area = parameters['private_area']
            self.scream_range = parameters['scream_range']
            self.restricted_chars = parameters['restricted_chars']
            self.default_description = parameters['default_description']
            self.has_lights = parameters['has_lights']
            self.cbg_allowed = parameters['cbg_allowed']
            self.song_switch_allowed = parameters['song_switch_allowed']
            self.bullet = parameters['bullet']

            # Store the current description separately from the default description
            self.description = self.default_description
            # Have a background backup in order to restore temporary background changes
            self.background_backup = self.background
            # Fix comma-separated entries
            self.reachable_areas = Constants.fix_and_setify(
                self.reachable_areas)
            self.scream_range = Constants.fix_and_setify(self.scream_range)
            self.restricted_chars = Constants.fix_and_setify(
                self.restricted_chars)

            self.default_reachable_areas = self.reachable_areas.copy()
            self.staffset_reachable_areas = self.reachable_areas.copy()

            if '<ALL>' not in self.reachable_areas:
                self.reachable_areas.add(self.name)  # Safety feature, yay sets

            # Make sure only characters that exist are part of the restricted char set
            try:
                for char_name in self.restricted_chars:
                    self.server.char_list.index(char_name)
            except ValueError:
                info = (
                    'Area `{}` has a character `{}` not in the character list of the server '
                    'listed as a restricted character. Please make sure this character exists '
                    'and try again.'.format(self.name, char_name))
                raise AreaError(info)
示例#26
0
    def load_commandhelp(self):
        with Constants.fopen('README.md', 'r', encoding='utf-8') as readme:
            lines = [x.rstrip() for x in readme.readlines()]

        self.linetorank = {
            '### User Commands': 'normie',
            '### GM Commands': 'gm',
            '### Community Manager Commands': 'cm',
            '### Moderator Commands': 'mod'}

        self.commandhelp = {
            'normie': dict(),
            'gm': dict(),
            'cm': dict(),
            'mod': dict()}

        # Look for the start of the command list
        try:
            start_index = lines.index('## Commands')
            end_index = lines.index('### Debug commands')
        except ValueError as error:
            error_mes = ", ".join([str(s) for s in error.args])
            message = ('Unable to generate help based on README.md: {}. Are you sure you have the '
                       'latest README.md?'.format(error_mes))
            raise ServerError(message)

        rank = None
        current_command = None

        for line in lines[start_index:end_index]:
            # Check if empty line
            if not line:
                continue

            # Check if this line defines the rank we are taking a look at right now
            if line in self.linetorank.keys():
                rank = self.linetorank[line]
                current_command = None
                continue

            # Otherwise, check if we do not have a rank yet
            if rank is None:
                continue

            # Otherwise, check if this is the start of a command
            if line[0] == '*':
                # Get the command name
                command_split = line[4:].split('** ')
                if len(command_split) == 1:
                    # Case: * **version**
                    current_command = command_split[0][:-2]
                else:
                    # Case: * **uninvite** "ID/IPID"
                    current_command = command_split[0]

                formatted_line = '/{}'.format(line[2:])
                formatted_line = formatted_line.replace('**', '')
                self.commandhelp[rank][current_command] = [formatted_line]
                continue

            # Otherwise, line is part of command description, so add it to its current command desc
            #     - Unlocks your area, provided the lock came as a result of /lock.
            # ... assuming we have a command
            if current_command:
                self.commandhelp[rank][current_command].append(line[4:])
                continue

            # Otherwise, we have a line that is a description of the rank
            # Do nothing about them
            continue # Not really needed, but made explicit
示例#27
0
    def net_cmd_ms(self, args):
        """ IC message.

        Refer to the implementation for details.

        """

        if self.client.is_muted:  # Checks to see if the client has been muted by a mod
            self.client.send_ooc("You have been muted by a moderator.")
            return
        if self.client.area.ic_lock and not self.client.is_staff():
            self.client.send_ooc(
                "IC chat in this area has been locked by a moderator.")
            return
        if not self.client.area.can_send_message():
            return
        pargs = self.process_arguments('ms', args)
        if not pargs:
            return

        # First, check if the player just sent the same message with the same character and did
        # not receive any other messages in the meantime.
        # This helps prevent record these messages and retransmit it to clients who may want to
        # filter these out
        if (pargs['text'] == self.client.last_ic_raw_message
                and self.client.last_ic_received_mine
                and self.client.get_char_name() == self.client.last_ic_char):
            return

        if not self.client.area.iniswap_allowed:
            if self.client.area.is_iniswap(self.client, pargs['pre'],
                                           pargs['anim'], pargs['folder']):
                self.client.send_ooc("Iniswap is blocked in this area.")
                return
        if pargs[
                'folder'] in self.client.area.restricted_chars and not self.client.is_staff(
                ):
            self.client.send_ooc('Your character is restricted in this area.')
            return
        if pargs['msg_type'] not in ('chat', '0', '1'):
            return
        if pargs['anim_type'] not in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10):
            return
        if pargs['cid'] != self.client.char_id:
            return
        if pargs['sfx_delay'] < 0:
            return
        if pargs['button'] not in (0, 1, 2, 3, 4, 5, 6, 7):  # Shouts
            return
        if pargs[
                'button'] > 0 and not self.client.area.bullet and not self.client.is_staff(
                ):
            self.client.send_ooc('Bullets are disabled in this area.')
            return
        if pargs['evidence'] < 0:
            return
        if pargs['ding'] not in (0, 1, 2, 3, 4, 5, 6, 7):  # Effects
            return
        if pargs['color'] not in (0, 1, 2, 3, 4, 5, 6):
            return
        if pargs[
                'color'] == 5 and not self.client.is_mod and not self.client.is_cm:
            pargs['color'] = 0
        if pargs['color'] == 6:
            # Remove all unicode to prevent now yellow text abuse
            pargs['text'] = re.sub(r'[^\x00-\x7F]+', ' ', pargs['text'])
            if len(pargs['text'].strip(' ')) == 1:
                pargs['color'] = 0
            else:
                if pargs['text'].strip(' ') in ('<num>', '<percent>',
                                                '<dollar>', '<and>'):
                    pargs['color'] = 0
        if self.client.pos:
            pargs['pos'] = self.client.pos
        else:
            if pargs['pos'] not in ('def', 'pro', 'hld', 'hlp', 'jud', 'wit'):
                return
        self.client.pos = pargs['pos']

        # At this point, the message is guaranteed to be sent
        # First, update last raw message sent *before* any transformations. That is so that the
        # server can accurately ignore client sending the same message over and over again
        self.client.last_ic_raw_message = pargs['text']
        self.client.last_ic_char = self.client.get_char_name()

        # Truncate and alter message if message effect is in place
        raw_msg = pargs['text'][:256]
        msg = raw_msg
        if self.client.gimp:  #If you are gimped, gimp message.
            msg = Constants.gimp_message()
        if self.client.disemvowel:  #If you are disemvoweled, replace string.
            msg = Constants.disemvowel_message(msg)
        if self.client.disemconsonant:  #If you are disemconsonanted, replace string.
            msg = Constants.disemconsonant_message(msg)
        if self.client.remove_h:  #If h is removed, replace string.
            msg = Constants.remove_h_message(msg)

        gag_replaced = False
        if self.client.is_gagged:
            allowed_starters = ('(', '*', '[')
            if msg != ' ' and not msg.startswith(allowed_starters):
                gag_replaced = True
                msg = Constants.gagged_message()
            if msg != raw_msg:
                self.client.send_ooc_others(
                    '(X) {} [{}] tried to say `{}` but is currently gagged.'.
                    format(self.client.displayname, self.client.id, raw_msg),
                    is_zstaff_flex=True,
                    in_area=True)

        # Censor passwords if login command accidentally typed in IC
        for password in self.server.all_passwords:
            for login in ['login ', 'logincm ', 'loginrp ', 'logingm ']:
                if login + password in msg:
                    msg = msg.replace(password, '[CENSORED]')

        if pargs['evidence']:
            evidence_position = self.client.evi_list[pargs['evidence']] - 1
            if self.client.area.evi_list.evidences[
                    evidence_position].pos != 'all':
                self.client.area.evi_list.evidences[
                    evidence_position].pos = 'all'
                self.client.area.broadcast_evidence_list()

        # If client has GlobalIC enabled, set area range target to intended range and remove
        # GlobalIC prefix if needed.
        if self.client.multi_ic is None or not msg.startswith(
                self.client.multi_ic_pre):
            area_range = range(self.client.area.id, self.client.area.id + 1)
        else:
            # As msg.startswith('') is True, this also accounts for having no required prefix.
            start, end = self.client.multi_ic[
                0].id, self.client.multi_ic[1].id + 1
            start_area = self.server.area_manager.get_area_by_id(start)
            end_area = self.server.area_manager.get_area_by_id(end - 1)
            area_range = range(start, end)

            truncated_msg = msg.replace(self.client.multi_ic_pre, '', 1)
            if start != end - 1:
                self.client.send_ooc(
                    'Sent global IC message "{}" to areas {} through {}.'.
                    format(truncated_msg, start_area.name, end_area.name))
            else:
                self.client.send_ooc(
                    'Sent global IC message "{}" to area {}.'.format(
                        truncated_msg, start_area.name))

        pargs['msg'] = msg
        pargs['evidence'] = self.client.evi_list[pargs['evidence']]
        pargs[
            'showname'] = ''  # Dummy value, actual showname is computed later

        # Compute pairs
        # Based on tsuserver3.3 code
        # Only do this if character is paired, which would only happen for AO 2.6+ clients

        # Handle AO 2.8 logic
        # AO 2.8 sends their charid_pair in slightly longer format (\d+\^\d+)
        # The first bit corresponds to the proper charid_pair, the latter one to whether
        # the character should appear in front or behind the pair. We still want to extract
        # charid_pair so pre-AO 2.8 still see the pair; but make it so that AO 2.6 can send pair
        # messages. Thus, we 'invent' the missing arguments based on available info.
        if 'charid_pair_pair_order' in pargs:
            # AO 2.8 sender
            pargs['charid_pair'] = int(
                pargs['charid_pair_pair_order'].split('^')[0])
        elif 'charid_pair' in pargs:
            # AO 2.6 sender
            pargs['charid_pair_pair_order'] = f'{pargs["charid_pair"]}^0'
        else:
            # E.g. DRO
            pargs['charid_pair'] = -1
            pargs['charid_pair_pair_order'] = -1

        self.client.charid_pair = pargs[
            'charid_pair'] if 'charid_pair' in pargs else -1
        self.client.offset_pair = pargs[
            'offset_pair'] if 'offset_pair' in pargs else 0
        self.client.flip = pargs['flip']
        self.client.char_folder = pargs['folder']

        if pargs['anim_type'] not in (5, 6):
            self.client.last_sprite = pargs['anim']

        pargs['other_offset'] = 0
        pargs['other_emote'] = 0
        pargs['other_flip'] = 0
        pargs['other_folder'] = ''
        if 'charid_pair' not in pargs or pargs['charid_pair'] < -1:
            pargs['charid_pair'] = -1
            pargs['charid_pair_pair_order'] = -1

        if pargs['charid_pair'] > -1:
            for target in self.client.area.clients:
                if target == self.client:
                    continue
                # Check pair has accepted pair
                if target.char_id != self.client.charid_pair:
                    continue
                if target.charid_pair != self.client.char_id:
                    continue
                # Check pair is in same position
                if target.pos != self.client.pos:
                    continue

                pargs['other_offset'] = target.offset_pair
                pargs['other_emote'] = target.last_sprite
                pargs['other_flip'] = target.flip
                pargs['other_folder'] = target.char_folder
                break
            else:
                # There are no clients who want to pair with this client
                pargs['charid_pair'] = -1
                pargs['offset_pair'] = 0
                pargs['charid_pair_pair_order'] = -1

        for area_id in area_range:
            target_area = self.server.area_manager.get_area_by_id(area_id)
            for c in target_area.clients:
                c.send_ic(params=pargs,
                          sender=self.client,
                          gag_replaced=gag_replaced)

            target_area.set_next_msg_delay(len(msg))

            # Deal with shoutlog
            if pargs['button'] > 0:
                info = 'used shout {} with the message: {}'.format(
                    pargs['button'], msg)
                target_area.add_to_shoutlog(self.client, info)

        self.client.area.set_next_msg_delay(len(msg))
        logger.log_server(
            '[IC][{}][{}]{}'.format(self.client.area.id,
                                    self.client.get_char_name(), msg),
            self.client)

        # Sending IC messages reveals sneaked players
        if not self.client.is_staff() and not self.client.is_visible:
            self.client.change_visibility(True)
            self.client.send_ooc_others(
                '(X) {} [{}] revealed themselves by talking ({}).'.format(
                    self.client.displayname, self.client.id,
                    self.client.area.id),
                is_zstaff=True)

        self.server.tasker.create_task(self.client, [
            'as_afk_kick', self.client.area.afk_delay,
            self.client.area.afk_sendto
        ])
        if self.client.area.is_recording:
            self.client.area.recorded_messages.append(args)

        self.client.last_ic_message = msg
        self.client.last_active = Constants.get_time()
示例#28
0
 def dump_hdids(self):
     with Constants.fopen('storage/hd_ids.json', 'w') as whole_list:
         json.dump(self.hdid_list, whole_list)
示例#29
0
 def load_characters(self):
     with Constants.fopen('config/characters.yaml', 'r', encoding='utf-8') as chars:
         self.char_list = Constants.yaml_load(chars)
     self.build_char_pages_ao1()
示例#30
0
        def receive_command_stc(self, command_type, *args):
            command_type, *args = Constants.decode_ao_packet([command_type] + list(args))
            self.received_packets.append([command_type, tuple(args)])

            buffer = ''
            if command_type == 'decryptor':  # Hi
                buffer = 'HI#FAKEHDID#%'
            elif command_type == 'ID':  # Server ID
                buffer = "ID#DRO#1.0.0#%"
                err = ('Wrong client ID for {}.\nExpected {}\nGot {}'
                       .format(self, args[0], self.id))
                assert args[0] == str(self.id), err
            elif command_type == 'FL':  # AO 2.2.5 configs
                pass
            elif command_type == 'PN':  # Player count
                pass
            elif command_type == 'SI':  # Counts for char/evi/music
                pass
            elif command_type == 'SC':  # Character list
                # TODO: RC!!!
                pass
            elif command_type == 'SM':  # First timer music/area list
                pass
            elif command_type == 'CharsCheck':  # Available characters
                pass
            elif command_type == 'HP':  # Def/pro bar
                pass
            elif command_type == 'BN':  # Background file
                pass
            elif command_type == 'LE':  # Evidence list
                pass
            elif command_type == 'MM':  # ?????????????
                pass
            elif command_type == 'OPPASS':  # Guard pass
                pass
            elif command_type == 'DONE':  # Done setting up courtroom
                pass
            elif command_type == 'CT':  # OOC message
                self.received_ooc.append((args[0], args[1]))
            elif command_type == 'FM':  # Updated music/area list
                pass
            elif command_type == 'PV':  # Current character
                pass
            elif command_type == 'MS':  # IC message
                # 0 = msg_type
                # 1 = pre
                # 2 = folder
                # 3 = anim
                # 4 = msg
                # 5 = pos
                # 6 = sfx
                # 7 = anim_type
                # 8 = char_id
                # 9 = sfx_delay
                # 10 = button
                # 11 = self.client.evi_list[evidence]
                # 12 = flip
                # 13 = ding
                # 14 = color
                # 15 = showname
                if not (len(args) == 16):
                    raise ValueError('Malformed MS packet for an IC message {}'.format(args))
                self.received_ic.append(args)
            elif command_type == 'MC':  # Start playing track
                pass
            elif command_type == 'ZZ':  # Mod call
                pass
            elif command_type == 'GM':  # Gamemode switch
                pass
            elif command_type == 'TOD':  # Time of day switch
                pass
            elif command_type == 'ackMS':  # Acknowledge MS packet
                pass
            elif command_type == 'SN':  # Showname change
                pass
            else:
                raise KeyError('Unrecognized STC argument `{}` {}'.format(command_type, args))

            if buffer:
                self.send_command_cts(buffer)