def send_error_report(self, client: ClientManager.Client, cmd: str, args: List[str], ex: Exception): """ In case of an error caused by a client packet, send error report to user, notify moderators and have full traceback available on console and through /lasterror """ # Send basic logging information to user info = ( '=========\nThe server ran into a Python issue. Please contact the server owner ' 'and send them the following logging information:') etype, evalue, etraceback = sys.exc_info() tb = traceback.extract_tb(tb=etraceback) current_time = Constants.get_time() file, line_num, module, func = tb[-1] file = file[file.rfind('\\') + 1:] # Remove unnecessary directories version = self.version info += '\r\n*Server version: {}'.format(version) info += '\r\n*Server time: {}'.format(current_time) info += '\r\n*Packet details: {} {}'.format(cmd, args) info += '\r\n*Client status: {}'.format(client) info += '\r\n*Area status: {}'.format(client.area) info += '\r\n*File: {}'.format(file) info += '\r\n*Line number: {}'.format(line_num) info += '\r\n*Module: {}'.format(module) info += '\r\n*Function: {}'.format(func) info += '\r\n*Error: {}: {}'.format(type(ex).__name__, ex) info += '\r\nYour help would be much appreciated.' info += '\r\n=========' client.send_ooc(info) client.send_ooc_others( 'Client {} triggered a Python error through a client packet. ' 'Do /lasterror to take a look at it.'.format(client.id), pred=lambda c: c.is_mod) # Print complete traceback to console info = 'TSUSERVERDR HAS ENCOUNTERED AN ERROR HANDLING A CLIENT PACKET' info += '\r\n*Server time: {}'.format(current_time) info += '\r\n*Packet details: {} {}'.format(cmd, args) info += '\r\n*Client status: {}'.format(client) info += '\r\n*Area status: {}'.format(client.area) info += '\r\n\r\n{}'.format("".join( traceback.format_exception(etype, evalue, etraceback))) logger.log_print(info) self.last_error = [info, etype, evalue, etraceback] # Log error to file logger.log_error(info, server=self, errortype='C') if self.in_test: raise ex
def notify_others_status(self, client: ClientManager.Client, area: AreaManager.Area, name: str, status: str = 'stay'): # Assume client's special status is worth announcing # If client has custom status, send reminder in OOC to everyone but those not staff, # blind and deaf simultaneously. norm_mes = f'You note something about {name} {{}}' vague_mes = 'You think there is something odd about someone' staff_mes = f'(X) {name} [{client.id}] {{}} and has a custom status: {client.status}' if status == 'stay': norm_mes = norm_mes.format(' who was already here') vague_mes += ' who was already here.' staff_mes = staff_mes.format('was already here{}') elif status == 'arrived': norm_mes = norm_mes.format(' who has just arrived') vague_mes += ' who has just arrived.' staff_mes = staff_mes.format('has just arrived{}') else: # Case status is left or anything else # Boring cases for which the function should not be called raise KeyError('Invalid call of notify_others_status with client {}. Player status: {}.' 'Status: {}'.format(client, client.status, status)) if client.is_visible and area.lights: norm = norm_mes ybnd = vague_mes nbyd = norm_mes staff = staff_mes.format('') elif not client.is_visible and area.lights: norm = vague_mes ybnd = vague_mes nbyd = vague_mes staff = staff_mes.format(' while sneaking') elif client.is_visible and not area.lights: norm = vague_mes ybnd = vague_mes nbyd = vague_mes staff = staff_mes.format('') else: norm = vague_mes ybnd = vague_mes nbyd = vague_mes staff = staff_mes.format(' while sneaking') 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(staff, is_zstaff_flex=True, in_area=area)
def _remove_player(self, user: ClientManager.Client): if user not in self._players: raise ZoneError.PlayerNotInZoneError( 'User {} is not a player of zone {}.'.format(user, self)) self._players.remove(user) self._cleanup_removed_player(user) # If no more watchers nor players, delete the zone if not self._watchers and not self._players: self._server.zone_manager.delete_zone(self._zone_id) user.send_ooc( '(X) Zone `{}` that you were in was automatically ended as no one ' 'was in an area part of it or was watching it anymore.'. format(self._zone_id), is_staff=True) user.send_ooc_others( 'Zone `{}` was automatically ended as no one was in an ' 'area part of it or was watching it anymore.'.format( self._zone_id), is_officer=True)
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)