def can_send_message(self, client: ClientManager.Client) -> bool: """Check if a client can send an IC message in this area. Args: client (ClientManager.Client): sender Returns: bool: True is client can send a message, False if not """ if self.cannot_ic_interact(client): client.send_ooc('This is a locked area - ask the CM to speak.') return False return (time.time() * 1000.0 - self.next_message_time) > 0
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 start_testimony(self, client: ClientManager.Client, title: str) -> bool: """ Start a new testimony in this area. Args: client (ClientManager.Client): requester title (str): title of the testimony Returns: bool: whether the testimony was started """ if client not in self.owners and (self.evidence_mod == "HiddenCM" or self.evidence_mod == "Mods"): # some servers don't utilise area owners, so we use evidence_mod to determine behavior client.send_ooc('You don\'t have permission to start a new testimony in this area!') return False elif self.is_testifying: client.send_ooc('You can\'t start a new testimony until you finish this one!') return False elif self.is_examining: client.send_ooc('You can\'t start a new testimony during an examination!') return False elif title == '': client.send_ooc('You can\'t start a new testimony without a title!') return False self.testimony = self.Testimony(title, self.testimony_limit) self.broadcast_ooc('Began testimony: ' + title) self.is_testifying = True self.send_command('RT', 'testimony1') return True
def remove_jukebox_vote(self, client: ClientManager.Client, silent: bool): """Removes a vote on the jukebox. Args: client (ClientManager.Client): client whose vote should be removed silent (bool): do not notify client """ if not self.jukebox: return for current_vote in self.jukebox_votes: if current_vote.client.id == client.id: self.jukebox_votes.remove(current_vote) if not silent: client.send_ooc( 'You removed your song from the jukebox.')
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 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 _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 end_testimony(self, client: ClientManager.Client) -> bool: """ End the current testimony or examination. Args: client (ClientManager.Client): requester Returns: bool: if the current testimony or examination was ended """ 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 end testimonies or examinations in this area!') return False elif self.is_testifying: if len(self.testimony.statements) <= 1: client.send_ooc('Please add at least one statement before ending your testimony.') return False self.is_testifying = False self.broadcast_ooc('Recording stopped.') return True elif self.is_examining: self.is_examining = False self.broadcast_ooc('Examination stopped.') return True else: client.send_ooc('No testimony or examination in progress.') 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 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 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 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 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()