From 0156849cc28c2cf1f32847beef6a0b27db6b5747 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 18 Sep 2018 19:51:20 +0200 Subject: [PATCH] BEGINNINGS! of the multi-CM system. - Allows people to become CMs of multiple areas. - Allows people to appoint CMs besides themselves using `/cm id]`. - CMs can now freely leave the areas they CM in without losing CM status. - CMs that own an area, but aren't there, still show up in them when using `/getarea` with the `[RCM]` = Remote Case Manager tag. - CMs get all IC and OOC messages from their areas. - CMs can use `/s` to send a message to all the areas they own, or `/a [area_id]` to send a message only to a specific area. This is usable both IC and OOC. --- server/aoprotocol.py | 36 +++++++++++- server/area_manager.py | 37 +++++++++---- server/client_manager.py | 30 ++++++---- server/commands.py | 116 +++++++++++++++++++++++++++++---------- server/evidence.py | 6 +- 5 files changed, 172 insertions(+), 53 deletions(-) diff --git a/server/aoprotocol.py b/server/aoprotocol.py index bea9e35..bd022a4 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -335,6 +335,8 @@ class AOProtocol(asyncio.Protocol): return if not self.client.area.can_send_message(self.client): return + + target_area = [] if self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, self.ArgType.STR, @@ -399,6 +401,28 @@ class AOProtocol(asyncio.Protocol): 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): @@ -441,7 +465,7 @@ class AOProtocol(asyncio.Protocol): button = 0 # Turn off the ding. ding = 0 - if color == 2 and not (self.client.is_mod or self.client.is_cm): + 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 @@ -498,6 +522,15 @@ class AOProtocol(asyncio.Protocol): 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, 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.abbreviation, self.client.get_char_name(), msg), self.client) @@ -557,6 +590,7 @@ class AOProtocol(asyncio.Protocol): 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.abbreviation, self.client.get_char_name(), args[1]), self.client) diff --git a/server/area_manager.py b/server/area_manager.py index 68eea42..cfb2be0 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -54,7 +54,6 @@ class AreaManager: self.showname_changes_allowed = showname_changes_allowed self.shouts_allowed = shouts_allowed self.abbreviation = abbreviation - self.owned = False self.cards = dict() """ @@ -71,6 +70,8 @@ class AreaManager: self.jukebox_votes = [] self.jukebox_prev_char_id = -1 + self.owners = [] + class Locked(Enum): FREE = 1, SPECTATABLE = 2, @@ -84,12 +85,6 @@ class AreaManager: self.clients.remove(client) if len(self.clients) == 0: self.change_status('IDLE') - if client.is_cm: - client.is_cm = False - self.owned = False - self.server.area_manager.send_arup_cms() - if self.is_locked != self.Locked.FREE: - self.unlock() def unlock(self): self.is_locked = self.Locked.FREE @@ -102,6 +97,8 @@ class AreaManager: 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.') @@ -109,6 +106,8 @@ class AreaManager: 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.') @@ -124,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, '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) @@ -300,6 +305,14 @@ 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): @@ -365,6 +378,11 @@ class AreaManager: 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] @@ -382,9 +400,8 @@ class AreaManager: cms_list = [2] for area in self.areas: cm = 'FREE' - for client in area.clients: - if client.is_cm: - cm = client.get_char_name() + if len(area.owners) > 0: + cm = area.get_cms() cms_list.append(cm) self.server.send_arup(cms_list) diff --git a/server/client_manager.py b/server/client_manager.py index e7655bd..f5ef4ef 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -44,7 +44,6 @@ 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 @@ -140,7 +139,7 @@ class ClientManager: .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']: @@ -157,7 +156,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']: @@ -218,10 +217,8 @@ class ClientManager: msg = '=== Areas ===' 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 = 'CM: {}'.format(client.get_char_name()) - break + 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: @@ -244,13 +241,19 @@ class ClientManager: 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' - if c.is_cm: - info +='[CM]' + 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) @@ -266,7 +269,7 @@ 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 += '{}'.format(self.get_area_info(i, mods)) info = 'Current online: {}'.format(cnt) + info @@ -382,6 +385,13 @@ 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/server/commands.py b/server/commands.py index 57b0c3f..992d0c7 100644 --- a/server/commands.py +++ b/server/commands.py @@ -23,6 +23,34 @@ 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) def ooc_cmd_switch(client, arg): if len(arg) == 0: @@ -90,7 +118,7 @@ def ooc_cmd_allow_iniswap(client, arg): return def ooc_cmd_allow_blankposting(client, arg): - if not client.is_mod and not client.is_cm: + 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'} @@ -98,7 +126,7 @@ def ooc_cmd_allow_blankposting(client, arg): return def ooc_cmd_force_nonint_pres(client, arg): - if not client.is_mod and not client.is_cm: + 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'} @@ -179,7 +207,7 @@ def ooc_cmd_currentmusic(client, arg): client.area.current_music_player)) def ooc_cmd_jukebox_toggle(client, arg): - if not client.is_mod and not client.is_cm: + 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.') @@ -188,7 +216,7 @@ def ooc_cmd_jukebox_toggle(client, arg): 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.is_cm: + 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.') @@ -276,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() @@ -689,25 +717,55 @@ 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.server.area_manager.send_arup_cms() - client.area.send_host_message('{} is CM in this area now.'.format(client.get_char_name())) + 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.is_cm: - client.is_cm = False - client.area.owned = False - client.area.blankposting_allowed = True - if client.area.is_locked != client.area.Locked.FREE: - client.area.unlock() - client.server.area_manager.send_arup_cms() - client.area.send_host_message('{} is no longer CM in this area.'.format(client.get_char_name())) + 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 cannot give up being the CM when you are not one') + raise ClientError('You must be authorized to do that.') def ooc_cmd_unmod(client, arg): client.is_mod = False @@ -721,7 +779,7 @@ def ooc_cmd_area_lock(client, arg): return if client.area.is_locked == client.area.Locked.LOCKED: client.send_host_message('Area is already locked.') - if client.is_cm: + if client in client.area.owners: client.area.lock() return else: @@ -733,7 +791,7 @@ def ooc_cmd_area_spectate(client, arg): return if client.area.is_locked == client.area.Locked.SPECTATABLE: client.send_host_message('Area is already spectatable.') - if client.is_cm: + if client in client.area.owners: client.area.spectator() return else: @@ -742,7 +800,7 @@ def ooc_cmd_area_spectate(client, arg): def ooc_cmd_area_unlock(client, arg): 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.') @@ -750,9 +808,9 @@ 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 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.') try: c = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False)[0] @@ -763,9 +821,9 @@ def ooc_cmd_invite(client, arg): raise ClientError('You must specify a target. Use /invite ') def ooc_cmd_uninvite(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.') - 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 ') @@ -776,7 +834,7 @@ 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: + if client.area.is_locked != client.area.Locked.FREE: client.area.invite_list.pop(c.id) except AreaError: raise @@ -788,7 +846,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 #]') @@ -809,7 +867,7 @@ 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: + if client.area.is_locked != client.area.Locked.FREE: client.area.invite_list.pop(c.id) except AreaError: raise @@ -1070,7 +1128,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.') diff --git a/server/evidence.py b/server/evidence.py index efa2e25..b34172a 100644 --- a/server/evidence.py +++ b/server/evidence.py @@ -39,13 +39,13 @@ class EvidenceList: 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