def net_cmd_ct(self, args: List[str]): """ OOC Message CT#<name:string>#<message:string>#% """ pargs = self.process_arguments('CT', args) username, message = pargs['username'], pargs['message'] # Trim out any leading/trailing whitespace characters up to a chain of spaces username = Constants.trim_extra_whitespace(username) message = Constants.trim_extra_whitespace(message) if self.client.is_ooc_muted: # Checks to see if the client has been muted by a mod self.client.send_ooc("You have been muted by a moderator.") return if username == '' or not self.client.is_valid_name(username): self.client.send_ooc('You must insert a name with at least one letter.') return if username.startswith(' '): self.client.send_ooc('You must insert a name that starts with a letter.') return if Constants.contains_illegal_characters(username): self.client.send_ooc('Your name contains an illegal character.') return if (Constants.decode_ao_packet([self.server.config['hostname']])[0] in username or '$G' in username): self.client.send_ooc('That name is reserved.') return # After this the name is validated self.client.publish_inbound_command('CT', pargs) self.client.name = username if message.startswith('/'): spl = message[1:].split(' ', 1) cmd = spl[0] arg = '' if len(spl) == 2: arg = spl[1][:1024] arg = Constants.trim_extra_whitespace(arg) # Do it again because args may be weird try: called_function = 'ooc_cmd_{}'.format(cmd) function = None # Double assignment to check if it matched to a function later function = getattr(self.server.commands, called_function) except AttributeError: try: function = getattr(self.server.commands_alt, called_function) except AttributeError: self.client.send_ooc(f'Invalid command `{cmd}`.') if function: try: function(self.client, arg) except TsuserverException as ex: if ex.message: self.client.send_ooc(ex) else: self.client.send_ooc(type(ex).__name__) else: # Censor passwords if accidentally said without a slash in OOC for password in self.server.all_passwords: for login in ['login ', 'logincm ', 'loginrp ', 'logingm ']: if login + password in args[1]: message = message.replace(password, '[CENSORED]') if self.client.disemvowel: # If you are disemvoweled, replace string. message = Constants.disemvowel_message(message) if self.client.disemconsonant: # If you are disemconsonanted, replace string. message = Constants.disemconsonant_message(message) if self.client.remove_h: # If h is removed, replace string. message = Constants.remove_h_message(message) for client in self.client.area.clients: client.send_ooc(message, username=self.client.name) self.client.last_ooc_message = args[1] logger.log_server('[OOC][{}][{}][{}]{}' .format(self.client.area.id, self.client.get_char_name(), self.client.name, message), self.client) self.client.last_active = Constants.get_time()
def net_cmd_ms(self, args: List[str]): """ IC message. Refer to the implementation for details. """ pargs = self.process_arguments('MS', args) if self.client.is_muted: # Checks to see if the client has been muted by a mod self.client.send_ooc("You have been muted by a moderator.") return if (self.client.area.ic_lock and not self.client.is_staff() and not self.client.can_bypass_iclock): self.client.send_ooc('The IC chat in this area is currently locked.') return if not self.client.area.can_send_message(): return # Trim out any leading/trailing whitespace characters up to a chain of spaces pargs['text'] = Constants.trim_extra_whitespace(pargs['text']) # Check if after all of this, the message is empty. If so, ignore if not pargs['text']: return # First, check if the player just sent the same message with the same character and did # not receive any other messages in the meantime. # This helps prevent record these messages and retransmit it to clients who may want to # filter these out if (pargs['text'] == self.client.last_ic_raw_message and self.client.last_received_ic[0] == self.client and self.client.get_char_name() == self.client.last_ic_char): return if not self.client.area.iniswap_allowed: if self.client.area.is_iniswap(self.client, pargs['pre'], pargs['anim'], pargs['folder']): self.client.send_ooc("Iniswap is blocked in this area.") return if pargs['folder'] in self.client.area.restricted_chars and not self.client.is_staff(): self.client.send_ooc('Your character is restricted in this area.') return if pargs['msg_type'] not in ('chat', '0', '1'): return if pargs['anim_type'] not in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10): return if pargs['char_id'] != self.client.char_id: return if Constants.includes_relative_directories(pargs['sfx']): self.client.send_ooc(f'Sound effects and voicelines may not not reference parent or ' f'current directories: {pargs["sfx"]}') return if pargs['sfx_delay'] < 0: return if pargs['button'] not in (0, 1, 2, 3, 4, 5, 6, 7, 8): # Shouts return if pargs['button'] > 0 and not self.client.area.bullet and not self.client.is_staff(): self.client.send_ooc('Bullets are disabled in this area.') return if pargs['evidence'] < 0: return if pargs['ding'] not in (0, 1, 2, 3, 4, 5, 6, 7): # Effects return if pargs['color'] not in (0, 1, 2, 3, 4, 5, 6, 7, 8): return if pargs['color'] == 5 and not self.client.is_officer(): pargs['color'] = 0 if self.client.pos: pargs['pos'] = self.client.pos else: if pargs['pos'] not in ('def', 'pro', 'hld', 'hlp', 'jud', 'wit'): return # Make sure the areas are ok with this try: self.client.area.publisher.publish('area_client_inbound_ms_check', { 'client': self.client, 'contents': pargs, }) except TsuserverException as ex: self.client.send_ooc(ex) return # Make sure the clients are ok with this try: self.client.publisher.publish('client_inbound_ms_check', { 'contents': pargs, }) except TsuserverException as ex: self.client.send_ooc(ex) return # At this point, the message is guaranteed to be sent self.client.publish_inbound_command('MS', pargs) self.client.send_command_dict('ackMS', dict()) self.client.pos = pargs['pos'] # First, update last raw message sent *before* any transformations. That is so that the # server can accurately ignore client sending the same message over and over again self.client.last_ic_raw_message = pargs['text'] self.client.last_ic_char = self.client.get_char_name() # Truncate and alter message if message effect is in place raw_msg = pargs['text'][:256] msg = raw_msg if self.client.gimp: # If you are gimped, gimp message. msg = random.choice(self.server.gimp_list) if self.client.disemvowel: # If you are disemvoweled, replace string. msg = Constants.disemvowel_message(msg) if self.client.disemconsonant: # If you are disemconsonanted, replace string. msg = Constants.disemconsonant_message(msg) if self.client.remove_h: # If h is removed, replace string. msg = Constants.remove_h_message(msg) gag_replaced = False if self.client.is_gagged: allowed_starters = ('(', '*', '[') if msg != ' ' and not msg.startswith(allowed_starters): gag_replaced = True msg = Constants.gagged_message() if msg != raw_msg: self.client.send_ooc_others('(X) {} [{}] tried to say `{}` but is currently gagged.' .format(self.client.displayname, self.client.id, raw_msg), is_zstaff_flex=True, in_area=True) # Censor passwords if login command accidentally typed in IC for password in self.server.all_passwords: for login in ['login ', 'logincm ', 'loginrp ', 'logingm ']: if login + password in msg: msg = msg.replace(password, '[CENSORED]') if pargs['evidence'] and pargs['evidence'] in self.client.evi_list: evidence_position = self.client.evi_list[pargs['evidence']] - 1 if self.client.area.evi_list.evidences[evidence_position].pos != 'all': self.client.area.evi_list.evidences[evidence_position].pos = 'all' self.client.area.broadcast_evidence_list() pargs['evidence'] = self.client.evi_list[pargs['evidence']] else: pargs['evidence'] = 0 # If client has GlobalIC enabled, set area range target to intended range and remove # GlobalIC prefix if needed. if self.client.multi_ic is None or not msg.startswith(self.client.multi_ic_pre): area_range = range(self.client.area.id, self.client.area.id + 1) else: # As msg.startswith('') is True, this also accounts for having no required prefix. start, end = self.client.multi_ic[0].id, self.client.multi_ic[1].id + 1 start_area = self.server.area_manager.get_area_by_id(start) end_area = self.server.area_manager.get_area_by_id(end-1) area_range = range(start, end) truncated_msg = msg.replace(self.client.multi_ic_pre, '', 1) if start != end-1: self.client.send_ooc('Sent global IC message "{}" to areas {} through {}.' .format(truncated_msg, start_area.name, end_area.name)) else: self.client.send_ooc('Sent global IC message "{}" to area {}.' .format(truncated_msg, start_area.name)) pargs['msg'] = msg pargs['showname'] = '' # Dummy value, actual showname is computed later # Compute pairs # Based on tsuserver3.3 code # Only do this if character is paired, which would only happen for AO 2.6+ clients # Handle AO 2.8 logic # AO 2.8 sends their charid_pair in slightly longer format (\d+\^\d+) # The first bit corresponds to the proper charid_pair, the latter one to whether # the character should appear in front or behind the pair. We still want to extract # charid_pair so pre-AO 2.8 still see the pair; but make it so that AO 2.6 can send pair # messages. Thus, we 'invent' the missing arguments based on available info. if 'charid_pair_pair_order' in pargs: # AO 2.8 sender pargs['charid_pair'] = int(pargs['charid_pair_pair_order'].split('^')[0]) elif 'charid_pair' in pargs: # AO 2.6 sender pargs['charid_pair_pair_order'] = f'{pargs["charid_pair"]}^0' else: # E.g. DRO pargs['charid_pair'] = -1 pargs['charid_pair_pair_order'] = -1 self.client.charid_pair = pargs['charid_pair'] if 'charid_pair' in pargs else -1 self.client.offset_pair = pargs['offset_pair'] if 'offset_pair' in pargs else 0 self.client.flip = pargs['flip'] if not self.client.char_folder: self.client.char_folder = pargs['folder'] if pargs['anim_type'] not in (5, 6): self.client.last_sprite = pargs['anim'] pargs['other_offset'] = 0 pargs['other_emote'] = 0 pargs['other_flip'] = 0 pargs['other_folder'] = '' if 'charid_pair' not in pargs or pargs['charid_pair'] < -1: pargs['charid_pair'] = -1 pargs['charid_pair_pair_order'] = -1 if pargs['charid_pair'] > -1: for target in self.client.area.clients: if target == self.client: continue # Check pair has accepted pair if target.char_id != self.client.charid_pair: continue if target.charid_pair != self.client.char_id: continue # Check pair is in same position if target.pos != self.client.pos: continue pargs['other_offset'] = target.offset_pair pargs['other_emote'] = target.last_sprite pargs['other_flip'] = target.flip pargs['other_folder'] = target.char_folder break else: # There are no clients who want to pair with this client pargs['charid_pair'] = -1 pargs['offset_pair'] = 0 pargs['charid_pair_pair_order'] = -1 self.client.publish_inbound_command('MS_final', pargs) for area_id in area_range: target_area = self.server.area_manager.get_area_by_id(area_id) for c in target_area.clients: c.send_ic(params=pargs, sender=self.client, gag_replaced=gag_replaced) target_area.set_next_msg_delay(len(msg)) # Deal with shoutlog if pargs['button'] > 0: info = 'used shout {} with the message: {}'.format(pargs['button'], msg) target_area.add_to_shoutlog(self.client, info) self.client.area.set_next_msg_delay(len(msg)) logger.log_server('[IC][{}][{}]{}' .format(self.client.area.id, self.client.get_char_name(), msg), self.client) # Sending IC messages reveals sneaked players if not self.client.is_staff() and not self.client.is_visible: self.client.change_visibility(True) self.client.send_ooc_others('(X) {} [{}] revealed themselves by talking ({}).' .format(self.client.displayname, self.client.id, self.client.area.id), is_zstaff=True) # Restart AFK kick timer and lurk callout timers, if needed self.server.tasker.create_task(self.client, ['as_afk_kick', self.client.area.afk_delay, self.client.area.afk_sendto]) self.client.check_lurk() if self.client.area.is_recording: self.client.area.recorded_messages.append(args) self.client.last_ic_message = msg self.client.last_active = Constants.get_time()