diff --git a/tsuserver3.patch b/tsuserver3.patch deleted file mode 100644 index 021e599..0000000 --- a/tsuserver3.patch +++ /dev/null @@ -1,2227 +0,0 @@ -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')