diff --git a/tsuserver3/server/aoprotocol.py b/AO2-Client/server/aoprotocol.py index c5e4f63..2cf6fb4 100644 --- a/tsuserver3/server/aoprotocol.py +++ b/AO2-Client/server/aoprotocol.py @@ -26,6 +26,7 @@ from .exceptions import ClientError, AreaError, ArgumentError, ServerError from .fantacrypt import fanta_decrypt from .evidence import EvidenceList from .websocket import WebSocket +import unicodedata class AOProtocol(asyncio.Protocol): @@ -171,6 +172,7 @@ class AOProtocol(asyncio.Protocol): self.client.server.dump_hdids() for ipid in self.client.server.hdid_list[self.client.hdid]: if self.server.ban_manager.is_banned(ipid): + self.client.send_command('BD') self.client.disconnect() return logger.log_server('Connected. HDID: {}.'.format(self.client.hdid), self.client) @@ -211,7 +213,7 @@ class AOProtocol(asyncio.Protocol): self.client.is_ao2 = True - self.client.send_command('FL', 'yellowtext', 'customobjections', 'flipping', 'fastloading', 'noencryption', 'deskmod', 'evidence') + self.client.send_command('FL', 'yellowtext', 'customobjections', 'flipping', 'fastloading', 'noencryption', 'deskmod', 'evidence', 'modcall_reason', 'cccc_ic_support', 'arup', 'casing_alerts') def net_cmd_ch(self, _): """ Periodically checks the connection. @@ -333,16 +335,94 @@ class AOProtocol(asyncio.Protocol): return if not self.client.area.can_send_message(self.client): return - if not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, + + target_area = [] + + if self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT): + # Vanilla validation monstrosity. + msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color = args + showname = "" + charid_pair = -1 + offset_pair = 0 + nonint_pre = 0 + elif self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, + self.ArgType.STR, + self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.INT, + self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, + self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR_OR_EMPTY): + # 1.3.0 validation monstrosity. + msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname = args + charid_pair = -1 + offset_pair = 0 + nonint_pre = 0 + if len(showname) > 0 and not self.client.area.showname_changes_allowed: + self.client.send_host_message("Showname changes are forbidden in this area!") + return + elif self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, + self.ArgType.STR, + self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.INT, + self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, + self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR_OR_EMPTY, self.ArgType.INT, self.ArgType.INT): + # 1.3.5 validation monstrosity. + msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname, charid_pair, offset_pair = args + nonint_pre = 0 + if len(showname) > 0 and not self.client.area.showname_changes_allowed: + self.client.send_host_message("Showname changes are forbidden in this area!") + return + elif self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, + self.ArgType.STR, + self.ArgType.STR, self.ArgType.STR, self.ArgType.STR, self.ArgType.INT, + self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, + self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR_OR_EMPTY, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT): + # 1.4.0 validation monstrosity. + msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname, charid_pair, offset_pair, nonint_pre = args + if len(showname) > 0 and not self.client.area.showname_changes_allowed: + self.client.send_host_message("Showname changes are forbidden in this area!") + return + else: return - msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color = args if self.client.area.is_iniswap(self.client, pre, anim, folder) and folder != self.client.get_char_name(): self.client.send_host_message("Iniswap is blocked in this area") return + if len(self.client.charcurse) > 0 and folder != self.client.get_char_name(): + self.client.send_host_message("You may not iniswap while you are charcursed!") + return + if not self.client.area.blankposting_allowed: + if text == ' ': + self.client.send_host_message("Blankposting is forbidden in this area!") + return + if text.isspace(): + self.client.send_host_message("Blankposting is forbidden in this area, and putting more spaces in does not make it not blankposting.") + return + if len(re.sub(r'[{}\\`|(~~)]','', text).replace(' ', '')) < 3 and text != '<' and text != '>': + self.client.send_host_message("While that is not a blankpost, it is still pretty spammy. Try forming sentences.") + return + if text.startswith('/a '): + part = text.split(' ') + try: + aid = int(part[1]) + if self.client in self.server.area_manager.get_area_by_id(aid).owners: + target_area.append(aid) + if not target_area: + self.client.send_host_message('You don\'t own {}!'.format(self.server.area_manager.get_area_by_id(aid).name)) + return + text = ' '.join(part[2:]) + except ValueError: + self.client.send_host_message("That does not look like a valid area ID!") + return + elif text.startswith('/s '): + part = text.split(' ') + for a in self.server.area_manager.areas: + if self.client in a.owners: + target_area.append(a.id) + if not target_area: + self.client.send_host_message('You don\'t any areas!') + return + text = ' '.join(part[1:]) if msg_type not in ('chat', '0', '1'): return if anim_type not in (0, 1, 2, 5, 6): @@ -354,12 +434,38 @@ class AOProtocol(asyncio.Protocol): if button not in (0, 1, 2, 3, 4): return if evidence < 0: - return - if ding not in (0, 1): return - if color not in (0, 1, 2, 3, 4, 5, 6): + if ding not in (0, 1): return - if color == 2 and not self.client.is_mod: + if color not in (0, 1, 2, 3, 4, 5, 6, 7, 8): + return + if len(showname) > 15: + self.client.send_host_message("Your IC showname is way too long!") + return + if nonint_pre == 1: + if button in (1, 2, 3, 4, 23): + if anim_type == 1 or anim_type == 2: + anim_type = 0 + elif anim_type == 6: + anim_type = 5 + if self.client.area.non_int_pres_only: + if anim_type == 1 or anim_type == 2: + anim_type = 0 + nonint_pre = 1 + elif anim_type == 6: + anim_type = 5 + nonint_pre = 1 + if not self.client.area.shouts_allowed: + # Old clients communicate the objecting in anim_type. + if anim_type == 2: + anim_type = 1 + elif anim_type == 6: + anim_type = 5 + # New clients do it in a specific objection message area. + button = 0 + # Turn off the ding. + ding = 0 + if color == 2 and not (self.client.is_mod or self.client in self.client.area.owners): color = 0 if color == 6: text = re.sub(r'[^\x00-\x7F]+',' ', text) #remove all unicode to prevent redtext abuse @@ -371,9 +477,11 @@ class AOProtocol(asyncio.Protocol): if self.client.pos: pos = self.client.pos else: - if pos not in ('def', 'pro', 'hld', 'hlp', 'jud', 'wit'): + if pos not in ('def', 'pro', 'hld', 'hlp', 'jud', 'wit', 'jur', 'sea'): return msg = text[:256] + if self.client.shaken: + msg = self.client.shake_message(msg) if self.client.disemvowel: msg = self.client.disemvowel_message(msg) self.client.pos = pos @@ -381,13 +489,53 @@ class AOProtocol(asyncio.Protocol): if self.client.area.evi_list.evidences[self.client.evi_list[evidence] - 1].pos != 'all': self.client.area.evi_list.evidences[self.client.evi_list[evidence] - 1].pos = 'all' self.client.area.broadcast_evidence_list() + + # Here, we check the pair stuff, and save info about it to the client. + # Notably, while we only get a charid_pair and an offset, we send back a chair_pair, an emote, a talker offset + # and an other offset. + self.client.charid_pair = charid_pair + self.client.offset_pair = offset_pair + if anim_type not in (5, 6): + self.client.last_sprite = anim + self.client.flip = flip + self.client.claimed_folder = folder + other_offset = 0 + other_emote = '' + other_flip = 0 + other_folder = '' + + confirmed = False + if charid_pair > -1: + for target in self.client.area.clients: + if target.char_id == self.client.charid_pair and target.charid_pair == self.client.char_id and target != self.client and target.pos == self.client.pos: + confirmed = True + other_offset = target.offset_pair + other_emote = target.last_sprite + other_flip = target.flip + other_folder = target.claimed_folder + break + + if not confirmed: + charid_pair = -1 + offset_pair = 0 + self.client.area.send_command('MS', msg_type, pre, folder, anim, msg, pos, sfx, anim_type, cid, - sfx_delay, button, self.client.evi_list[evidence], flip, ding, color) + sfx_delay, button, self.client.evi_list[evidence], flip, ding, color, showname, + charid_pair, other_folder, other_emote, offset_pair, other_offset, other_flip, nonint_pre) + + self.client.area.send_owner_command('MS', msg_type, pre, folder, anim, '[' + self.client.area.abbreviation + ']' + msg, pos, sfx, anim_type, cid, + sfx_delay, button, self.client.evi_list[evidence], flip, ding, color, showname, + charid_pair, other_folder, other_emote, offset_pair, other_offset, other_flip, nonint_pre) + + self.server.area_manager.send_remote_command(target_area, 'MS', msg_type, pre, folder, anim, msg, pos, sfx, anim_type, cid, + sfx_delay, button, self.client.evi_list[evidence], flip, ding, color, showname, + charid_pair, other_folder, other_emote, offset_pair, other_offset, other_flip, nonint_pre) + 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) + logger.log_server('[IC][{}][{}]{}'.format(self.client.area.abbreviation, self.client.get_char_name(), msg), self.client) if (self.client.area.is_recording): - self.client.area.recorded_messages.append(args) + self.client.area.recorded_messages.append(args) def net_cmd_ct(self, args): """ OOC Message @@ -409,12 +557,22 @@ class AOProtocol(asyncio.Protocol): if self.client.name == '': self.client.send_host_message('You must insert a name with at least one letter') return - if self.client.name.startswith(self.server.config['hostname']) or self.client.name.startswith('G'): + if len(self.client.name) > 30: + self.client.send_host_message('Your OOC name is too long! Limit it to 30 characters.') + return + for c in self.client.name: + if unicodedata.category(c) == 'Cf': + self.client.send_host_message('You cannot use format characters in your name!') + return + if self.client.name.startswith(self.server.config['hostname']) or self.client.name.startswith('G') or self.client.name.startswith('M'): self.client.send_host_message('That name is reserved!') return + if args[1].startswith(' /'): + self.client.send_host_message('Your message was not sent for safety reasons: you left a space before that slash.') + return if args[1].startswith('/'): spl = args[1][1:].split(' ', 1) - cmd = spl[0] + cmd = spl[0].lower() arg = '' if len(spl) == 2: arg = spl[1][:256] @@ -427,11 +585,14 @@ class AOProtocol(asyncio.Protocol): except (ClientError, AreaError, ArgumentError, ServerError) as ex: self.client.send_host_message(ex) else: + if self.client.shaken: + args[1] = self.client.shake_message(args[1]) if self.client.disemvowel: args[1] = self.client.disemvowel_message(args[1]) self.client.area.send_command('CT', self.client.name, args[1]) + self.client.area.send_owner_command('CT', '[' + self.client.area.abbreviation + ']' + self.client.name, args[1]) logger.log_server( - '[OOC][{}][{}][{}]{}'.format(self.client.area.id, self.client.get_char_name(), self.client.name, + '[OOC][{}][{}]{}'.format(self.client.area.abbreviation, self.client.get_char_name(), args[1]), self.client) def net_cmd_mc(self, args): @@ -450,7 +611,10 @@ class AOProtocol(asyncio.Protocol): if not self.client.is_dj: self.client.send_host_message('You were blockdj\'d by a moderator.') return - if not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT): + if self.client.area.cannot_ic_interact(self.client): + self.client.send_host_message("You are not on the area's invite list, and thus, you cannot change music!") + return + if not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT) and not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT, self.ArgType.STR): return if args[1] != self.client.char_id: return @@ -459,10 +623,29 @@ class AOProtocol(asyncio.Protocol): return try: name, length = self.server.get_song_data(args[0]) - self.client.area.play_music(name, self.client.char_id, length) - self.client.area.add_music_playing(self.client, name) - logger.log_server('[{}][{}]Changed music to {}.' - .format(self.client.area.id, self.client.get_char_name(), name), self.client) + + if self.client.area.jukebox: + showname = '' + if len(args) > 2: + showname = args[2] + if len(showname) > 0 and not self.client.area.showname_changes_allowed: + self.client.send_host_message("Showname changes are forbidden in this area!") + return + self.client.area.add_jukebox_vote(self.client, name, length, showname) + logger.log_server('[{}][{}]Added a jukebox vote for {}.'.format(self.client.area.abbreviation, self.client.get_char_name(), name), self.client) + else: + if len(args) > 2: + showname = args[2] + if len(showname) > 0 and not self.client.area.showname_changes_allowed: + self.client.send_host_message("Showname changes are forbidden in this area!") + return + self.client.area.play_music_shownamed(name, self.client.char_id, showname, length) + self.client.area.add_music_playing_shownamed(self.client, showname, name) + else: + self.client.area.play_music(name, self.client.char_id, length) + self.client.area.add_music_playing(self.client, name) + logger.log_server('[{}][{}]Changed music to {}.' + .format(self.client.area.abbreviation, self.client.get_char_name(), name), self.client) except ServerError: return except ClientError as ex: @@ -474,26 +657,37 @@ class AOProtocol(asyncio.Protocol): RT##% """ + if not self.client.area.shouts_allowed: + self.client.send_host_message("You cannot use the testimony buttons here!") + return if self.client.is_muted: # Checks to see if the client has been muted by a mod self.client.send_host_message("You have been muted by a moderator") return if not self.client.can_wtce: self.client.send_host_message('You were blocked from using judge signs by a moderator.') return - if not self.validate_net_cmd(args, self.ArgType.STR): + if self.client.area.cannot_ic_interact(self.client): + self.client.send_host_message("You are not on the area's invite list, and thus, you cannot use the WTCE buttons!") + return + if not self.validate_net_cmd(args, self.ArgType.STR) and not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT): return if args[0] == 'testimony1': sign = 'WT' elif args[0] == 'testimony2': sign = 'CE' + elif args[0] == 'judgeruling': + sign = 'JR' else: return if self.client.wtce_mute(): self.client.send_host_message('You used witness testimony/cross examination signs too many times. Please try again after {} seconds.'.format(int(self.client.wtce_mute()))) return - self.client.area.send_command('RT', args[0]) + if len(args) == 1: + self.client.area.send_command('RT', args[0]) + elif len(args) == 2: + self.client.area.send_command('RT', args[0], args[1]) self.client.area.add_to_judgelog(self.client, 'used {}'.format(sign)) - logger.log_server("[{}]{} Used WT/CE".format(self.client.area.id, self.client.get_char_name()), self.client) + logger.log_server("[{}]{} Used WT/CE".format(self.client.area.abbreviation, self.client.get_char_name()), self.client) def net_cmd_hp(self, args): """ Sets the penalty bar. @@ -504,13 +698,16 @@ class AOProtocol(asyncio.Protocol): if self.client.is_muted: # Checks to see if the client has been muted by a mod self.client.send_host_message("You have been muted by a moderator") return + if self.client.area.cannot_ic_interact(self.client): + self.client.send_host_message("You are not on the area's invite list, and thus, you cannot change the Confidence bars!") + return if not self.validate_net_cmd(args, self.ArgType.INT, self.ArgType.INT): return try: self.client.area.change_hp(args[0], args[1]) self.client.area.add_to_judgelog(self.client, 'changed the penalties') logger.log_server('[{}]{} changed HP ({}) to {}' - .format(self.client.area.id, self.client.get_char_name(), args[0], args[1]), self.client) + .format(self.client.area.abbreviation, self.client.get_char_name(), args[0], args[1]), self.client) except AreaError: return @@ -552,7 +749,7 @@ class AOProtocol(asyncio.Protocol): self.client.area.broadcast_evidence_list() - def net_cmd_zz(self, _): + def net_cmd_zz(self, args): """ Sent on mod call. """ @@ -566,11 +763,16 @@ class AOProtocol(asyncio.Protocol): current_time = strftime("%H:%M", localtime()) - self.server.send_all_cmd_pred('ZZ', '[{}] {} ({}) in {} ({})' - .format(current_time, self.client.get_char_name(), self.client.get_ip(), self.client.area.name, - self.client.area.id), pred=lambda c: c.is_mod) - self.client.set_mod_call_delay() - logger.log_server('[{}][{}]{} called a moderator.'.format(self.client.get_ip(), self.client.area.id, self.client.get_char_name())) + if len(args) < 1: + self.server.send_all_cmd_pred('ZZ', '[{}] {} ({}) in {} without reason (not using the Case Café client?)' + .format(current_time, self.client.get_char_name(), self.client.get_ip(), self.client.area.name), pred=lambda c: c.is_mod) + self.client.set_mod_call_delay() + logger.log_server('[{}]{} called a moderator.'.format(self.client.area.abbreviation, self.client.get_char_name()), self.client) + else: + self.server.send_all_cmd_pred('ZZ', '[{}] {} ({}) in {} with reason: {}' + .format(current_time, self.client.get_char_name(), self.client.get_ip(), self.client.area.name, args[0][:100]), pred=lambda c: c.is_mod) + self.client.set_mod_call_delay() + logger.log_server('[{}]{} called a moderator: {}.'.format(self.client.area.abbreviation, self.client.get_char_name(), args[0]), self.client) def net_cmd_opKICK(self, args): self.net_cmd_ct(['opkick', '/kick {}'.format(args[0])]) diff --git a/tsuserver3/server/area_manager.py b/AO2-Client/server/area_manager.py index 39eb211..cfb2be0 100644 --- a/tsuserver3/server/area_manager.py +++ b/AO2-Client/server/area_manager.py @@ -22,11 +22,12 @@ import yaml from server.exceptions import AreaError from server.evidence import EvidenceList +from enum import Enum class AreaManager: class Area: - def __init__(self, area_id, server, name, background, bg_lock, evidence_mod = 'FFA', locking_allowed = False, iniswap_allowed = True): + def __init__(self, area_id, server, name, background, bg_lock, evidence_mod = 'FFA', locking_allowed = False, iniswap_allowed = True, showname_changes_allowed = False, shouts_allowed = True, jukebox = False, abbreviation = '', non_int_pres_only = False): self.iniswap_allowed = iniswap_allowed self.clients = set() self.invite_list = {} @@ -44,12 +45,15 @@ class AreaManager: self.judgelog = [] self.current_music = '' self.current_music_player = '' + self.current_music_player_ipid = -1 self.evi_list = EvidenceList() self.is_recording = False self.recorded_messages = [] self.evidence_mod = evidence_mod self.locking_allowed = locking_allowed - self.owned = False + self.showname_changes_allowed = showname_changes_allowed + self.shouts_allowed = shouts_allowed + self.abbreviation = abbreviation self.cards = dict() """ @@ -59,23 +63,53 @@ class AreaManager: self.evidence_list.append(Evidence("weeeeeew", "desc3", "3.png")) """ - self.is_locked = False + self.is_locked = self.Locked.FREE + self.blankposting_allowed = True + self.non_int_pres_only = non_int_pres_only + self.jukebox = jukebox + self.jukebox_votes = [] + self.jukebox_prev_char_id = -1 + + self.owners = [] + + class Locked(Enum): + FREE = 1, + SPECTATABLE = 2, + LOCKED = 3 def new_client(self, client): self.clients.add(client) + self.server.area_manager.send_arup_players() def remove_client(self, client): self.clients.remove(client) - if client.is_cm: - client.is_cm = False - self.owned = False - if self.is_locked: - self.unlock() + if len(self.clients) == 0: + self.change_status('IDLE') def unlock(self): - self.is_locked = False + self.is_locked = self.Locked.FREE + self.blankposting_allowed = True self.invite_list = {} + self.server.area_manager.send_arup_lock() self.send_host_message('This area is open now.') + + def spectator(self): + self.is_locked = self.Locked.SPECTATABLE + for i in self.clients: + self.invite_list[i.id] = None + for i in self.owners: + self.invite_list[i.id] = None + self.server.area_manager.send_arup_lock() + self.send_host_message('This area is spectatable now.') + + def lock(self): + self.is_locked = self.Locked.LOCKED + for i in self.clients: + self.invite_list[i.id] = None + for i in self.owners: + self.invite_list[i.id] = None + self.server.area_manager.send_arup_lock() + self.send_host_message('This area is locked now.') def is_char_available(self, char_id): return char_id not in [x.char_id for x in self.clients] @@ -89,9 +123,15 @@ class AreaManager: def send_command(self, cmd, *args): for c in self.clients: c.send_command(cmd, *args) + + def send_owner_command(self, cmd, *args): + for c in self.owners: + if not c in self.clients: + c.send_command(cmd, *args) def send_host_message(self, msg): - self.send_command('CT', self.server.config['hostname'], msg) + self.send_command('CT', self.server.config['hostname'], msg, '1') + self.send_owner_command('CT', '[' + self.abbreviation + ']' + self.server.config['hostname'], msg, '1') def set_next_msg_delay(self, msg_length): delay = min(3000, 100 + 60 * msg_length) @@ -106,6 +146,83 @@ class AreaManager: if client.get_char_name() in char_link and char in char_link: return False return True + + def add_jukebox_vote(self, client, music_name, length=-1, showname=''): + 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_host_message('Your song was added to the jukebox.') + if len(self.jukebox_votes) == 1: + self.start_jukebox() + + def remove_jukebox_vote(self, client, silent): + 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_host_message('You removed your song from the jukebox.') + + def get_jukebox_picked(self): + if not self.jukebox: + return + if len(self.jukebox_votes) == 0: + return None + elif len(self.jukebox_votes) == 1: + return self.jukebox_votes[0] + else: + weighted_votes = [] + for current_vote in self.jukebox_votes: + i = 0 + while i < current_vote.chance: + weighted_votes.append(current_vote) + i += 1 + return random.choice(weighted_votes) + + def start_jukebox(self): + # There is a probability that the jukebox feature has been turned off since then, + # we should check that. + # We also do a check if we were the last to play a song, just in case. + if not self.jukebox: + if self.current_music_player == 'The Jukebox' and self.current_music_player_ipid == 'has no IPID': + self.current_music = '' + return + + vote_picked = self.get_jukebox_picked() + + if vote_picked is None: + self.current_music = '' + return + + if vote_picked.client.char_id != self.jukebox_prev_char_id or vote_picked.name != self.current_music or len(self.jukebox_votes) > 1: + self.jukebox_prev_char_id = vote_picked.client.char_id + if vote_picked.showname == '': + self.send_command('MC', vote_picked.name, vote_picked.client.char_id) + else: + self.send_command('MC', vote_picked.name, vote_picked.client.char_id, vote_picked.showname) + else: + self.send_command('MC', vote_picked.name, -1) + + self.current_music_player = 'The Jukebox' + self.current_music_player_ipid = 'has no IPID' + self.current_music = vote_picked.name + + for current_vote in self.jukebox_votes: + # Choosing the same song will get your votes down to 0, too. + # Don't want the same song twice in a row! + if current_vote.name == vote_picked.name: + current_vote.chance = 0 + else: + current_vote.chance += 1 + + if self.music_looper: + self.music_looper.cancel() + self.music_looper = asyncio.get_event_loop().call_later(vote_picked.length, lambda: self.start_jukebox()) def play_music(self, name, cid, length=-1): self.send_command('MC', name, cid) @@ -114,14 +231,25 @@ class AreaManager: if length > 0: self.music_looper = asyncio.get_event_loop().call_later(length, lambda: self.play_music(name, -1, length)) + + def play_music_shownamed(self, name, cid, showname, length=-1): + self.send_command('MC', name, cid, showname) + if self.music_looper: + self.music_looper.cancel() + if length > 0: + self.music_looper = asyncio.get_event_loop().call_later(length, + lambda: self.play_music(name, -1, length)) def can_send_message(self, client): - if self.is_locked and not client.is_mod and not client.ipid in self.invite_list: + if self.cannot_ic_interact(client): client.send_host_message('This is a locked area - ask the CM to speak.') return False return (time.time() * 1000.0 - self.next_message_time) > 0 + def cannot_ic_interact(self, client): + return self.is_locked != self.Locked.FREE and not client.is_mod and not client.id in self.invite_list + def change_hp(self, side, val): if not 0 <= val <= 10: raise AreaError('Invalid penalty value.') @@ -140,10 +268,13 @@ class AreaManager: self.send_command('BN', self.background) def change_status(self, value): - allowed_values = ('idle', 'building-open', 'building-full', 'casing-open', 'casing-full', 'recess') + allowed_values = ('idle', 'rp', 'casing', 'looking-for-players', 'lfp', 'recess', 'gaming') if value.lower() not in allowed_values: raise AreaError('Invalid status. Possible values: {}'.format(', '.join(allowed_values))) + if value.lower() == 'lfp': + value = 'looking-for-players' self.status = value.upper() + self.server.area_manager.send_arup_status() def change_doc(self, doc='No document.'): self.doc = doc @@ -155,6 +286,12 @@ class AreaManager: def add_music_playing(self, client, name): self.current_music_player = client.get_char_name() + self.current_music_player_ipid = client.ipid + self.current_music = name + + def add_music_playing_shownamed(self, client, showname, name): + self.current_music_player = showname + " (" + client.get_char_name() + ")" + self.current_music_player_ipid = client.ipid self.current_music = name def get_evidence_list(self, client): @@ -168,7 +305,22 @@ class AreaManager: """ for client in self.clients: client.send_command('LE', *self.get_evidence_list(client)) - + + def get_cms(self): + msg = '' + for i in self.owners: + msg = msg + '[' + str(i.id) + '] ' + i.get_char_name() + ', ' + if len(msg) > 2: + msg = msg[:-2] + return msg + + class JukeboxVote: + def __init__(self, client, name, length, showname): + self.client = client + self.name = name + self.length = length + self.chance = 1 + self.showname = showname def __init__(self, server): self.server = server @@ -186,8 +338,18 @@ class AreaManager: item['locking_allowed'] = False if 'iniswap_allowed' not in item: item['iniswap_allowed'] = True + if 'showname_changes_allowed' not in item: + item['showname_changes_allowed'] = False + if 'shouts_allowed' not in item: + item['shouts_allowed'] = True + if 'jukebox' not in item: + item['jukebox'] = False + if 'noninterrupting_pres' not in item: + item['noninterrupting_pres'] = False + if 'abbreviation' not in item: + item['abbreviation'] = self.get_generated_abbreviation(item['area']) self.areas.append( - self.Area(self.cur_id, self.server, item['area'], item['background'], item['bglock'], item['evidence_mod'], item['locking_allowed'], item['iniswap_allowed'])) + self.Area(self.cur_id, self.server, item['area'], item['background'], item['bglock'], item['evidence_mod'], item['locking_allowed'], item['iniswap_allowed'], item['showname_changes_allowed'], item['shouts_allowed'], item['jukebox'], item['abbreviation'], item['noninterrupting_pres'])) self.cur_id += 1 def default_area(self): @@ -204,3 +366,47 @@ class AreaManager: if area.id == num: return area raise AreaError('Area not found.') + + def get_generated_abbreviation(self, name): + if name.lower().startswith("courtroom"): + return "CR" + name.split()[-1] + elif name.lower().startswith("area"): + return "A" + name.split()[-1] + elif len(name.split()) > 1: + return "".join(item[0].upper() for item in name.split()) + elif len(name) > 3: + return name[:3].upper() + else: + return name.upper() + + def send_remote_command(self, area_ids, cmd, *args): + for a_id in area_ids: + self.get_area_by_id(a_id).send_command(cmd, *args) + self.get_area_by_id(a_id).send_owner_command(cmd, *args) + + def send_arup_players(self): + players_list = [0] + for area in self.areas: + players_list.append(len(area.clients)) + self.server.send_arup(players_list) + + def send_arup_status(self): + status_list = [1] + for area in self.areas: + status_list.append(area.status) + self.server.send_arup(status_list) + + def send_arup_cms(self): + cms_list = [2] + for area in self.areas: + cm = 'FREE' + if len(area.owners) > 0: + cm = area.get_cms() + cms_list.append(cm) + self.server.send_arup(cms_list) + + def send_arup_lock(self): + lock_list = [3] + for area in self.areas: + lock_list.append(area.is_locked.name) + self.server.send_arup(lock_list) diff --git a/tsuserver3/server/ban_manager.py b/AO2-Client/server/ban_manager.py index 24518b2..20c186f 100644 --- a/tsuserver3/server/ban_manager.py +++ b/AO2-Client/server/ban_manager.py @@ -51,4 +51,4 @@ class BanManager: self.write_banlist() def is_banned(self, ipid): - return (ipid in self.bans) + return (ipid in self.bans) \ No newline at end of file diff --git a/tsuserver3/server/client_manager.py b/AO2-Client/server/client_manager.py index 38974b3..432c39d 100644 --- a/tsuserver3/server/client_manager.py +++ b/AO2-Client/server/client_manager.py @@ -44,9 +44,10 @@ class ClientManager: self.is_dj = True self.can_wtce = True self.pos = '' - self.is_cm = False self.evi_list = [] self.disemvowel = False + self.shaken = False + self.charcurse = [] self.muted_global = False self.muted_adverts = False self.is_muted = False @@ -56,7 +57,24 @@ class ClientManager: self.in_rp = False self.ipid = ipid self.websocket = None + + # Pairing stuff + self.charid_pair = -1 + self.offset_pair = 0 + self.last_sprite = '' + self.flip = 0 + self.claimed_folder = '' + # Casing stuff + self.casing_cm = False + self.casing_cases = "" + self.casing_def = False + self.casing_pro = False + self.casing_jud = False + self.casing_jur = False + self.casing_steno = False + self.case_call_time = 0 + #flood-guard stuff self.mus_counter = 0 self.mus_mute_time = 0 @@ -85,7 +103,7 @@ class ClientManager: self.send_raw_message('{}#%'.format(command)) def send_host_message(self, msg): - self.send_command('CT', self.server.config['hostname'], msg) + self.send_command('CT', self.server.config['hostname'], msg, '1') def send_motd(self): self.send_host_message('=== MOTD ===\r\n{}\r\n============='.format(self.server.config['motd'])) @@ -111,6 +129,10 @@ class ClientManager: def change_character(self, char_id, force=False): if not self.server.is_valid_char_id(char_id): raise ClientError('Invalid Character ID.') + if len(self.charcurse) > 0: + if not char_id in self.charcurse: + raise ClientError('Character not available.') + force = True if not self.area.is_char_available(char_id): if force: for client in self.area.clients: @@ -122,11 +144,12 @@ class ClientManager: self.char_id = char_id self.pos = '' self.send_command('PV', self.id, 'CID', self.char_id) + self.area.send_command('CharsCheck', *self.get_available_char_list()) logger.log_server('[{}]Changed character from {} to {}.' - .format(self.area.id, old_char, self.get_char_name()), self) + .format(self.area.abbreviation, old_char, self.get_char_name()), self) def change_music_cd(self): - if self.is_mod or self.is_cm: + if self.is_mod or self in self.area.owners: return 0 if self.mus_mute_time: if time.time() - self.mus_mute_time < self.server.config['music_change_floodguard']['mute_length']: @@ -143,7 +166,7 @@ class ClientManager: return 0 def wtce_mute(self): - if self.is_mod or self.is_cm: + if self.is_mod or self in self.area.owners: return 0 if self.wtce_mute_time: if time.time() - self.wtce_mute_time < self.server.config['wtce_floodguard']['mute_length']: @@ -168,9 +191,14 @@ class ClientManager: def change_area(self, area): if self.area == area: raise ClientError('User already in specified area.') - if area.is_locked and not self.is_mod and not self.ipid in area.invite_list: - self.send_host_message('This area is locked - you will be unable to send messages ICly.') - #raise ClientError("That area is locked!") + if area.is_locked == area.Locked.LOCKED and not self.is_mod and not self.id in area.invite_list: + raise ClientError("That area is locked!") + if area.is_locked == area.Locked.SPECTATABLE and not self.is_mod and not self.id in area.invite_list: + self.send_host_message('This area is spectatable, but not free - you will be unable to send messages ICly unless invited.') + + if self.area.jukebox: + self.area.remove_jukebox_vote(self, True) + old_area = self.area if not area.is_char_available(self.char_id): try: @@ -189,6 +217,7 @@ class ClientManager: logger.log_server( '[{}]Changed area from {} ({}) to {} ({}).'.format(self.get_char_name(), old_area.name, old_area.id, self.area.name, self.area.id), self) + self.area.send_command('CharsCheck', *self.get_available_char_list()) self.send_command('HP', 1, self.area.hp_def) self.send_command('HP', 2, self.area.hp_pro) self.send_command('BN', self.area.background) @@ -196,34 +225,50 @@ class ClientManager: def send_area_list(self): msg = '=== Areas ===' - lock = {True: '[LOCKED]', False: ''} for i, area in enumerate(self.server.area_manager.areas): owner = 'FREE' - if area.owned: - for client in [x for x in area.clients if x.is_cm]: - owner = 'MASTER: {}'.format(client.get_char_name()) - break - msg += '\r\nArea {}: {} (users: {}) [{}][{}]{}'.format(i, area.name, len(area.clients), area.status, owner, lock[area.is_locked]) + if len(area.owners) > 0: + owner = 'CM: {}'.format(area.get_cms()) + lock = {area.Locked.FREE: '', area.Locked.SPECTATABLE: '[SPECTATABLE]', area.Locked.LOCKED: '[LOCKED]'} + msg += '\r\nArea {}: {} (users: {}) [{}][{}]{}'.format(area.abbreviation, area.name, len(area.clients), area.status, owner, lock[area.is_locked]) if self.area == area: msg += ' [*]' self.send_host_message(msg) def get_area_info(self, area_id, mods): - info = '' + info = '\r\n' try: area = self.server.area_manager.get_area_by_id(area_id) except AreaError: raise - info += '= Area {}: {} =='.format(area.id, area.name) + info += '=== {} ==='.format(area.name) + info += '\r\n' + + lock = {area.Locked.FREE: '', area.Locked.SPECTATABLE: '[SPECTATABLE]', area.Locked.LOCKED: '[LOCKED]'} + info += '[{}]: [{} users][{}]{}'.format(area.abbreviation, len(area.clients), area.status, lock[area.is_locked]) + sorted_clients = [] for client in area.clients: if (not mods) or client.is_mod: sorted_clients.append(client) + for owner in area.owners: + if not (mods or owner in area.clients): + sorted_clients.append(owner) + if not sorted_clients: + return '' sorted_clients = sorted(sorted_clients, key=lambda x: x.get_char_name()) for c in sorted_clients: - info += '\r\n[{}] {}'.format(c.id, c.get_char_name()) + info += '\r\n' + if c in area.owners: + if not c in area.clients: + info += '[RCM]' + else: + info +='[CM]' + info += '[{}] {}'.format(c.id, c.get_char_name()) if self.is_mod: info += ' ({})'.format(c.ipid) + info += ': {}'.format(c.name) + return info def send_area_info(self, area_id, mods): @@ -234,13 +279,13 @@ class ClientManager: cnt = 0 info = '\n== Area List ==' for i in range(len(self.server.area_manager.areas)): - if len(self.server.area_manager.areas[i].clients) > 0: + if len(self.server.area_manager.areas[i].clients) > 0 or len(self.server.area_manager.areas[i].owners) > 0: cnt += len(self.server.area_manager.areas[i].clients) - info += '\r\n{}'.format(self.get_area_info(i, mods)) + info += '{}'.format(self.get_area_info(i, mods)) info = 'Current online: {}'.format(cnt) + info else: try: - info = 'People in this area: {}\n'.format(len(self.server.area_manager.areas[area_id].clients)) + self.get_area_info(area_id, mods) + info = 'People in this area: {}'.format(len(self.server.area_manager.areas[area_id].clients)) + self.get_area_info(area_id, mods) except AreaError: raise self.send_host_message(info) @@ -267,22 +312,34 @@ class ClientManager: self.send_host_message(info) def send_done(self): - avail_char_ids = set(range(len(self.server.char_list))) - set([x.char_id for x in self.area.clients]) - char_list = [-1] * len(self.server.char_list) - for x in avail_char_ids: - char_list[x] = 0 - self.send_command('CharsCheck', *char_list) + self.send_command('CharsCheck', *self.get_available_char_list()) self.send_command('HP', 1, self.area.hp_def) self.send_command('HP', 2, self.area.hp_pro) self.send_command('BN', self.area.background) self.send_command('LE', *self.area.get_evidence_list(self)) self.send_command('MM', 1) + + self.server.area_manager.send_arup_players() + self.server.area_manager.send_arup_status() + self.server.area_manager.send_arup_cms() + self.server.area_manager.send_arup_lock() + self.send_command('DONE') def char_select(self): self.char_id = -1 self.send_done() + def get_available_char_list(self): + if len(self.charcurse) > 0: + avail_char_ids = set(range(len(self.server.char_list))) and set(self.charcurse) + else: + avail_char_ids = set(range(len(self.server.char_list))) - set([x.char_id for x in self.area.clients]) + char_list = [-1] * len(self.server.char_list) + for x in avail_char_ids: + char_list[x] = 0 + return char_list + def auth_mod(self, password): if self.is_mod: raise ClientError('Already logged in.') @@ -302,8 +359,8 @@ class ClientManager: return self.server.char_list[self.char_id] def change_position(self, pos=''): - if pos not in ('', 'def', 'pro', 'hld', 'hlp', 'jud', 'wit'): - raise ClientError('Invalid position. Possible values: def, pro, hld, hlp, jud, wit.') + if pos not in ('', 'def', 'pro', 'hld', 'hlp', 'jud', 'wit', 'jur', 'sea'): + raise ClientError('Invalid position. Possible values: def, pro, hld, hlp, jud, wit, jur, sea.') self.pos = pos def set_mod_call_delay(self): @@ -312,9 +369,22 @@ class ClientManager: def can_call_mod(self): return (time.time() * 1000.0 - self.mod_call_time) > 0 + def set_case_call_delay(self): + self.case_call_time = round(time.time() * 1000.0 + 60000) + + def can_call_case(self): + return (time.time() * 1000.0 - self.case_call_time) > 0 + def disemvowel_message(self, message): message = re.sub("[aeiou]", "", message, flags=re.IGNORECASE) return re.sub(r"\s+", " ", message) + + def shake_message(self, message): + import random + parts = message.split() + random.shuffle(parts) + return ' '.join(parts) + def __init__(self, server): self.clients = set() @@ -329,6 +399,15 @@ class ClientManager: def remove_client(self, client): + if client.area.jukebox: + client.area.remove_jukebox_vote(client, True) + for a in self.server.area_manager.areas: + if client in a.owners: + a.owners.remove(client) + client.server.area_manager.send_arup_cms() + if len(a.owners) == 0: + if a.is_locked != a.Locked.FREE: + a.unlock() heappush(self.cur_id, client.id) self.clients.remove(client) diff --git a/tsuserver3/server/commands.py b/AO2-Client/server/commands.py index 13d50f9..d02eff2 100644 --- a/tsuserver3/server/commands.py +++ b/AO2-Client/server/commands.py @@ -16,6 +16,7 @@ # along with this program. If not, see . #possible keys: ip, OOC, id, cname, ipid, hdid import random +import re import hashlib import string from server.constants import TargetType @@ -23,6 +24,35 @@ from server.constants import TargetType from server import logger from server.exceptions import ClientError, ServerError, ArgumentError, AreaError +def ooc_cmd_a(client, arg): + if len(arg) == 0: + raise ArgumentError('You must specify an area.') + arg = arg.split(' ') + + try: + area = client.server.area_manager.get_area_by_id(int(arg[0])) + except AreaError: + raise + + message_areas_cm(client, [area], ' '.join(arg[1:])) + +def ooc_cmd_s(client, arg): + areas = [] + for a in client.server.area_manager.areas: + if client in a.owners: + areas.append(a) + if not areas: + client.send_host_message('You aren\'t a CM in any area!') + return + message_areas_cm(client, areas, arg) + +def message_areas_cm(client, areas, message): + for a in areas: + if not client in a.owners: + client.send_host_message('You are not a CM in {}!'.format(a.name)) + return + a.send_command('CT', client.name, message) + a.send_owner_command('CT', client.name, message) def ooc_cmd_switch(client, arg): if len(arg) == 0: @@ -47,7 +77,7 @@ def ooc_cmd_bg(client, arg): except AreaError: raise client.area.send_host_message('{} changed the background to {}.'.format(client.get_char_name(), arg)) - logger.log_server('[{}][{}]Changed background to {}'.format(client.area.id, client.get_char_name(), arg), client) + logger.log_server('[{}][{}]Changed background to {}'.format(client.area.abbreviation, client.get_char_name(), arg), client) def ooc_cmd_bglock(client,arg): if not client.is_mod: @@ -58,8 +88,8 @@ def ooc_cmd_bglock(client,arg): client.area.bg_lock = "false" else: client.area.bg_lock = "true" - client.area.send_host_message('A mod has set the background lock to {}.'.format(client.area.bg_lock)) - logger.log_server('[{}][{}]Changed bglock to {}'.format(client.area.id, client.get_char_name(), client.area.bg_lock), client) + client.area.send_host_message('{} [{}] has set the background lock to {}.'.format(client.get_char_name(), client.id, client.area.bg_lock)) + logger.log_server('[{}][{}]Changed bglock to {}'.format(client.area.abbreviation, client.get_char_name(), client.area.bg_lock), client) def ooc_cmd_evidence_mod(client, arg): if not client.is_mod: @@ -89,7 +119,21 @@ def ooc_cmd_allow_iniswap(client, arg): client.send_host_message('iniswap is {}.'.format(answer[client.area.iniswap_allowed])) return +def ooc_cmd_allow_blankposting(client, arg): + if not client.is_mod and not client in client.area.owners: + raise ClientError('You must be authorized to do that.') + client.area.blankposting_allowed = not client.area.blankposting_allowed + answer = {True: 'allowed', False: 'forbidden'} + client.area.send_host_message('{} [{}] has set blankposting in the area to {}.'.format(client.get_char_name(), client.id, answer[client.area.blankposting_allowed])) + return +def ooc_cmd_force_nonint_pres(client, arg): + if not client.is_mod and not client in client.area.owners: + raise ClientError('You must be authorized to do that.') + client.area.non_int_pres_only = not client.area.non_int_pres_only + answer = {True: 'non-interrupting only', False: 'non-interrupting or interrupting as you choose'} + client.area.send_host_message('{} [{}] has set pres in the area to be {}.'.format(client.get_char_name(), client.id, answer[client.area.non_int_pres_only])) + return def ooc_cmd_roll(client, arg): roll_max = 11037 @@ -116,7 +160,7 @@ def ooc_cmd_roll(client, arg): roll = '(' + roll + ')' client.area.send_host_message('{} rolled {} out of {}.'.format(client.get_char_name(), roll, val[0])) logger.log_server( - '[{}][{}]Used /roll and got {} out of {}.'.format(client.area.id, client.get_char_name(), roll, val[0])) + '[{}][{}]Used /roll and got {} out of {}.'.format(client.area.abbreviation, client.get_char_name(), roll, val[0]), client) def ooc_cmd_rollp(client, arg): roll_max = 11037 @@ -126,13 +170,13 @@ def ooc_cmd_rollp(client, arg): if not 1 <= val[0] <= roll_max: raise ArgumentError('Roll value must be between 1 and {}.'.format(roll_max)) except ValueError: - raise ArgumentError('Wrong argument. Use /roll [] []') + raise ArgumentError('Wrong argument. Use /rollp [] []') else: val = [6] if len(val) == 1: val.append(1) if len(val) > 2: - raise ArgumentError('Too many arguments. Use /roll [] []') + raise ArgumentError('Too many arguments. Use /rollp [] []') if val[1] > 20 or val[1] < 1: raise ArgumentError('Num of rolls must be between 1 and 20') roll = '' @@ -142,19 +186,97 @@ def ooc_cmd_rollp(client, arg): if val[1] > 1: roll = '(' + roll + ')' client.send_host_message('{} rolled {} out of {}.'.format(client.get_char_name(), roll, val[0])) - client.area.send_host_message('{} rolled.'.format(client.get_char_name(), roll, val[0])) - SALT = ''.join(random.choices(string.ascii_uppercase + string.digits, k=16)) + + client.area.send_host_message('{} rolled in secret.'.format(client.get_char_name())) + for c in client.area.owners: + c.send_host_message('[{}]{} secretly rolled {} out of {}.'.format(client.area.abbreviation, client.get_char_name(), roll, val[0])) + logger.log_server( - '[{}][{}]Used /roll and got {} out of {}.'.format(client.area.id, client.get_char_name(), hashlib.sha1((str(roll) + SALT).encode('utf-8')).hexdigest() + '|' + SALT, val[0])) + '[{}][{}]Used /rollp and got {} out of {}.'.format(client.area.abbreviation, client.get_char_name(), roll, val[0]), client) def ooc_cmd_currentmusic(client, arg): if len(arg) != 0: raise ArgumentError('This command has no arguments.') if client.area.current_music == '': raise ClientError('There is no music currently playing.') - client.send_host_message('The current music is {} and was played by {}.'.format(client.area.current_music, + if client.is_mod: + client.send_host_message('The current music is {} and was played by {} ({}).'.format(client.area.current_music, + client.area.current_music_player, client.area.current_music_player_ipid)) + else: + client.send_host_message('The current music is {} and was played by {}.'.format(client.area.current_music, client.area.current_music_player)) +def ooc_cmd_jukebox_toggle(client, arg): + if not client.is_mod and not client in client.area.owners: + raise ClientError('You must be authorized to do that.') + if len(arg) != 0: + raise ArgumentError('This command has no arguments.') + client.area.jukebox = not client.area.jukebox + client.area.jukebox_votes = [] + client.area.send_host_message('{} [{}] has set the jukebox to {}.'.format(client.get_char_name(), client.id, client.area.jukebox)) + +def ooc_cmd_jukebox_skip(client, arg): + if not client.is_mod and not client in client.area.owners: + raise ClientError('You must be authorized to do that.') + if len(arg) != 0: + raise ArgumentError('This command has no arguments.') + if not client.area.jukebox: + raise ClientError('This area does not have a jukebox.') + if len(client.area.jukebox_votes) == 0: + raise ClientError('There is no song playing right now, skipping is pointless.') + client.area.start_jukebox() + if len(client.area.jukebox_votes) == 1: + client.area.send_host_message('{} [{}] has forced a skip, restarting the only jukebox song.'.format(client.get_char_name(), client.id)) + else: + client.area.send_host_message('{} [{}] has forced a skip to the next jukebox song.'.format(client.get_char_name(), client.id)) + logger.log_server('[{}][{}]Skipped the current jukebox song.'.format(client.area.abbreviation, client.get_char_name()), client) + +def ooc_cmd_jukebox(client, arg): + if len(arg) != 0: + raise ArgumentError('This command has no arguments.') + if not client.area.jukebox: + raise ClientError('This area does not have a jukebox.') + if len(client.area.jukebox_votes) == 0: + client.send_host_message('The jukebox has no songs in it.') + else: + total = 0 + songs = [] + voters = dict() + chance = dict() + message = '' + + for current_vote in client.area.jukebox_votes: + if songs.count(current_vote.name) == 0: + songs.append(current_vote.name) + voters[current_vote.name] = [current_vote.client] + chance[current_vote.name] = current_vote.chance + else: + voters[current_vote.name].append(current_vote.client) + chance[current_vote.name] += current_vote.chance + total += current_vote.chance + + for song in songs: + message += '\n- ' + song + '\n' + message += '-- VOTERS: ' + + first = True + for voter in voters[song]: + if first: + first = False + else: + message += ', ' + message += voter.get_char_name() + ' [' + str(voter.id) + ']' + if client.is_mod: + message += '(' + str(voter.ipid) + ')' + message += '\n' + + if total == 0: + message += '-- CHANCE: 100' + else: + message += '-- CHANCE: ' + str(round(chance[song] / total * 100)) + + client.send_host_message('The jukebox has the following songs in it:{}'.format(message)) + def ooc_cmd_coinflip(client, arg): if len(arg) != 0: raise ArgumentError('This command has no arguments.') @@ -162,7 +284,7 @@ def ooc_cmd_coinflip(client, arg): flip = random.choice(coin) client.area.send_host_message('{} flipped a coin and got {}.'.format(client.get_char_name(), flip)) logger.log_server( - '[{}][{}]Used /coinflip and got {}.'.format(client.area.id, client.get_char_name(), flip)) + '[{}][{}]Used /coinflip and got {}.'.format(client.area.abbreviation, client.get_char_name(), flip), client) def ooc_cmd_motd(client, arg): if len(arg) != 0: @@ -182,7 +304,7 @@ def ooc_cmd_pos(client, arg): client.send_host_message('Position changed.') def ooc_cmd_forcepos(client, arg): - if not client.is_cm and not client.is_mod: + if not client in client.area.owners and not client.is_mod: raise ClientError('You must be authorized to do that.') args = arg.split() @@ -222,60 +344,81 @@ def ooc_cmd_forcepos(client, arg): client.area.send_host_message( '{} forced {} client(s) into /pos {}.'.format(client.get_char_name(), len(targets), pos)) logger.log_server( - '[{}][{}]Used /forcepos {} for {} client(s).'.format(client.area.id, client.get_char_name(), pos, len(targets))) + '[{}][{}]Used /forcepos {} for {} client(s).'.format(client.area.abbreviation, client.get_char_name(), pos, len(targets)), client) def ooc_cmd_help(client, arg): if len(arg) != 0: raise ArgumentError('This command has no arguments.') - help_url = 'https://github.com/AttorneyOnline/tsuserver3/blob/master/README.md' - help_msg = 'Available commands, source code and issues can be found here: {}'.format(help_url) + help_url = 'http://casecafe.byethost14.com/commandlist' + help_msg = 'The commands available on this server can be found here: {}'.format(help_url) client.send_host_message(help_msg) def ooc_cmd_kick(client, arg): if not client.is_mod: raise ClientError('You must be authorized to do that.') if len(arg) == 0: - raise ArgumentError('You must specify a target. Use /kick .') - targets = client.server.client_manager.get_targets(client, TargetType.IPID, int(arg), False) - if targets: - for c in targets: - logger.log_server('Kicked {}.'.format(c.ipid), client) - client.send_host_message("{} was kicked.".format(c.get_char_name())) - c.disconnect() - else: - client.send_host_message("No targets found.") - -def ooc_cmd_ban(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - try: - ipid = int(arg.strip()) - except: - raise ClientError('You must specify ipid') - try: - client.server.ban_manager.add_ban(ipid) - except ServerError: - raise - if ipid != None: + raise ArgumentError('You must specify a target. Use /kick ...') + args = list(arg.split(' ')) + client.send_host_message('Attempting to kick {} IPIDs.'.format(len(args))) + for raw_ipid in args: + try: + ipid = int(raw_ipid) + except: + raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) targets = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) if targets: for c in targets: + logger.log_server('Kicked {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) + logger.log_mod('Kicked {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) + client.send_host_message("{} was kicked.".format(c.get_char_name())) + c.send_command('KK', c.char_id) c.disconnect() - client.send_host_message('{} clients was kicked.'.format(len(targets))) - client.send_host_message('{} was banned.'.format(ipid)) - logger.log_server('Banned {}.'.format(ipid), client) + else: + client.send_host_message("No targets with the IPID {} were found.".format(ipid)) + +def ooc_cmd_ban(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + if len(arg) == 0: + raise ArgumentError('You must specify a target. Use /ban ...') + args = list(arg.split(' ')) + client.send_host_message('Attempting to ban {} IPIDs.'.format(len(args))) + for raw_ipid in args: + try: + ipid = int(raw_ipid) + except: + raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) + try: + client.server.ban_manager.add_ban(ipid) + except ServerError: + raise + if ipid != None: + targets = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) + if targets: + for c in targets: + c.send_command('KB', c.char_id) + c.disconnect() + client.send_host_message('{} clients was kicked.'.format(len(targets))) + client.send_host_message('{} was banned.'.format(ipid)) + logger.log_server('Banned {}.'.format(ipid), client) + logger.log_mod('Banned {}.'.format(ipid), client) def ooc_cmd_unban(client, arg): if not client.is_mod: raise ClientError('You must be authorized to do that.') - try: - client.server.ban_manager.remove_ban(int(arg.strip())) - except: - raise ClientError('You must specify \'hdid\'') - logger.log_server('Unbanned {}.'.format(arg), client) - client.send_host_message('Unbanned {}'.format(arg)) - - + if len(arg) == 0: + raise ArgumentError('You must specify a target. Use /unban ...') + args = list(arg.split(' ')) + client.send_host_message('Attempting to unban {} IPIDs.'.format(len(args))) + for raw_ipid in args: + try: + client.server.ban_manager.remove_ban(int(raw_ipid)) + except: + raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) + logger.log_server('Unbanned {}.'.format(raw_ipid), client) + logger.log_mod('Unbanned {}.'.format(raw_ipid), client) + client.send_host_message('Unbanned {}'.format(raw_ipid)) + def ooc_cmd_play(client, arg): if not client.is_mod: raise ClientError('You must be authorized to do that.') @@ -283,31 +426,59 @@ def ooc_cmd_play(client, arg): raise ArgumentError('You must specify a song.') client.area.play_music(arg, client.char_id, -1) client.area.add_music_playing(client, arg) - logger.log_server('[{}][{}]Changed music to {}.'.format(client.area.id, client.get_char_name(), arg), client) + logger.log_server('[{}][{}]Changed music to {}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) def ooc_cmd_mute(client, arg): if not client.is_mod: raise ClientError('You must be authorized to do that.') if len(arg) == 0: - raise ArgumentError('You must specify a target.') - try: - c = client.server.client_manager.get_targets(client, TargetType.IPID, int(arg), False)[0] - c.is_muted = True - client.send_host_message('{} existing client(s).'.format(c.get_char_name())) - except: - client.send_host_message("No targets found. Use /mute for mute") + raise ArgumentError('You must specify a target. Use /mute .') + args = list(arg.split(' ')) + client.send_host_message('Attempting to mute {} IPIDs.'.format(len(args))) + for raw_ipid in args: + if raw_ipid.isdigit(): + ipid = int(raw_ipid) + clients = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) + if (clients): + msg = 'Muted the IPID ' + str(ipid) + '\'s following clients:' + for c in clients: + c.is_muted = True + logger.log_server('Muted {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) + logger.log_mod('Muted {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) + msg += ' ' + c.get_char_name() + ' [' + str(c.id) + '],' + msg = msg[:-1] + msg += '.' + client.send_host_message('{}'.format(msg)) + else: + client.send_host_message("No targets found. Use /mute ... for mute.") + else: + client.send_host_message('{} does not look like a valid IPID.'.format(raw_ipid)) def ooc_cmd_unmute(client, arg): if not client.is_mod: raise ClientError('You must be authorized to do that.') if len(arg) == 0: raise ArgumentError('You must specify a target.') - try: - c = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False)[0] - c.is_muted = False - client.send_host_message('{} existing client(s).'.format(c.get_char_name())) - except: - client.send_host_message("No targets found. Use /mute for mute") + args = list(arg.split(' ')) + client.send_host_message('Attempting to unmute {} IPIDs.'.format(len(args))) + for raw_ipid in args: + if raw_ipid.isdigit(): + ipid = int(raw_ipid) + clients = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) + if (clients): + msg = 'Unmuted the IPID ' + str(ipid) + '\'s following clients::' + for c in clients: + c.is_muted = False + logger.log_server('Unmuted {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) + logger.log_mod('Unmuted {} [{}]({}).'.format(c.get_char_name(), c.id, c.ipid), client) + msg += ' ' + c.get_char_name() + ' [' + str(c.id) + '],' + msg = msg[:-1] + msg += '.' + client.send_host_message('{}'.format(msg)) + else: + client.send_host_message("No targets found. Use /unmute ... for unmute.") + else: + client.send_host_message('{} does not look like a valid IPID.'.format(raw_ipid)) def ooc_cmd_login(client, arg): if len(arg) == 0: @@ -320,6 +491,7 @@ def ooc_cmd_login(client, arg): client.area.broadcast_evidence_list() client.send_host_message('Logged in as a moderator.') logger.log_server('Logged in as moderator.', client) + logger.log_mod('Logged in as moderator.', client) def ooc_cmd_g(client, arg): if client.muted_global: @@ -327,7 +499,7 @@ def ooc_cmd_g(client, arg): if len(arg) == 0: raise ArgumentError("You can't send an empty message.") client.server.broadcast_global(client, arg) - logger.log_server('[{}][{}][GLOBAL]{}.'.format(client.area.id, client.get_char_name(), arg), client) + logger.log_server('[{}][{}][GLOBAL]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) def ooc_cmd_gm(client, arg): if not client.is_mod: @@ -337,7 +509,17 @@ def ooc_cmd_gm(client, arg): if len(arg) == 0: raise ArgumentError("Can't send an empty message.") client.server.broadcast_global(client, arg, True) - logger.log_server('[{}][{}][GLOBAL-MOD]{}.'.format(client.area.id, client.get_char_name(), arg), client) + logger.log_server('[{}][{}][GLOBAL-MOD]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) + logger.log_mod('[{}][{}][GLOBAL-MOD]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) + +def ooc_cmd_m(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + if len(arg) == 0: + raise ArgumentError("You can't send an empty message.") + client.server.send_modchat(client, arg) + logger.log_server('[{}][{}][MODCHAT]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) + logger.log_mod('[{}][{}][MODCHAT]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) def ooc_cmd_lm(client, arg): if not client.is_mod: @@ -346,7 +528,8 @@ def ooc_cmd_lm(client, arg): raise ArgumentError("Can't send an empty message.") client.area.send_command('CT', '{}[MOD][{}]' .format(client.server.config['hostname'], client.get_char_name()), arg) - logger.log_server('[{}][{}][LOCAL-MOD]{}.'.format(client.area.id, client.get_char_name(), arg), client) + logger.log_server('[{}][{}][LOCAL-MOD]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) + logger.log_mod('[{}][{}][LOCAL-MOD]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) def ooc_cmd_announce(client, arg): if not client.is_mod: @@ -354,8 +537,9 @@ def ooc_cmd_announce(client, arg): if len(arg) == 0: raise ArgumentError("Can't send an empty message.") client.server.send_all_cmd_pred('CT', '{}'.format(client.server.config['hostname']), - '=== Announcement ===\r\n{}\r\n=================='.format(arg)) - logger.log_server('[{}][{}][ANNOUNCEMENT]{}.'.format(client.area.id, client.get_char_name(), arg), client) + '=== Announcement ===\r\n{}\r\n=================='.format(arg), '1') + logger.log_server('[{}][{}][ANNOUNCEMENT]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) + logger.log_mod('[{}][{}][ANNOUNCEMENT]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) def ooc_cmd_toggleglobal(client, arg): if len(arg) != 0: @@ -373,7 +557,7 @@ def ooc_cmd_need(client, arg): if len(arg) == 0: raise ArgumentError("You must specify what you need.") client.server.broadcast_need(client, arg) - logger.log_server('[{}][{}][NEED]{}.'.format(client.area.id, client.get_char_name(), arg), client) + logger.log_server('[{}][{}][NEED]{}.'.format(client.area.abbreviation, client.get_char_name(), arg), client) def ooc_cmd_toggleadverts(client, arg): if len(arg) != 0: @@ -388,11 +572,11 @@ def ooc_cmd_doc(client, arg): if len(arg) == 0: client.send_host_message('Document: {}'.format(client.area.doc)) logger.log_server( - '[{}][{}]Requested document. Link: {}'.format(client.area.id, client.get_char_name(), client.area.doc)) + '[{}][{}]Requested document. Link: {}'.format(client.area.abbreviation, client.get_char_name(), client.area.doc), client) else: client.area.change_doc(arg) client.area.send_host_message('{} changed the doc link.'.format(client.get_char_name())) - logger.log_server('[{}][{}]Changed document to: {}'.format(client.area.id, client.get_char_name(), arg)) + logger.log_server('[{}][{}]Changed document to: {}'.format(client.area.abbreviation, client.get_char_name(), arg), client) def ooc_cmd_cleardoc(client, arg): @@ -400,7 +584,7 @@ def ooc_cmd_cleardoc(client, arg): raise ArgumentError('This command has no arguments.') client.area.send_host_message('{} cleared the doc link.'.format(client.get_char_name())) logger.log_server('[{}][{}]Cleared document. Old link: {}' - .format(client.area.id, client.get_char_name(), client.area.doc)) + .format(client.area.abbreviation, client.get_char_name(), client.area.doc), client) client.area.change_doc() @@ -412,7 +596,7 @@ def ooc_cmd_status(client, arg): client.area.change_status(arg) client.area.send_host_message('{} changed status to {}.'.format(client.get_char_name(), client.area.status)) logger.log_server( - '[{}][{}]Changed status to {}'.format(client.area.id, client.get_char_name(), client.area.status)) + '[{}][{}]Changed status to {}'.format(client.area.abbreviation, client.get_char_name(), client.area.status), client) except AreaError: raise @@ -466,7 +650,10 @@ def ooc_cmd_pm(client, arg): if c.pm_mute: raise ClientError('This user muted all pm conversation') else: - c.send_host_message('PM from {} in {} ({}): {}'.format(client.name, client.area.name, client.get_char_name(), msg)) + if c.is_mod: + c.send_host_message('PM from {} (ID: {}, IPID: {}) in {} ({}): {}'.format(client.name, client.id, client.ipid, client.area.name, client.get_char_name(), msg)) + else: + c.send_host_message('PM from {} (ID: {}) in {} ({}): {}'.format(client.name, client.id, client.area.name, client.get_char_name(), msg)) client.send_host_message('PM sent to {}. Message: {}'.format(args[0], msg)) def ooc_cmd_mutepm(client, arg): @@ -497,10 +684,13 @@ def ooc_cmd_reload(client, arg): def ooc_cmd_randomchar(client, arg): if len(arg) != 0: raise ArgumentError('This command has no arguments.') - try: - free_id = client.area.get_rand_avail_char_id() - except AreaError: - raise + if len(client.charcurse) > 0: + free_id = random.choice(client.charcurse) + else: + try: + free_id = client.area.get_rand_avail_char_id() + except AreaError: + raise try: client.change_character(free_id) except ClientError: @@ -529,12 +719,105 @@ def ooc_cmd_evi_swap(client, arg): def ooc_cmd_cm(client, arg): if 'CM' not in client.area.evidence_mod: raise ClientError('You can\'t become a CM in this area') - if client.area.owned == False: - client.area.owned = True - client.is_cm = True + if len(client.area.owners) == 0: + if len(arg) > 0: + raise ArgumentError('You cannot \'nominate\' people to be CMs when you are not one.') + client.area.owners.append(client) if client.area.evidence_mod == 'HiddenCM': client.area.broadcast_evidence_list() - client.area.send_host_message('{} is CM in this area now.'.format(client.get_char_name())) + client.server.area_manager.send_arup_cms() + client.area.send_host_message('{} [{}] is CM in this area now.'.format(client.get_char_name(), client.id)) + elif client in client.area.owners: + if len(arg) > 0: + arg = arg.split(' ') + for id in arg: + try: + id = int(id) + c = client.server.client_manager.get_targets(client, TargetType.ID, id, False)[0] + if c in client.area.owners: + client.send_host_message('{} [{}] is already a CM here.'.format(c.get_char_name(), c.id)) + else: + client.area.owners.append(c) + if client.area.evidence_mod == 'HiddenCM': + client.area.broadcast_evidence_list() + client.server.area_manager.send_arup_cms() + client.area.send_host_message('{} [{}] is CM in this area now.'.format(c.get_char_name(), c.id)) + except: + client.send_host_message('{} does not look like a valid ID.'.format(id)) + else: + raise ClientError('You must be authorized to do that.') + + +def ooc_cmd_uncm(client, arg): + if client in client.area.owners: + if len(arg) > 0: + arg = arg.split(' ') + else: + arg = [client.id] + for id in arg: + try: + id = int(id) + c = client.server.client_manager.get_targets(client, TargetType.ID, id, False)[0] + if c in client.area.owners: + client.area.owners.remove(c) + client.server.area_manager.send_arup_cms() + client.area.send_host_message('{} [{}] is no longer CM in this area.'.format(c.get_char_name(), c.id)) + else: + client.send_host_message('You cannot remove someone from CMing when they aren\'t a CM.') + except: + client.send_host_message('{} does not look like a valid ID.'.format(id)) + else: + raise ClientError('You must be authorized to do that.') + +def ooc_cmd_setcase(client, arg): + args = re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', arg) + if len(args) == 0: + raise ArgumentError('Please do not call this command manually!') + else: + client.casing_cases = args[0] + client.casing_cm = args[1] == "1" + client.casing_def = args[2] == "1" + client.casing_pro = args[3] == "1" + client.casing_jud = args[4] == "1" + client.casing_jur = args[5] == "1" + client.casing_steno = args[6] == "1" + +def ooc_cmd_anncase(client, arg): + if client in client.area.owners: + if not client.can_call_case(): + raise ClientError('Please wait 60 seconds between case announcements!') + args = re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', arg) + if len(args) == 0: + raise ArgumentError('Please do not call this command manually!') + elif len(args) == 1: + raise ArgumentError('You should probably announce the case to at least one person.') + else: + if not args[1] == "1" and not args[2] == "1" and not args[3] == "1" and not args[4] == "1" and not args[5] == "1": + raise ArgumentError('You should probably announce the case to at least one person.') + msg = '=== Case Announcement ===\r\n{} [{}] is hosting {}, looking for '.format(client.get_char_name(), client.id, args[0]) + + lookingfor = [] + + if args[1] == "1": + lookingfor.append("defence") + if args[2] == "1": + lookingfor.append("prosecutor") + if args[3] == "1": + lookingfor.append("judge") + if args[4] == "1": + lookingfor.append("juror") + if args[5] == "1": + lookingfor.append("stenographer") + + msg = msg + ', '.join(lookingfor) + '.\r\n==================' + + client.server.send_all_cmd_pred('CASEA', msg, args[1], args[2], args[3], args[4], args[5], '1') + + client.set_case_call_delay() + + logger.log_server('[{}][{}][CASE_ANNOUNCEMENT]{}, DEF: {}, PRO: {}, JUD: {}, JUR: {}, STENO: {}.'.format(client.area.abbreviation, client.get_char_name(), args[0], args[1], args[2], args[3], args[4], args[5]), client) + else: + raise ClientError('You cannot announce a case in an area where you are not a CM!') def ooc_cmd_unmod(client, arg): client.is_mod = False @@ -546,21 +829,30 @@ def ooc_cmd_area_lock(client, arg): if not client.area.locking_allowed: client.send_host_message('Area locking is disabled in this area.') return - if client.area.is_locked: + if client.area.is_locked == client.area.Locked.LOCKED: client.send_host_message('Area is already locked.') - if client.is_cm: - client.area.is_locked = True - client.area.send_host_message('Area is locked.') - for i in client.area.clients: - client.area.invite_list[i.ipid] = None + if client in client.area.owners: + client.area.lock() return else: raise ClientError('Only CM can lock the area.') + +def ooc_cmd_area_spectate(client, arg): + if not client.area.locking_allowed: + client.send_host_message('Area locking is disabled in this area.') + return + if client.area.is_locked == client.area.Locked.SPECTATABLE: + client.send_host_message('Area is already spectatable.') + if client in client.area.owners: + client.area.spectator() + return + else: + raise ClientError('Only CM can make the area spectatable.') def ooc_cmd_area_unlock(client, arg): - if not client.area.is_locked: + if client.area.is_locked == client.area.Locked.FREE: raise ClientError('Area is already unlocked.') - if not client.is_cm: + if not client in client.area.owners: raise ClientError('Only CM can unlock area.') client.area.unlock() client.send_host_message('Area is unlocked.') @@ -568,22 +860,22 @@ def ooc_cmd_area_unlock(client, arg): def ooc_cmd_invite(client, arg): if not arg: raise ClientError('You must specify a target. Use /invite ') - if not client.area.is_locked: + if client.area.is_locked == client.area.Locked.FREE: raise ClientError('Area isn\'t locked.') - if not client.is_cm or client.is_mod: + if not client in client.area.owners and not client.is_mod: raise ClientError('You must be authorized to do that.') try: c = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False)[0] - client.area.invite_list[c.ipid] = None + client.area.invite_list[c.id] = None client.send_host_message('{} is invited to your area.'.format(c.get_char_name())) - c.send_host_message('You were invited and given access to area {}.'.format(client.area.id)) + c.send_host_message('You were invited and given access to {}.'.format(client.area.name)) except: raise ClientError('You must specify a target. Use /invite ') def ooc_cmd_uninvite(client, arg): - if not client.is_cm or client.is_mod: + if not client in client.area.owners and not client.is_mod: raise ClientError('You must be authorized to do that.') - if not client.area.is_locked and not client.is_mod: + if client.area.is_locked == client.area.Locked.FREE: raise ClientError('Area isn\'t locked.') if not arg: raise ClientError('You must specify a target. Use /uninvite ') @@ -594,8 +886,8 @@ def ooc_cmd_uninvite(client, arg): for c in targets: client.send_host_message("You have removed {} from the whitelist.".format(c.get_char_name())) c.send_host_message("You were removed from the area whitelist.") - if client.area.is_locked: - client.area.invite_list.pop(c.ipid) + if client.area.is_locked != client.area.Locked.FREE: + client.area.invite_list.pop(c.id) except AreaError: raise except ClientError: @@ -606,7 +898,7 @@ def ooc_cmd_uninvite(client, arg): def ooc_cmd_area_kick(client, arg): if not client.is_mod: raise ClientError('You must be authorized to do that.') - if not client.area.is_locked and not client.is_mod: + if client.area.is_locked == client.area.Locked.FREE: raise ClientError('Area isn\'t locked.') if not arg: raise ClientError('You must specify a target. Use /area_kick [destination #]') @@ -627,8 +919,8 @@ def ooc_cmd_area_kick(client, arg): client.send_host_message("Attempting to kick {} to area {}.".format(c.get_char_name(), output)) c.change_area(area) c.send_host_message("You were kicked from the area to area {}.".format(output)) - if client.area.is_locked: - client.area.invite_list.pop(c.ipid) + if client.area.is_locked != client.area.Locked.FREE: + client.area.invite_list.pop(c.id) except AreaError: raise except ClientError: @@ -653,10 +945,10 @@ def ooc_cmd_ooc_unmute(client, arg): if not client.is_mod: raise ClientError('You must be authorized to do that.') if len(arg) == 0: - raise ArgumentError('You must specify a target. Use /ooc_mute .') - targets = client.server.client_manager.get_targets(client, TargetType.ID, arg, False) + raise ArgumentError('You must specify a target. Use /ooc_unmute .') + targets = client.server.client_manager.get_ooc_muted_clients() if not targets: - raise ArgumentError('Target not found. Use /ooc_mute .') + raise ArgumentError('Targets not found. Use /ooc_unmute .') for target in targets: target.is_ooc_muted = False client.send_host_message('Unmuted {} existing client(s).'.format(len(targets))) @@ -673,6 +965,7 @@ def ooc_cmd_disemvowel(client, arg): if targets: for c in targets: logger.log_server('Disemvowelling {}.'.format(c.get_ip()), client) + logger.log_mod('Disemvowelling {}.'.format(c.get_ip()), client) c.disemvowel = True client.send_host_message('Disemvowelled {} existing client(s).'.format(len(targets))) else: @@ -686,15 +979,120 @@ def ooc_cmd_undisemvowel(client, arg): try: targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) except: - raise ArgumentError('You must specify a target. Use /disemvowel .') + raise ArgumentError('You must specify a target. Use /undisemvowel .') if targets: for c in targets: logger.log_server('Undisemvowelling {}.'.format(c.get_ip()), client) + logger.log_mod('Undisemvowelling {}.'.format(c.get_ip()), client) c.disemvowel = False client.send_host_message('Undisemvowelled {} existing client(s).'.format(len(targets))) else: client.send_host_message('No targets found.') +def ooc_cmd_shake(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + elif len(arg) == 0: + raise ArgumentError('You must specify a target.') + try: + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) + except: + raise ArgumentError('You must specify a target. Use /shake .') + if targets: + for c in targets: + logger.log_server('Shaking {}.'.format(c.get_ip()), client) + logger.log_mod('Shaking {}.'.format(c.get_ip()), client) + c.shaken = True + client.send_host_message('Shook {} existing client(s).'.format(len(targets))) + else: + client.send_host_message('No targets found.') + +def ooc_cmd_unshake(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + elif len(arg) == 0: + raise ArgumentError('You must specify a target.') + try: + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) + except: + raise ArgumentError('You must specify a target. Use /unshake .') + if targets: + for c in targets: + logger.log_server('Unshaking {}.'.format(c.get_ip()), client) + logger.log_mod('Unshaking {}.'.format(c.get_ip()), client) + c.shaken = False + client.send_host_message('Unshook {} existing client(s).'.format(len(targets))) + else: + client.send_host_message('No targets found.') + +def ooc_cmd_charcurse(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + elif len(arg) == 0: + raise ArgumentError('You must specify a target (an ID) and at least one character ID. Consult /charids for the character IDs.') + elif len(arg) == 1: + raise ArgumentError('You must specific at least one character ID. Consult /charids for the character IDs.') + args = arg.split() + try: + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) + except: + raise ArgumentError('You must specify a valid target! Make sure it is a valid ID.') + if targets: + for c in targets: + log_msg = ' ' + str(c.get_ip()) + ' to' + part_msg = ' [' + str(c.id) + '] to' + for raw_cid in args[1:]: + try: + cid = int(raw_cid) + c.charcurse.append(cid) + part_msg += ' ' + str(client.server.char_list[cid]) + ',' + log_msg += ' ' + str(client.server.char_list[cid]) + ',' + except: + ArgumentError('' + str(raw_cid) + ' does not look like a valid character ID.') + part_msg = part_msg[:-1] + part_msg += '.' + log_msg = log_msg[:-1] + log_msg += '.' + c.char_select() + logger.log_server('Charcursing' + log_msg, client) + logger.log_mod('Charcursing' + log_msg, client) + client.send_host_message('Charcursed' + part_msg) + else: + client.send_host_message('No targets found.') + +def ooc_cmd_uncharcurse(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + elif len(arg) == 0: + raise ArgumentError('You must specify a target (an ID).') + args = arg.split() + try: + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) + except: + raise ArgumentError('You must specify a valid target! Make sure it is a valid ID.') + if targets: + for c in targets: + if len(c.charcurse) > 0: + c.charcurse = [] + logger.log_server('Uncharcursing {}.'.format(c.get_ip()), client) + logger.log_mod('Uncharcursing {}.'.format(c.get_ip()), client) + client.send_host_message('Uncharcursed [{}].'.format(c.id)) + c.char_select() + else: + client.send_host_message('[{}] is not charcursed.'.format(c.id)) + else: + client.send_host_message('No targets found.') + +def ooc_cmd_charids(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + if len(arg) != 0: + raise ArgumentError("This command doesn't take any arguments") + msg = 'Here is a list of all available characters on the server:' + for c in range(0, len(client.server.char_list)): + msg += '\n[' + str(c) + '] ' + client.server.char_list[c] + client.send_host_message(msg) + def ooc_cmd_blockdj(client, arg): if not client.is_mod: raise ClientError('You must be authorized to do that.') @@ -709,6 +1107,9 @@ def ooc_cmd_blockdj(client, arg): for target in targets: target.is_dj = False target.send_host_message('A moderator muted you from changing the music.') + logger.log_server('BlockDJ\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) + logger.log_mod('BlockDJ\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) + target.area.remove_jukebox_vote(target, True) client.send_host_message('blockdj\'d {}.'.format(targets[0].get_char_name())) def ooc_cmd_unblockdj(client, arg): @@ -725,6 +1126,8 @@ def ooc_cmd_unblockdj(client, arg): for target in targets: target.is_dj = True target.send_host_message('A moderator unmuted you from changing the music.') + logger.log_server('UnblockDJ\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) + logger.log_mod('UnblockDJ\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) client.send_host_message('Unblockdj\'d {}.'.format(targets[0].get_char_name())) def ooc_cmd_blockwtce(client, arg): @@ -741,6 +1144,8 @@ def ooc_cmd_blockwtce(client, arg): for target in targets: target.can_wtce = False target.send_host_message('A moderator blocked you from using judge signs.') + logger.log_server('BlockWTCE\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) + logger.log_mod('BlockWTCE\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) client.send_host_message('blockwtce\'d {}.'.format(targets[0].get_char_name())) def ooc_cmd_unblockwtce(client, arg): @@ -757,6 +1162,8 @@ def ooc_cmd_unblockwtce(client, arg): for target in targets: target.can_wtce = True target.send_host_message('A moderator unblocked you from using judge signs.') + logger.log_server('UnblockWTCE\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) + logger.log_mod('UnblockWTCE\'d {} [{}]({}).'.format(target.get_char_name(), target.id, target.get_ip()), client) client.send_host_message('unblockwtce\'d {}.'.format(targets[0].get_char_name())) def ooc_cmd_notecard(client, arg): @@ -773,7 +1180,7 @@ def ooc_cmd_notecard_clear(client, arg): raise ClientError('You do not have a note card.') def ooc_cmd_notecard_reveal(client, arg): - if not client.is_cm and not client.is_mod: + if not client in client.area.owners and not client.is_mod: raise ClientError('You must be a CM or moderator to reveal cards.') if len(client.area.cards) == 0: raise ClientError('There are no cards to reveal in this area.') @@ -800,7 +1207,7 @@ def rolla_reload(area): def ooc_cmd_rolla_set(client, arg): if not hasattr(client.area, 'ability_dice'): rolla_reload(client.area) - available_sets = client.area.ability_dice.keys() + available_sets = ', '.join(client.area.ability_dice.keys()) if len(arg) == 0: raise ArgumentError('You must specify the ability set name.\nAvailable sets: {}'.format(available_sets)) if arg in client.area.ability_dice: diff --git a/tsuserver3/server/districtclient.py b/AO2-Client/server/districtclient.py index adc29ec..c766ba5 100644 --- a/tsuserver3/server/districtclient.py +++ b/AO2-Client/server/districtclient.py @@ -60,7 +60,7 @@ class DistrictClient: elif cmd == 'NEED': need_msg = '=== Cross Advert ===\r\n{} at {} in {} [{}] needs {}\r\n====================' \ .format(args[1], args[0], args[2], args[3], args[4]) - self.server.send_all_cmd_pred('CT', '{}'.format(self.server.config['hostname']), need_msg, + self.server.send_all_cmd_pred('CT', '{}'.format(self.server.config['hostname']), need_msg, '1', pred=lambda x: not x.muted_adverts) async def write_queue(self): diff --git a/tsuserver3/server/evidence.py b/AO2-Client/server/evidence.py index ddd9ba3..b34172a 100644 --- a/tsuserver3/server/evidence.py +++ b/AO2-Client/server/evidence.py @@ -24,19 +24,28 @@ class EvidenceList: def __init__(self): self.evidences = [] - self.poses = {'def':['def', 'hld'], 'pro':['pro', 'hlp'], 'wit':['wit'], 'hlp':['hlp', 'pro'], 'hld':['hld', 'def'], 'jud':['jud'], 'all':['hlp', 'hld', 'wit', 'jud', 'pro', 'def', ''], 'pos':[]} + self.poses = {'def':['def', 'hld'], + 'pro':['pro', 'hlp'], + 'wit':['wit', 'sea'], + 'sea':['sea', 'wit'], + 'hlp':['hlp', 'pro'], + 'hld':['hld', 'def'], + 'jud':['jud', 'jur'], + 'jur':['jur', 'jud'], + 'all':['hlp', 'hld', 'wit', 'jud', 'pro', 'def', 'jur', 'sea', ''], + 'pos':[]} def login(self, client): if client.area.evidence_mod == 'FFA': pass if client.area.evidence_mod == 'Mods': - if not client.is_cm: + if not client in client.area.owners: return False if client.area.evidence_mod == 'CM': - if not client.is_cm and not client.is_mod: + if not client in client.area.owners and not client.is_mod: return False if client.area.evidence_mod == 'HiddenCM': - if not client.is_cm and not client.is_mod: + if not client in client.area.owners and not client.is_mod: return False return True diff --git a/tsuserver3/server/logger.py b/AO2-Client/server/logger.py index 85c39b2..fb1b8b3 100644 --- a/tsuserver3/server/logger.py +++ b/AO2-Client/server/logger.py @@ -16,22 +16,20 @@ # along with this program. If not, see . import logging -import logging.handlers import time -def setup_logger(debug, log_size, log_backups): +def setup_logger(debug): logging.Formatter.converter = time.gmtime debug_formatter = logging.Formatter('[%(asctime)s UTC]%(message)s') srv_formatter = logging.Formatter('[%(asctime)s UTC]%(message)s') + mod_formatter = logging.Formatter('[%(asctime)s UTC]%(message)s') debug_log = logging.getLogger('debug') debug_log.setLevel(logging.DEBUG) - # 0 maxBytes = no rotation - # backupCount = number of old logs to save - debug_handler = logging.handlers.RotatingFileHandler('logs/debug.log', maxBytes = log_size, backupCount = log_backups, encoding='utf-8') + debug_handler = logging.FileHandler('logs/debug.log', encoding='utf-8') debug_handler.setLevel(logging.DEBUG) debug_handler.setFormatter(debug_formatter) debug_log.addHandler(debug_handler) @@ -42,11 +40,19 @@ def setup_logger(debug, log_size, log_backups): server_log = logging.getLogger('server') server_log.setLevel(logging.INFO) - server_handler = logging.handlers.RotatingFileHandler('logs/server.log', maxBytes = log_size, backupCount = log_backups, encoding='utf-8') + server_handler = logging.FileHandler('logs/server.log', encoding='utf-8') server_handler.setLevel(logging.INFO) server_handler.setFormatter(srv_formatter) server_log.addHandler(server_handler) + mod_log = logging.getLogger('mod') + mod_log.setLevel(logging.INFO) + + mod_handler = logging.FileHandler('logs/mod.log', encoding='utf-8') + mod_handler.setLevel(logging.INFO) + mod_handler.setFormatter(mod_formatter) + mod_log.addHandler(mod_handler) + def log_debug(msg, client=None): msg = parse_client_info(client) + msg @@ -58,10 +64,15 @@ def log_server(msg, client=None): logging.getLogger('server').info(msg) +def log_mod(msg, client=None): + msg = parse_client_info(client) + msg + logging.getLogger('mod').info(msg) + + def parse_client_info(client): if client is None: return '' info = client.get_ip() if client.is_mod: - return '[{:<15}][{}][MOD]'.format(info, client.id) - return '[{:<15}][{}]'.format(info, client.id) + return '[{:<15}][{:<3}][{}][MOD]'.format(info, client.id, client.name) + return '[{:<15}][{:<3}][{}]'.format(info, client.id, client.name) diff --git a/tsuserver3/server/tsuserver.py b/AO2-Client/server/tsuserver.py index 5e04b23..5af8161 100644 --- a/tsuserver3/server/tsuserver.py +++ b/AO2-Client/server/tsuserver.py @@ -58,7 +58,7 @@ class TsuServer3: self.district_client = None self.ms_client = None self.rp_mode = False - logger.setup_logger(debug=self.config['debug'], log_size=self.config['log_size'], log_backups=self.config['log_backups']) + logger.setup_logger(debug=self.config['debug']) def start(self): loop = asyncio.get_event_loop() @@ -118,10 +118,6 @@ class TsuServer3: self.config['music_change_floodguard'] = {'times_per_interval': 1, 'interval_length': 0, 'mute_length': 0} if 'wtce_floodguard' not in self.config: self.config['wtce_floodguard'] = {'times_per_interval': 1, 'interval_length': 0, 'mute_length': 0} - if 'log_size' not in self.config: - self.config['log_size'] = 1048576 - if 'log_backups' not in self.config: - self.config['log_backups'] = 5 def load_characters(self): with open('config/characters.yaml', 'r', encoding = 'utf-8') as chars: @@ -236,7 +232,7 @@ class TsuServer3: def broadcast_global(self, client, msg, as_mod=False): char_name = client.get_char_name() - ooc_name = '{}[{}][{}]'.format('G', client.area.id, char_name) + ooc_name = '{}[{}][{}]'.format('G', client.area.abbreviation, char_name) if as_mod: ooc_name += '[M]' self.send_all_cmd_pred('CT', ooc_name, msg, pred=lambda x: not x.muted_global) @@ -244,16 +240,58 @@ class TsuServer3: self.district_client.send_raw_message( 'GLOBAL#{}#{}#{}#{}'.format(int(as_mod), client.area.id, char_name, msg)) + def send_modchat(self, client, msg): + name = client.name + ooc_name = '{}[{}][{}]'.format('M', client.area.abbreviation, name) + self.send_all_cmd_pred('CT', ooc_name, msg, pred=lambda x: x.is_mod) + if self.config['use_district']: + self.district_client.send_raw_message( + 'MODCHAT#{}#{}#{}'.format(client.area.id, char_name, msg)) + def broadcast_need(self, client, msg): char_name = client.get_char_name() area_name = client.area.name - area_id = client.area.id + area_id = client.area.abbreviation self.send_all_cmd_pred('CT', '{}'.format(self.config['hostname']), - '=== Advert ===\r\n{} in {} [{}] needs {}\r\n===============' - .format(char_name, area_name, area_id, msg), pred=lambda x: not x.muted_adverts) + ['=== Advert ===\r\n{} in {} [{}] needs {}\r\n===============' + .format(char_name, area_name, area_id, msg), '1'], pred=lambda x: not x.muted_adverts) if self.config['use_district']: self.district_client.send_raw_message('NEED#{}#{}#{}#{}'.format(char_name, area_name, area_id, msg)) + def send_arup(self, args): + """ Updates the area properties on the Case Café Custom Client. + + Playercount: + ARUP#0###... + Status: + ARUP#1#####... + CM: + ARUP#2#####... + Lockedness: + ARUP#3#####... + + """ + if len(args) < 2: + # An argument count smaller than 2 means we only got the identifier of ARUP. + return + if args[0] not in (0,1,2,3): + return + + if args[0] == 0: + for part_arg in args[1:]: + try: + sanitised = int(part_arg) + except: + return + elif args[0] in (1, 2, 3): + for part_arg in args[1:]: + try: + sanitised = str(part_arg) + except: + return + + self.send_all_cmd_pred('ARUP', *args, pred=lambda x: True) + def refresh(self): with open('config/config.yaml', 'r') as cfg: self.config['motd'] = yaml.load(cfg)['motd'].replace('\\n', ' \n')