예제 #1
0
        def add_party(self, party):
            """
            Adds a party to the area's party list.

            Parameters
            ----------
            party: server.PartyManager.Party
                Party to record.

            Raises
            ------
            AreaError:
                If the party is already a part of the party list.
            """

            if party in self.parties:
                raise AreaError(
                    'Party {} is already part of the party list of this area.'.
                    format(party.get_id()))
            self.parties.add(party)
예제 #2
0
        def remove_party(self, party):
            """
            Removes a party from the area's party list.

            Parameters
            ----------
            party: server.PartyManager.Party
                Party to record.

            Raises
            ------
            AreaError:
                If the party is not part of the party list.
            """

            if party not in self.parties:
                raise AreaError(
                    'Party {} is not part of the party list of this area.'.
                    format(party.get_id()))
            self.parties.remove(party)
예제 #3
0
def ooc_cmd_evidence(client, arg):
    """
    Use /evidence to read all evidence in the area.
    Use /evidence [evi_name/id] to read specific evidence.
    Usage: /evidence [evi_name/id]
    """
    evi_list = client.area.get_evidence_list(client)

    # Just read all area evidence
    if arg == "":
        msg = f"==Evidence in '{client.area.name}'=="
        for i, evi in enumerate(evi_list):
            # 0 = name
            # 1 = desc
            # 2 = image
            evi_msg = f"\n💼[{i+1}]: '{evi[0]}'"  # (🖼️{evi[2]})
            if arg == "" or arg.lower() in evi_msg.lower():
                msg += evi_msg
        msg += "\n\n|| Use /evidence [evi_name/id] to read specific evidence. ||"
        client.send_ooc(msg)
        return

    # Arg is not empty
    try:
        evidence = None
        for i, evi in enumerate(evi_list):
            if (arg.isnumeric()
                    and int(arg) - 1 == i) or arg.lower() == evi[0].lower():
                evidence = evi
                break
        if evidence is None:
            raise AreaError(f"Target evidence not found! (/evidence {arg})")
        msg = f"==💼[{i+1}]: '{evidence[0]}=="
        msg += f"\n🖼️Image: {evidence[2]}"
        msg += f"\n📃Desc:\n{evidence[1]}"
        msg += "\n\n|| Use /evidence to read all evidence in the area ||"
        client.send_ooc(msg)
    except ValueError:
        raise
    except (AreaError, ClientError):
        raise
예제 #4
0
def ooc_cmd_bg(client, arg: str) -> None:
    """
	Set the background of a room.
	Usage: /bg <background>
	"""
    if len(arg) == 0:
        client.send_ooc(f'Current background is "{client.area.background}".')
        return
    if not client.is_mod and client.area.bg_lock == True:
        raise AreaError("This area's background is locked")
    try:
        client.area.change_background(arg)
    except AreaError:
        msg = 'custom/'
        msg += arg
        try:
            client.area.change_cbackground(msg)
        except AreaError:
            raise
    client.area.broadcast_ooc(
        f'{client.char_name} changed the background to {arg}.')
    database.log_room('bg', client, client.area, message=arg)
예제 #5
0
        def get_rand_avail_char_id(self,
                                   allow_restricted=False,
                                   more_unavail_chars=None):
            """
            Obtain a random available character in the area.

            Parameters
            ----------
            allow_restricted: bool, optional
                Whether to include characters whose usage has been manually restricted in the area.
                Defaults to false.
            more_unavail_chars: set, optional
                Additional characters to mark as taken (and thus unsuable) in the area. Defaults to
                None.

            Returns
            -------
            int
                ID of randomly chosen available character in the area.

            Raises
            -------
            AreaError
                If there are no available characters in the area.
            """

            unusable = self.get_chars_unusable(
                allow_restricted=allow_restricted,
                more_unavail_chars=more_unavail_chars)
            available = {
                i
                for i in range(len(self.server.char_list)) if i not in unusable
            }

            if not available:
                raise AreaError('No available characters.')

            return self.server.random.choice(tuple(available))
예제 #6
0
        def change_status(self, value):
            """
            Change the casing status of the area to one of predetermined values.

            Parameters
            ----------
            value: str
                New casing status of the area.

            Raises
            ------
            AreaError
                If the new casing status is not among the allowed values.
            """

            allowed_values = [
                'idle', 'building-open', 'building-full', 'casing-open',
                'casing-full', 'recess'
            ]
            if value.lower() not in allowed_values:
                raise AreaError('Invalid status. Possible values: {}'.format(
                    ', '.join(allowed_values)))
            self.status = value.upper()
예제 #7
0
        def change_background(self, bg, validate=True, override_blind=False):
            """
            Change the background of the current area.

            Parameters
            ----------
            bg: str
                New background name.
            validate: bool, optional
                Whether to first determine if background name is listed as a server background
                before changing. Defaults to True.
            override_blind: bool, optional
                Whether to send the intended background to blind people as opposed to the server
                blackout one. Defaults to False (send blackout).

            Raises
            ------
            AreaError
                If the server attempted to validate the background name and failed.
            """

            if validate and bg.lower() not in [
                    name.lower() for name in self.server.backgrounds
            ]:
                raise AreaError('Invalid background name.')

            if self.lights:
                self.background = bg
            else:
                self.background = self.server.config['blackout_background']
                self.background_backup = bg
            for c in self.clients:
                if c.is_blind and not override_blind:
                    c.send_background(
                        name=self.server.config['blackout_background'])
                else:
                    c.send_background(name=self.background)
예제 #8
0
    def get_area_by_id(self, area_id: int) -> AreaManager.Area:
        """
        Return the Area object corresponding to the area that has the given ID.

        Parameters
        ----------
        area_id: int
            Area ID to look for.

        Returns
        -------
        AreaManager.Area
            Area.

        Raises
        ------
        AreaError
            If no area has the given ID.
        """

        for area in self.areas:
            if area.id == area_id:
                return area
        raise AreaError('Area not found.')
예제 #9
0
    def get_area_by_name(self, name: str) -> AreaManager.Area:
        """
        Return the Area object corresponding to the area that has the given name.

        Parameters
        ----------
        name: str
            Area name to look for.

        Returns
        -------
        AreaManager.Area
            Area.

        Raises
        ------
        AreaError
            If no area has the given name.
        """

        for area in self.areas:
            if area.name == name:
                return area
        raise AreaError('Area not found.')
예제 #10
0
    def check_move_party(self, party, initiator, new_area):
        """
        * 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 target area contains a character from the current party.
        * If character of member is restricted in the target area

        LIGHTS
        1. No one moves
        2. Should be no
        3. Party disbands
        4. No one moves
        5. Affected user switches
        6. Party disbands
        """
        new_chars = set()
        movers = {True: dict(), False: dict()}

        # Assumes that players will only attempt to move if their sneak status matches that of the
        # initiator.
        for member in party.members:
            error = None

            # Player will only attempt to move under any of these two conditions
            # * Player and initiator are not sneaking
            # * Player and initiator are both sneaking
            if initiator.is_visible:
                attempt_move = member.is_visible
            else:
                attempt_move = not member.is_visible

            if attempt_move:
                try:
                    is_restricted = (member.get_char_name()
                                     in new_area.restricted_chars)
                    if is_restricted and not member.is_staff():
                        error = AreaError('', code='ChArRestrictedChar')
                        raise error

                    new_cid, _ = member.check_change_area(
                        new_area, more_unavail_chars=new_chars)
                    new_chars.add(new_cid)
                except (ClientError, AreaError) as ex:
                    error = ex
                    new_cid = member.char_id
            else:
                new_cid = member.char_id

            if error:
                if error.code in [
                        'ChArHandicap', 'ChArSneakLobby', 'ChArSneakPrivate',
                        'ChArUnreachable', 'ChrNoAvailableCharacters'
                ]:
                    member.send_ooc(error.message)
                    culprit = member.displayname if member != initiator else 'yourself'
                    raise ClientError(
                        'Unable to move the party due to {}.'.format(culprit))

                if error.code in [
                        'ChArLocked', 'ChArGMLocked', 'ChArModLocked',
                        'ChArRestrictedChar', 'ChArInArea'
                ]:
                    movers[False][member] = new_cid
                elif error.code is not None:
                    raise error
                else:
                    movers[True][member] = new_cid
            else:
                movers[attempt_move][member] = new_cid

        return movers
예제 #11
0
    def load(self, path='config/areas.yaml', hub_id=-1):
        try:
            with open(path, 'r', encoding='utf-8') as stream:
                hubs = yaml.safe_load(stream)
        except:
            raise AreaError(f'File path {path} is invalid!')

        if hub_id != -1:
            try:
                self.hubs[hub_id].load(hubs[hub_id], destructive=True)
            except ValueError:
                raise AreaError(
                    f'Invalid Hub ID {hub_id}! Please contact the server host.'
                )
            return

        if 'area' in hubs[0]:
            # Legacy support triggered! Abort operation
            if len(self.hubs) <= 0:
                self.hubs.append(AreaManager(self, f'Hub 0'))
            self.hubs[0].load_areas(hubs)

            is_dr_hub = False
            # tsuserverDR conversion hell
            for i, area in enumerate(hubs):
                # oh God why did they do it this way
                if 'reachable_areas' in area:
                    reachable_areas = area['reachable_areas'].split(',')
                    # I hate this
                    for a_name in reachable_areas:
                        a_name = a_name.strip()
                        target_area = self.hubs[0].get_area_by_name(
                            a_name, case_sensitive=True)
                        self.hubs[0].areas[i].link(target_area.id)
                        print(
                            f'[tsuDR conversion] Linking area {self.hubs[0].areas[i].name} to {target_area.name}'
                        )
                        is_dr_hub = True
                if 'default_description' in area:
                    self.hubs[0].areas[i].desc = area['default_description']
                if 'song_switch_allowed' in area:
                    self.hubs[0].areas[i].can_dj = area['song_switch_allowed']
            if is_dr_hub:
                self.hubs[0].arup_enabled = False
                print(
                    f'[tsuDR conversion] Setting hub 0 ARUP to False due to TsuserverDR yaml supplied. Please use /save_hub as a mod to adapt the areas.yaml to KFO style.'
                )
            return

        i = 0
        for hub in hubs:
            while len(self.hubs) < len(hubs):
                # Make sure that the hub manager contains enough hubs to update with new information
                self.hubs.append(AreaManager(self, f'Hub {len(self.hubs)}'))
            while len(self.hubs) > len(hubs):
                # Clean up excess hubs
                h = self.hubs.pop()
                clients = h.clients.copy()
                for client in clients:
                    client.set_area(self.default_hub().default_area())

            self.hubs[i].load(hub)
            self.hubs[i].o_name = self.hubs[i].name
            self.hubs[i].o_abbreviation = self.hubs[i].abbreviation
            i += 1
예제 #12
0
 def get_hub_by_abbreviation(self, abbr):
     """Get a hub by abbreviation."""
     for hub in self.hubs:
         if hub.abbreviation.lower() == abbr.lower():
             return hub
     raise AreaError('Hub not found.')
예제 #13
0
 def get_hub_by_id(self, num):
     """Get a hub by ID."""
     for hub in self.hubs:
         if hub.id == num:
             return hub
     raise AreaError('Hub not found.')
예제 #14
0
        def __init__(self, area_id, server, parameters):
            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.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.lights = True

            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.description = self.default_description  # Store the current description separately from the default description
            self.background_backup = self.background  # Used for restoring temporary background changes
            # Fix comma-separated entries
            self.reachable_areas = fix_and_setify(self.reachable_areas)
            self.scream_range = fix_and_setify(self.scream_range)
            self.restricted_chars = 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 an unrecognized character {} as a restricted character. '
                    'Please make sure this character exists and try again.'.
                    format(self.name, char_name))
                raise AreaError(info)
예제 #15
0
 def get_area_by_name(self, name):
     """Get an area by name."""
     for area in self.areas:
         if area.name == name:
             return area
     raise AreaError('Area not found.')
예제 #16
0
        def change_status(self, value):
            """
			Set the status of the room.
			:param value: status code
			"""
            allowed_values = ('idle', 'rp', 'casing', 'looking-for-players',
                              'lfp', 'recess', 'gaming')
            if value.lower() not in allowed_values:
                raise AreaError(
                    f'Invalid status. Possible values: {", ".join(allowed_values)}'
                )
            if value.lower() == 'lfp':
                value = 'looking-for-players'
            self.status = value.upper()
            if self.sub:
                if self.hub.hubtype == 'arcade' or self.hub.hubtype == 'courtroom':
                    if value == 'looking-for-players':
                        self.hub.status = value.upper()
                    else:
                        lfp = False
                        idle = True
                        recess = True
                        for area in self.hub.subareas:
                            if area.status == 'LOOKING-FOR-PLAYERS':
                                lfp = True
                            if area.status != 'IDLE':
                                idle = False
                            if area.status == 'RP' or area.status == 'CASING' or area.status == 'GAMING':
                                recess = False
                        if lfp == False and not value.lower(
                        ) == 'idle' and not value.lower() == 'recess':
                            self.hub.status = value.upper()
                        if value.lower() == 'idle' and idle == True:
                            self.hub.status = value.upper()
                        if value.lower() == 'recess' and recess == True:
                            self.hub.status = value.upper()
                        if self.hub.status == 'LOOKING-FOR-PLAYERS' and value.lower(
                        ) == 'recess' or self.hub.status == 'LOOKING-FOR-PLAYERS' and value.lower(
                        ) == 'idle':
                            if lfp == False:
                                for area in self.hub.subareas:
                                    if area.status == 'CASING':
                                        self.hub.status = 'CASING'
                                        break
                                    elif area.status == 'GAMING':
                                        self.hub.status = 'GAMING'
                                        break
                                    elif area.status == 'RP':
                                        self.hub.status = 'RP'
                                        break
                    self.server.area_manager.send_arup_status()
                if self.is_restricted:
                    self.conn_arup_status()
                else:
                    self.hub.sub_arup_status()

            elif self.is_hub:
                self.sub_arup_status()
                self.server.area_manager.send_arup_status()
            else:
                self.server.area_manager.send_arup_status()
예제 #17
0
 def get_area_by_abbreviation(self, abbr):
     """Get an area by abbreviation."""
     for area in self.areas:
         if area.abbreviation == abbr:
             return area
     raise AreaError('Area not found.')
예제 #18
0
파일: areas.py 프로젝트: Chrezm/TsuserverDR
    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 change_lights(self, new_lights, initiator=None):
            status = {True: 'on', False: 'off'}

            if new_lights:
                if self.background == self.server.config[
                        'blackout_background']:
                    intended_background = self.background_backup
                else:
                    intended_background = self.background
            else:
                if self.background != self.server.config['blackout_background']:
                    self.background_backup = self.background
                intended_background = self.server.config['blackout_background']

            try:
                self.change_background(intended_background)
            except AreaError:
                raise AreaError(
                    'Unable to turn lights {}: Background {} not found'.format(
                        status[new_lights], intended_background))

            self.lights = new_lights

            if initiator:  # If a player initiated the change light sequence, send targeted messages
                initiator.send_host_message('You turned the lights {}.'.format(
                    status[new_lights]))
                self.server.send_all_cmd_pred(
                    'CT',
                    '{}'.format(self.server.config['hostname']),
                    'The lights were turned {}.'.format(status[new_lights]),
                    pred=lambda c: not c.is_staff(
                    ) and c.area == self and c != initiator)
                self.server.send_all_cmd_pred(
                    'CT',
                    '{}'.format(self.server.config['hostname']),
                    '{} turned the lights {}.'.format(
                        initiator.get_char_name(), status[new_lights]),
                    pred=lambda c: c.is_staff(
                    ) and c.area == self and c != initiator)
            else:  # Otherwise, send generic message
                self.send_host_message('The lights were turned {}.'.format(
                    status[new_lights]))

            # Reveal people bleeding and not sneaking if lights were turned on
            if self.lights:
                for c in self.clients:
                    bleeding_visible = [
                        x for x in self.clients
                        if x.is_visible and x.is_bleeding and x != c
                    ]
                    info = ''

                    if len(bleeding_visible) == 1:
                        info = 'You now see {} is bleeding.'.format(
                            bleeding_visible[0].get_char_name())
                    elif len(bleeding_visible) > 1:
                        info = 'You now see {}'.format(
                            bleeding_visible[0].get_char_name())
                        for i in range(1, len(bleeding_visible) - 1):
                            info += ', {}'.format(
                                bleeding_visible[i].get_char_name())
                        info += ' and {} are bleeding.'.format(
                            bleeding_visible[-1].get_char_name())

                    if info:
                        c.send_host_message(info)
예제 #20
0
 def get_rand_avail_char_id(self):
     avail_set = set(range(len(self.server.char_list))) - set(
         [x.char_id for x in self.clients])
     if len(avail_set) == 0:
         raise AreaError('No available characters.')
     return random.choice(tuple(avail_set))
예제 #21
0
 def change_background(self, bg):
     if bg.lower() not in (name.lower()
                           for name in self.server.backgrounds):
         raise AreaError('Invalid background name.')
     self.background = bg
     self.send_command('BN', self.background)
예제 #22
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)
예제 #23
0
 def get_area_by_id(self, num):
     """Get an area by ID."""
     for area in self.areas:
         if area.id == num:
             return area
     raise AreaError('Area not found.')
예제 #24
0
 def unlink(self, target):
     try:
         del self.links[str(target)]
     except KeyError:
         raise AreaError(
             f'Link {target} does not exist in Area {self.name}!')
예제 #25
0
 def get_sub(self, name):
     for area in self.subareas:
         if area.name == name:
             return area
     raise AreaError('Area not found.')
예제 #26
0
    def load_areas(self, area_list_file='config/areas.yaml'):
        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
        try:
            with open(area_list_file, 'r') as chars:
                areas = yaml.safe_load(chars)
        except FileNotFoundError:
            info = 'Could not find area list file {}'.format(area_list_file)
            raise FileNotFoundError(info)

        # Create the areas
        for item in areas:
            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)
            if 'bglock' not in item:
                item['bglock'] = False
            if 'evidence_mod' not in item:
                item['evidence_mod'] = 'FFA'
            if 'locking_allowed' not in item:
                item['locking_allowed'] = False
            if 'iniswap_allowed' not in item:
                item['iniswap_allowed'] = True
            if 'rp_getarea_allowed' not in item:
                item['rp_getarea_allowed'] = True
            if 'rp_getareas_allowed' not in item:
                item['rp_getareas_allowed'] = True
            if 'rollp_allowed' not in item:
                item['rollp_allowed'] = True
            if 'reachable_areas' not in item:
                item['reachable_areas'] = '<ALL>'
            if 'change_reachability_allowed' not in item:
                item['change_reachability_allowed'] = True
            if 'gm_iclock_allowed' not in item:
                item['gm_iclock_allowed'] = True
            if 'afk_delay' not in item:
                item['afk_delay'] = 0
            if 'afk_sendto' not in item:
                item['afk_sendto'] = 0
            if 'lobby_area' not in item:
                item['lobby_area'] = False
            if 'private_area' not in item:
                item['private_area'] = False
            if 'scream_range' not in item:
                item['scream_range'] = ''
            if 'restricted_chars' not in item:
                item['restricted_chars'] = ''
            if 'default_description' not in item:
                item['default_description'] = self.server.config[
                    'default_area_description']
            if 'has_lights' not in item:
                item['has_lights'] = True

            # Backwards compatibility notice
            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 = (
                    'Unexpected duplicated area names in area list: {}. '
                    'Please rename the duplicated areas and try again.'.format(
                        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_reachable_area_names.union(
                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 = (
                'Unrecognized area names defined as a reachable area in area list file: {}. '
                '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
        self.areas = temp_areas
        self.area_names = temp_area_names

        # 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

        # Move existing clients to new corresponding area (or to default area if their previous area no longer exists)
        for client in self.server.client_manager.clients:
            try:
                new_area = self.get_area_by_name(client.area.name)
                client.change_area(new_area, override_all=True)
                client.send_host_message('Moving you to new area {}'.format(
                    new_area.name))
            except AreaError:
                client.change_area(self.default_area(), override_all=True)
                client.send_host_message(
                    'Your previous area no longer exists. Moving you to default area {}'
                    .format(client.area.name))
예제 #27
0
        def change_lights(self, new_lights, initiator=None, area=None):
            """
            Change the light status of the area and send related announcements.

            This also updates the light status for parties.

            Parameters
            ----------
            new_lights: bool
                New light status
            initiator: server.ClientManager.Client, optional
                Client who triggered the light status change.
            area: server.AreaManager.Area, optional
                Broadcasts light change messages to chosen area. Used if
                the initiator is elsewhere, such as in /zone_lights.
                If not None, the initiator will receive no notifications of
                light status changes.

            Raises
            ------
            AreaError
                If the new light status matches the current one.
            """

            status = {True: 'on', False: 'off'}
            if self.lights == new_lights:
                raise AreaError('The lights are already turned {}.'.format(
                    status[new_lights]))

            # Change background to match new status
            if new_lights:
                if self.background == self.server.config[
                        'blackout_background']:
                    intended_background = self.background_backup
                else:
                    intended_background = self.background
            else:
                if self.background != self.server.config['blackout_background']:
                    self.background_backup = self.background
                intended_background = self.background

            self.lights = new_lights
            self.change_background(
                intended_background,
                validate=False)  # Allow restoring custom bg.

            # Announce light status change
            if initiator:  # If a player initiated the change light sequence, send targeted messages
                if area is None:
                    if not initiator.is_blind:
                        initiator.send_ooc('You turned the lights {}.'.format(
                            status[new_lights]))
                    elif not initiator.is_deaf:
                        initiator.send_ooc('You hear a flicker.')
                    else:
                        initiator.send_ooc(
                            'You feel a light switch was flipped.')

                initiator.send_ooc_others('The lights were turned {}.'.format(
                    status[new_lights]),
                                          is_zstaff_flex=False,
                                          in_area=area if area else True,
                                          to_blind=False)
                initiator.send_ooc_others('You hear a flicker.',
                                          is_zstaff_flex=False,
                                          in_area=area if area else True,
                                          to_blind=True,
                                          to_deaf=False)
                initiator.send_ooc_others(
                    '(X) {} [{}] turned the lights {}.'.format(
                        initiator.displayname, initiator.id,
                        status[new_lights]),
                    is_zstaff_flex=True,
                    in_area=area if area else True)
            else:  # Otherwise, send generic message
                self.broadcast_ooc('The lights were turned {}.'.format(
                    status[new_lights]))

            # Notify the parties in the area that the lights have changed
            for party in self.parties:
                party.check_lights()

            for c in self.clients:
                c.area_changer.notify_me_blood(self,
                                               changed_visibility=True,
                                               changed_hearing=False)
예제 #28
0
 def change_status(self, value):
     allowed_values = ('idle', 'building-open', 'building-full', 'casing-open', 'casing-full', 'recess')
     if value.lower() not in allowed_values:
         raise AreaError('Invalid status. Possible values: {}'.format(', '.join(allowed_values)))
     self.status = value.upper()
예제 #29
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
예제 #30
0
 def get_hub_by_name(self, name):
     """Get a hub by name."""
     for hub in self.hubs:
         if hub.name.lower() == name.lower():
             return hub
     raise AreaError('Hub not found.')