def add_jukebox_vote(self, client: ClientManager.Client, music_name: str, length: int = -1, showname: str = ''): """Cast a vote on the jukebox. Args: client (ClientManager.Client): Client that is requesting music_name (str): track name length (int, optional): length of track. Defaults to -1. showname (str, optional): showname of voter. Defaults to ''. """ if not self.jukebox: return if length <= 0: self.remove_jukebox_vote(client, False) else: self.remove_jukebox_vote(client, True) self.jukebox_votes.append( self.JukeboxVote(client, music_name, length, showname)) client.send_ooc('Your song was added to the jukebox.') if len(self.jukebox_votes) == 1: self.start_jukebox()
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)
def navigate_testimony(self, client: ClientManager.Client, command: str, index: int = None) -> bool: """ Navigate the current testimony using the commands >, <, =, and [>|<]<index>. Args: client (ClientManager.Client): requester command (str): either >, <, or = index (int): index of the statement to move to, or None Returns: bool: if the navigation was successful """ if len(self.testimony.statements) <= 1: client.send_ooc('Testimony is empty, can\'t navigate!' ) # should never happen return False if index == None: if command == '=': if self.examine_index == 0: self.examine_index = 1 elif command == '>': if len(self.testimony.statements ) <= self.examine_index + 1: self.broadcast_ooc( 'Reached end of testimony, looping...') self.examine_index = 1 else: self.examine_index = self.examine_index + 1 elif command == '<': if self.examine_index <= 1: client.send_ooc( 'Can\'t go back, already on the first statement!') return False else: self.examine_index = self.examine_index - 1 else: try: self.examine_index = int(index) except ValueError: client.send_ooc( "That does not look like a valid statement number!") return False self.send_command('MS', *self.testimony.statements[self.examine_index]) return True
def remove_statement(self, client: ClientManager.Client, index: int) -> bool: """ Remove the statement at <index>. Args: client (ClientManager.Client): requester index (int): index of the statement to remove Returns: bool: whether the statement was removed """ if client not in self.owners and (self.evidence_mod == "HiddenCM" or self.evidence_mod == "Mods"): client.send_ooc('You don\'t have permission to amend testimony in this area!') return False if self.testimony.remove_statement(index): client.send_ooc('Removed statement ' + str(index) + ' successfully.') return True else: client.send_ooc('Couldn\'t remove statement ' + str(index) + '. Are you sure it exists?') return True
def amend_testimony(self, client: ClientManager.Client, index:int, statement: list) -> bool: """ Replace the statement at <index> with a new <statement>. Args: client (ClientManager.Client): requester index (int): index of the statement to amend statement (list): the new statement Returns: bool: whether the statement was amended """ if client not in self.owners and (self.evidence_mod == "HiddenCM" or self.evidence_mod == "Mods"): client.send_ooc('You don\'t have permission to amend testimony in this area!') return False if self.testimony.amend_statement(index, statement): client.send_ooc('Amended statement ' + str(index) + ' successfully.') return True else: client.send_ooc('Couldn\'t amend statement ' + str(index) + '. Are you sure it exists?') return False
def insert_testimony(self, client: ClientManager.Client, index:int, statement: list) -> bool: """ Insert into the testimony a new <statement> after the statement at <index>. Args: client (ClientManager.Client): requester index (int): index of the statement to insert AFTER statement (list): the affected statement Returns: bool: whether the insert was successful """ if client not in self.owners and (self.evidence_mod == "HiddenCM" or self.evidence_mod == "Mods"): client.send_ooc('You don\'t have permission to amend testimony in this area!') return False if self.testimony.insert_statement(index, statement): client.send_ooc('Inserted a new statement after statement ' + str(index) + ' successfully.') return True else: client.send_ooc('Couldn\'t find statement ' + str(index) + '. Are you sure it exists?') return False
def start_examination(self, client: ClientManager.Client) -> bool: """ Start an examination of this area's testimony. Args: client (ClientManager.Client): requester Returns: bool: whether the examination was started """ if client not in self.owners and (self.evidence_mod == "HiddenCM" or self.evidence_mod == "Mods"): client.send_ooc('You don\'t have permission to start a new examination in this area!') return False elif self.is_testifying: client.send_ooc('You can\'t start an examination during a testimony! (Hint: Say \'/end\' to stop recording!)') return False elif self.is_examining: client.send_ooc('You can\'t start an examination until you finish this one!') return False self.examine_index = 0 self.is_examining = True self.send_command('RT', 'testimony2') return True
def _cleanup_removed_player(self, player: ClientManager.Client): self.listener.unsubscribe(player) # Restore their gamemode if needed (if player moved to a new zone, this zone will # be in charge of updating the gamemode) if not player.area.in_zone or player.area.in_zone == self: player.send_gamemode(name='') # Remove handicap if self.is_property('Handicap'): # Avoid double notification try: player.change_handicap(False) except ClientError: # If the player no longer had a handicap, no need to do anything # This can happen if /unhandicap was run with a client in an area part of # a zone with a handicap pass # Remove chat tick rate if self.is_property('Chat_tick_rate'): player.send_chat_tick_rate(chat_tick_rate=None)
def _add_player(self, user: ClientManager.Client): if user in self._players: raise ZoneError.PlayerConflictError( 'User is already a player in the zone.') if user.area not in self._areas: raise ZoneError.PlayerNotInZoneError( 'User is in an area not part of the zone.') self._players.add(user) self.listener.subscribe(user) user.send_gamemode(name=self.get_mode()) if self.is_property('Handicap'): length, name, announce_if_over = self.get_property('Handicap') user.change_handicap(True, length=length, name=name, announce_if_over=announce_if_over) if self.is_property('Chat_tick_rate'): chat_tick_rate = self.get_property('Chat_tick_rate') user.send_chat_tick_rate(chat_tick_rate=chat_tick_rate)
def new_client(self, client: ClientManager.Client): """Add a client to the area.""" self.clients.add(client) self.server.area_manager.send_arup_players() if client.char_id != -1: database.log_room('area.join', client, self) # Update the timers timer = self.server.area_manager.timer if timer.set: s = int(not timer.started) current_time = timer.static if timer.started: current_time = timer.target - arrow.get() int_time = int(current_time.total_seconds()) * 1000 # Unhide the timer client.send_command('TI', 0, 2) # Start the timer client.send_command('TI', 0, s, int_time) else: # Stop the timer client.send_command('TI', 0, 3, 0) # Hide the timer client.send_command('TI', 0, 1) for timer_id, timer in enumerate(self.timers): # Send static time if applicable if timer.set: s = int(not timer.started) current_time = timer.static if timer.started: current_time = timer.target - arrow.get() int_time = int(current_time.total_seconds()) * 1000 # Start the timer client.send_command('TI', timer_id + 1, s, int_time) # Unhide the timer client.send_command('TI', timer_id + 1, 2) client.send_ooc(f'Timer {timer_id+1} is at {current_time}') else: # Stop the timer client.send_command('TI', timer_id + 1, 1, 0) # Hide the timer client.send_command('TI', timer_id + 1, 3)
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)
def change_lights(self, new_lights: bool, initiator: ClientManager.Client = None, area: AreaManager.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: found_something = c.area_changer.notify_me_rp( self, changed_visibility=True, changed_hearing=False) if found_something and new_lights: c.send_ic_attention()
def notify_others_blood(self, client: ClientManager.Client, area: AreaManager.Area, char: str, status: str = 'stay', send_to_staff: bool = True): # Assume client's bleeding status is worth announcing (for example, it changed or lights on) # If bleeding, send reminder, and notify everyone in the area if not sneaking # (otherwise, just send vague message). others_bleeding = len([c for c in area.clients if c.is_bleeding and c != client]) if client.is_bleeding and (status == 'stay' or status == 'arrived'): discriminant = (others_bleeding > 0) # Check if someone was bleeding already dsh = {True: 'You start hearing more drops of blood.', False: 'You faintly start hearing drops of blood.'} dshs = {True: 'You start hearing and smelling more drops of blood.', False: 'You faintly start hearing and smelling drops of blood.'} dss = {True: 'You start smelling more blood.', False: 'You faintly start smelling blood.'} vis_status = 'now' elif ((client.is_bleeding and status == 'left') or (not client.is_bleeding and status == 'stay')): discriminant = (others_bleeding == 0) # Check if no one else in area was bleeding dsh = {True: 'You stop hearing drops of blood.', False: 'You start hearing less drops of blood.'} dshs = {True: 'You stop hearing and smelling drops of blood.', False: 'You start hearing and smelling less drops of blood.'} dss = {True: 'You stop smelling blood.', False: 'You start smelling less blood.'} vis_status = 'no longer' else: # Case client is not bleeding and status is left or arrived (or anything but 'stay') # Boring cases for which the function should not be called raise KeyError('Invalid call of notify_others_blood with client {}. Bleeding: {}.' 'Status: {}'.format(client, client.is_bleeding, status)) h_mes = dsh[discriminant] # hearing message s_mes = dss[discriminant] # smelling message hs_mes = dshs[discriminant] # hearing and smelling message ybyd = hs_mes darkened = 'darkened ' if not area.lights else '' if status == 'stay': connector = 'is {}'.format(vis_status) pconnector = 'was {}'.format(vis_status) elif status == 'left': connector = 'leave the {}area while still'.format(darkened) pconnector = 'left the {}area while still'.format(darkened) elif status == 'arrived': connector = 'arrive to the {}area while'.format(darkened) pconnector = 'arrived to the {}area while'.format(darkened) if client.is_visible and area.lights: norm = 'You see {} {} bleeding.'.format(char, connector) ybnd = h_mes nbyd = norm staff = norm elif not client.is_visible and area.lights: norm = h_mes ybnd = hs_mes nbyd = s_mes staff = '(X) {} {} bleeding and sneaking.'.format(char, pconnector) elif client.is_visible and not area.lights: norm = hs_mes ybnd = hs_mes nbyd = s_mes staff = '(X) {} {} bleeding.'.format(char, pconnector) elif not client.is_visible and not area.lights: norm = hs_mes ybnd = hs_mes nbyd = s_mes staff = ('(X) {} {} bleeding and sneaking.'.format(char, pconnector)) staff = staff.replace('no longer bleeding and sneaking.', 'no longer bleeding, but is still sneaking.') # Ugly client.send_ooc_others(norm, is_zstaff_flex=False, in_area=area, to_blind=False, to_deaf=False) client.send_ooc_others(ybnd, is_zstaff_flex=False, in_area=area, to_blind=True, to_deaf=False) client.send_ooc_others(nbyd, is_zstaff_flex=False, in_area=area, to_blind=False, to_deaf=True) client.send_ooc_others(ybyd, is_zstaff_flex=False, in_area=area, to_blind=True, to_deaf=True) if send_to_staff: client.send_ooc_others(staff, is_zstaff_flex=True, in_area=area)
def notify_others_moving(self, client: ClientManager.Client, area: AreaManager.Area, autopass_mes: str, blind_mes: str): staff = nbnd = ybnd = nbyd = '' # nbnd = notblindnotdeaf ybnd=yesblindnotdeaf # Autopass: at most footsteps if no lights # No autopass: at most footsteps if no lights # Blind: at most footsteps # Deaf: can hear autopass but not footsteps # No lights: at most footsteps # Remove trailing periods and add it again. This helps prevent duplicate periods. autopass_mes = autopass_mes[:-1] if autopass_mes.endswith('.') else autopass_mes if client.autopass: staff = autopass_mes + '.' nbnd = autopass_mes + '.' ybnd = blind_mes nbyd = autopass_mes + '.' else: staff = '(X) {} (no autopass).'.format(autopass_mes) if not area.lights: staff = '(X) {} while the lights were out.'.format(autopass_mes) nbnd = blind_mes ybnd = blind_mes nbyd = '' if not client.is_visible: # This should be the last statement staff = '(X) {} while sneaking.'.format(autopass_mes) nbnd = '' ybnd = '' nbyd = '' if client.autopass: client.send_ooc_others(staff, in_area=area, is_zstaff_flex=True) else: client.send_ooc_others(staff, in_area=area, is_zstaff_flex=True, pred=lambda c: c.get_nonautopass_autopass) client.send_ooc_others(nbnd, in_area=area, is_zstaff_flex=True, pred=lambda c: not c.get_nonautopass_autopass) client.send_ooc_others(nbnd, in_area=area, is_zstaff_flex=False, to_blind=False, to_deaf=False) client.send_ooc_others(ybnd, in_area=area, is_zstaff_flex=False, to_blind=True, to_deaf=False) client.send_ooc_others(nbyd, in_area=area, is_zstaff_flex=False, to_blind=False, to_deaf=True)
def _cleanup_removed_watcher(self, user: ClientManager.Client): user.zone_watched = None