Coalesce server changes into patch file (this is not a monorepo)
This commit is contained in:
		
							parent
							
								
									56ec03a23a
								
							
						
					
					
						commit
						de348c22d5
					
				@ -1,807 +0,0 @@
 | 
				
			|||||||
# tsuserver3, an Attorney Online server
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Copyright (C) 2016 argoneus <argoneuscze@gmail.com>
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
# it under the terms of the GNU General Public License as published by
 | 
					 | 
				
			||||||
# the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
# (at your option) any later version.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
# GNU General Public License for more details.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# You should have received a copy of the GNU General Public License
 | 
					 | 
				
			||||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import asyncio
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
from time import localtime, strftime
 | 
					 | 
				
			||||||
from enum import Enum
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from . import commands
 | 
					 | 
				
			||||||
from . import logger
 | 
					 | 
				
			||||||
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):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    The main class that deals with the AO protocol.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class ArgType(Enum):
 | 
					 | 
				
			||||||
        STR = 1,
 | 
					 | 
				
			||||||
        STR_OR_EMPTY = 2,
 | 
					 | 
				
			||||||
        INT = 3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, server):
 | 
					 | 
				
			||||||
        super().__init__()
 | 
					 | 
				
			||||||
        self.server = server
 | 
					 | 
				
			||||||
        self.client = None
 | 
					 | 
				
			||||||
        self.buffer = ''
 | 
					 | 
				
			||||||
        self.ping_timeout = None
 | 
					 | 
				
			||||||
        self.websocket = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def data_received(self, data):
 | 
					 | 
				
			||||||
        """ Handles any data received from the network.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Receives data, parses them into a command and passes it
 | 
					 | 
				
			||||||
        to the command handler.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :param data: bytes of data
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if self.websocket is None:
 | 
					 | 
				
			||||||
            self.websocket = WebSocket(self.client, self)
 | 
					 | 
				
			||||||
            if not self.websocket.handshake(data):
 | 
					 | 
				
			||||||
                self.websocket = False
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                self.client.websocket = self.websocket
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        buf = data
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if not self.client.is_checked and self.server.ban_manager.is_banned(self.client.ipid):
 | 
					 | 
				
			||||||
            self.client.transport.close()
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            self.client.is_checked = True
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if self.websocket:
 | 
					 | 
				
			||||||
            buf = self.websocket.handle(data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if buf is None:
 | 
					 | 
				
			||||||
            buf = b''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not isinstance(buf, str):
 | 
					 | 
				
			||||||
            # try to decode as utf-8, ignore any erroneous characters
 | 
					 | 
				
			||||||
            self.buffer += buf.decode('utf-8', 'ignore')
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            self.buffer = buf
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if len(self.buffer) > 8192:
 | 
					 | 
				
			||||||
            self.client.disconnect()
 | 
					 | 
				
			||||||
        for msg in self.get_messages():
 | 
					 | 
				
			||||||
            if len(msg) < 2:
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            # general netcode structure is not great
 | 
					 | 
				
			||||||
            if msg[0] in ('#', '3', '4'):
 | 
					 | 
				
			||||||
                if msg[0] == '#':
 | 
					 | 
				
			||||||
                    msg = msg[1:]
 | 
					 | 
				
			||||||
                spl = msg.split('#', 1)
 | 
					 | 
				
			||||||
                msg = '#'.join([fanta_decrypt(spl[0])] + spl[1:])
 | 
					 | 
				
			||||||
                logger.log_debug('[INC][RAW]{}'.format(msg), self.client)
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                cmd, *args = msg.split('#')
 | 
					 | 
				
			||||||
                self.net_cmd_dispatcher[cmd](self, args)
 | 
					 | 
				
			||||||
            except KeyError:
 | 
					 | 
				
			||||||
                logger.log_debug('[INC][UNK]{}'.format(msg), self.client)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def connection_made(self, transport):
 | 
					 | 
				
			||||||
        """ Called upon a new client connecting
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :param transport: the transport object
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.client = self.server.new_client(transport)
 | 
					 | 
				
			||||||
        self.ping_timeout = asyncio.get_event_loop().call_later(self.server.config['timeout'], self.client.disconnect)
 | 
					 | 
				
			||||||
        asyncio.get_event_loop().call_later(0.25, self.client.send_command, 'decryptor', 34) # just fantacrypt things)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def connection_lost(self, exc):
 | 
					 | 
				
			||||||
        """ User disconnected
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :param exc: reason
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.server.remove_client(self.client)
 | 
					 | 
				
			||||||
        self.ping_timeout.cancel()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_messages(self):
 | 
					 | 
				
			||||||
        """ Parses out full messages from the buffer.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :return: yields messages
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        while '#%' in self.buffer:
 | 
					 | 
				
			||||||
            spl = self.buffer.split('#%', 1)
 | 
					 | 
				
			||||||
            self.buffer = spl[1]
 | 
					 | 
				
			||||||
            yield spl[0]
 | 
					 | 
				
			||||||
        # exception because bad netcode
 | 
					 | 
				
			||||||
        askchar2 = '#615810BC07D12A5A#'
 | 
					 | 
				
			||||||
        if self.buffer == askchar2:
 | 
					 | 
				
			||||||
            self.buffer = ''
 | 
					 | 
				
			||||||
            yield askchar2
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def validate_net_cmd(self, args, *types, needs_auth=True):
 | 
					 | 
				
			||||||
        """ Makes sure the net command's arguments match expectations.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :param args: actual arguments to the net command
 | 
					 | 
				
			||||||
        :param types: what kind of data types are expected
 | 
					 | 
				
			||||||
        :param needs_auth: whether you need to have chosen a character
 | 
					 | 
				
			||||||
        :return: returns True if message was validated
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        if needs_auth and self.client.char_id == -1:
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
        if len(args) != len(types):
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
        for i, arg in enumerate(args):
 | 
					 | 
				
			||||||
            if len(arg) == 0 and types[i] != self.ArgType.STR_OR_EMPTY:
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
            if types[i] == self.ArgType.INT:
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    args[i] = int(arg)
 | 
					 | 
				
			||||||
                except ValueError:
 | 
					 | 
				
			||||||
                    return False
 | 
					 | 
				
			||||||
        return True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_hi(self, args):
 | 
					 | 
				
			||||||
        """ Handshake.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        HI#<hdid:string>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :param args: a list containing all the arguments
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        if not self.validate_net_cmd(args, self.ArgType.STR, needs_auth=False):
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        self.client.hdid = args[0]
 | 
					 | 
				
			||||||
        if self.client.hdid not in self.client.server.hdid_list:
 | 
					 | 
				
			||||||
            self.client.server.hdid_list[self.client.hdid] = []
 | 
					 | 
				
			||||||
        if self.client.ipid not in self.client.server.hdid_list[self.client.hdid]:
 | 
					 | 
				
			||||||
            self.client.server.hdid_list[self.client.hdid].append(self.client.ipid)
 | 
					 | 
				
			||||||
            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)
 | 
					 | 
				
			||||||
        self.client.send_command('ID', self.client.id, self.server.software, self.server.get_version_string())
 | 
					 | 
				
			||||||
        self.client.send_command('PN', self.server.get_player_count() - 1, self.server.config['playerlimit'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_id(self, args):
 | 
					 | 
				
			||||||
        """ Client version and PV
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ID#<pv:int>#<software:string>#<version:string>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.client.is_ao2 = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if len(args) < 2:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        version_list = args[1].split('.')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if len(version_list) < 3:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        release = int(version_list[0])
 | 
					 | 
				
			||||||
        major = int(version_list[1])
 | 
					 | 
				
			||||||
        minor = int(version_list[2])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if args[0] != 'AO2':
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        if release < 2:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        elif release == 2:
 | 
					 | 
				
			||||||
            if major < 2:
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
            elif major == 2:
 | 
					 | 
				
			||||||
                if minor < 5:
 | 
					 | 
				
			||||||
                    return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.client.is_ao2 = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        CHECK#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.client.send_command('CHECK')
 | 
					 | 
				
			||||||
        self.ping_timeout.cancel()
 | 
					 | 
				
			||||||
        self.ping_timeout = asyncio.get_event_loop().call_later(self.server.config['timeout'], self.client.disconnect)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_askchaa(self, _):
 | 
					 | 
				
			||||||
        """ Ask for the counts of characters/evidence/music
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        askchaa#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        char_cnt = len(self.server.char_list)
 | 
					 | 
				
			||||||
        evi_cnt = 0
 | 
					 | 
				
			||||||
        music_cnt = sum([len(x) for x in self.server.music_pages_ao1])
 | 
					 | 
				
			||||||
        self.client.send_command('SI', char_cnt, evi_cnt, music_cnt)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_askchar2(self, _):
 | 
					 | 
				
			||||||
        """ Asks for the character list.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        askchar2#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.client.send_command('CI', *self.server.char_pages_ao1[0])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_an(self, args):
 | 
					 | 
				
			||||||
        """ Asks for specific pages of the character list.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        AN#<page:int>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        if not self.validate_net_cmd(args, self.ArgType.INT, needs_auth=False):
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        if len(self.server.char_pages_ao1) > args[0] >= 0:
 | 
					 | 
				
			||||||
            self.client.send_command('CI', *self.server.char_pages_ao1[args[0]])
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            self.client.send_command('EM', *self.server.music_pages_ao1[0])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_ae(self, _):
 | 
					 | 
				
			||||||
        """ Asks for specific pages of the evidence list.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        AE#<page:int>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        pass  # todo evidence maybe later
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_am(self, args):
 | 
					 | 
				
			||||||
        """ Asks for specific pages of the music list.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        AM#<page:int>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        if not self.validate_net_cmd(args, self.ArgType.INT, needs_auth=False):
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        if len(self.server.music_pages_ao1) > args[0] >= 0:
 | 
					 | 
				
			||||||
            self.client.send_command('EM', *self.server.music_pages_ao1[args[0]])
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            self.client.send_done()
 | 
					 | 
				
			||||||
            self.client.send_area_list()
 | 
					 | 
				
			||||||
            self.client.send_motd()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_rc(self, _):
 | 
					 | 
				
			||||||
        """ Asks for the whole character list(AO2)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        AC#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.client.send_command('SC', *self.server.char_list)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_rm(self, _):
 | 
					 | 
				
			||||||
        """ Asks for the whole music list(AO2)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        AM#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.client.send_command('SM', *self.server.music_list_ao2)
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_rd(self, _):
 | 
					 | 
				
			||||||
        """ Asks for server metadata(charscheck, motd etc.) and a DONE#% signal(also best packet)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        RD#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.client.send_done()
 | 
					 | 
				
			||||||
        self.client.send_area_list()
 | 
					 | 
				
			||||||
        self.client.send_motd()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_cc(self, args):
 | 
					 | 
				
			||||||
        """ Character selection.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        CC#<client_id:int>#<char_id:int>#<hdid:string>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        if not self.validate_net_cmd(args, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR, needs_auth=False):
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        cid = args[1]
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            self.client.change_character(cid)
 | 
					 | 
				
			||||||
        except ClientError:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_ms(self, args):
 | 
					 | 
				
			||||||
        """ IC message.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Refer to the implementation for details.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        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.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,
 | 
					 | 
				
			||||||
                                     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
 | 
					 | 
				
			||||||
        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):
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        if cid != self.client.char_id:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        if sfx_delay < 0:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        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, 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
 | 
					 | 
				
			||||||
            if len(text.strip( ' ' )) == 1:
 | 
					 | 
				
			||||||
                color = 0
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                if text.strip( ' ' ) in ('<num>', '<percent>', '<dollar>', '<and>'):
 | 
					 | 
				
			||||||
                    color = 0
 | 
					 | 
				
			||||||
        if self.client.pos:
 | 
					 | 
				
			||||||
            pos = self.client.pos
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            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
 | 
					 | 
				
			||||||
        if evidence:
 | 
					 | 
				
			||||||
            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, 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)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (self.client.area.is_recording):
 | 
					 | 
				
			||||||
            self.client.area.recorded_messages.append(args)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_ct(self, args):
 | 
					 | 
				
			||||||
        """ OOC Message
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        CT#<name:string>#<message:string>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        if self.client.is_ooc_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.validate_net_cmd(args, self.ArgType.STR, self.ArgType.STR):
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        if self.client.name != args[0] and self.client.fake_name != args[0]:
 | 
					 | 
				
			||||||
            if self.client.is_valid_name(args[0]):
 | 
					 | 
				
			||||||
                self.client.name = args[0]
 | 
					 | 
				
			||||||
                self.client.fake_name = args[0]
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                self.client.fake_name = args[0]
 | 
					 | 
				
			||||||
        if self.client.name == '':
 | 
					 | 
				
			||||||
            self.client.send_host_message('You must insert a name with at least one letter')
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        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('<dollar>G') or self.client.name.startswith('<dollar>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].lower()
 | 
					 | 
				
			||||||
            arg = ''
 | 
					 | 
				
			||||||
            if len(spl) == 2:
 | 
					 | 
				
			||||||
                arg = spl[1][:256]
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                called_function = 'ooc_cmd_{}'.format(cmd)
 | 
					 | 
				
			||||||
                getattr(commands, called_function)(self.client, arg)
 | 
					 | 
				
			||||||
            except AttributeError:
 | 
					 | 
				
			||||||
                print('Attribute error with ' + called_function)
 | 
					 | 
				
			||||||
                self.client.send_host_message('Invalid command.')
 | 
					 | 
				
			||||||
            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.abbreviation, self.client.get_char_name(),
 | 
					 | 
				
			||||||
                                             args[1]), self.client)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_mc(self, args):
 | 
					 | 
				
			||||||
        """ Play music.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        MC#<song_name:int>#<???:int>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            area = self.server.area_manager.get_area_by_name(args[0])
 | 
					 | 
				
			||||||
            self.client.change_area(area)
 | 
					 | 
				
			||||||
        except AreaError:
 | 
					 | 
				
			||||||
            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.is_dj:
 | 
					 | 
				
			||||||
                self.client.send_host_message('You were blockdj\'d 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 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
 | 
					 | 
				
			||||||
            if self.client.change_music_cd():
 | 
					 | 
				
			||||||
                self.client.send_host_message('You changed song too many times. Please try again after {} seconds.'.format(int(self.client.change_music_cd())))
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                name, length = self.server.get_song_data(args[0])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                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:
 | 
					 | 
				
			||||||
            self.client.send_host_message(ex)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_rt(self, args):
 | 
					 | 
				
			||||||
        """ Plays the Testimony/CE animation.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        RT#<type:string>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        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 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
 | 
					 | 
				
			||||||
        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.abbreviation, self.client.get_char_name()), self.client)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_hp(self, args):
 | 
					 | 
				
			||||||
        """ Sets the penalty bar.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        HP#<type:int>#<new_value:int>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        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.abbreviation, self.client.get_char_name(), args[0], args[1]), self.client)
 | 
					 | 
				
			||||||
        except AreaError:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_pe(self, args):
 | 
					 | 
				
			||||||
        """ Adds a piece of evidence.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        PE#<name: string>#<description: string>#<image: string>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        if len(args) < 3:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        #evi = Evidence(args[0], args[1], args[2], self.client.pos)
 | 
					 | 
				
			||||||
        self.client.area.evi_list.add_evidence(self.client, args[0], args[1], args[2], 'all')
 | 
					 | 
				
			||||||
        self.client.area.broadcast_evidence_list()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def net_cmd_de(self, args):
 | 
					 | 
				
			||||||
        """ Deletes a piece of evidence.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        DE#<id: int>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.client.area.evi_list.del_evidence(self.client, self.client.evi_list[int(args[0])])
 | 
					 | 
				
			||||||
        self.client.area.broadcast_evidence_list()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_ee(self, args):
 | 
					 | 
				
			||||||
        """ Edits a piece of evidence.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        EE#<id: int>#<name: string>#<description: string>#<image: string>#%
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if len(args) < 4:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        evi = (args[1], args[2], args[3], 'all')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.client.area.evi_list.edit_evidence(self.client, self.client.evi_list[int(args[0])], evi)
 | 
					 | 
				
			||||||
        self.client.area.broadcast_evidence_list()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_zz(self, args):
 | 
					 | 
				
			||||||
        """ Sent on mod call.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        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_call_mod():
 | 
					 | 
				
			||||||
            self.client.send_host_message("You must wait 30 seconds between mod calls.")
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        current_time = strftime("%H:%M", localtime())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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])])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def net_cmd_opBAN(self, args):
 | 
					 | 
				
			||||||
        self.net_cmd_ct(['opban', '/ban {}'.format(args[0])])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    net_cmd_dispatcher = {
 | 
					 | 
				
			||||||
        'HI': net_cmd_hi,  # handshake
 | 
					 | 
				
			||||||
        'ID': net_cmd_id,  # client version
 | 
					 | 
				
			||||||
        'CH': net_cmd_ch,  # keepalive
 | 
					 | 
				
			||||||
        'askchaa': net_cmd_askchaa,  # ask for list lengths
 | 
					 | 
				
			||||||
        'askchar2': net_cmd_askchar2,  # ask for list of characters
 | 
					 | 
				
			||||||
        'AN': net_cmd_an,  # character list
 | 
					 | 
				
			||||||
        'AE': net_cmd_ae,  # evidence list
 | 
					 | 
				
			||||||
        'AM': net_cmd_am,  # music list
 | 
					 | 
				
			||||||
        'RC': net_cmd_rc,  # AO2 character list
 | 
					 | 
				
			||||||
        'RM': net_cmd_rm,  # AO2 music list
 | 
					 | 
				
			||||||
        'RD': net_cmd_rd,  # AO2 done request, charscheck etc.
 | 
					 | 
				
			||||||
        'CC': net_cmd_cc,  # select character
 | 
					 | 
				
			||||||
        'MS': net_cmd_ms,  # IC message
 | 
					 | 
				
			||||||
        'CT': net_cmd_ct,  # OOC message
 | 
					 | 
				
			||||||
        'MC': net_cmd_mc,  # play song
 | 
					 | 
				
			||||||
        'RT': net_cmd_rt,  # WT/CE buttons
 | 
					 | 
				
			||||||
        'HP': net_cmd_hp,  # penalties
 | 
					 | 
				
			||||||
        'PE': net_cmd_pe,  # add evidence
 | 
					 | 
				
			||||||
        'DE': net_cmd_de,  # delete evidence
 | 
					 | 
				
			||||||
        'EE': net_cmd_ee,  # edit evidence
 | 
					 | 
				
			||||||
        'ZZ': net_cmd_zz,  # call mod button
 | 
					 | 
				
			||||||
        'opKICK': net_cmd_opKICK,  # /kick with guard on
 | 
					 | 
				
			||||||
        'opBAN': net_cmd_opBAN,  # /ban with guard on
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
@ -1,412 +0,0 @@
 | 
				
			|||||||
# tsuserver3, an Attorney Online server
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Copyright (C) 2016 argoneus <argoneuscze@gmail.com>
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
# it under the terms of the GNU General Public License as published by
 | 
					 | 
				
			||||||
# the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
# (at your option) any later version.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
# GNU General Public License for more details.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# You should have received a copy of the GNU General Public License
 | 
					 | 
				
			||||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
import asyncio
 | 
					 | 
				
			||||||
import random
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
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, 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 = {}
 | 
					 | 
				
			||||||
            self.id = area_id
 | 
					 | 
				
			||||||
            self.name = name
 | 
					 | 
				
			||||||
            self.background = background
 | 
					 | 
				
			||||||
            self.bg_lock = bg_lock
 | 
					 | 
				
			||||||
            self.server = server
 | 
					 | 
				
			||||||
            self.music_looper = None
 | 
					 | 
				
			||||||
            self.next_message_time = 0
 | 
					 | 
				
			||||||
            self.hp_def = 10
 | 
					 | 
				
			||||||
            self.hp_pro = 10
 | 
					 | 
				
			||||||
            self.doc = 'No document.'
 | 
					 | 
				
			||||||
            self.status = 'IDLE'
 | 
					 | 
				
			||||||
            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.showname_changes_allowed = showname_changes_allowed
 | 
					 | 
				
			||||||
            self.shouts_allowed = shouts_allowed
 | 
					 | 
				
			||||||
            self.abbreviation = abbreviation
 | 
					 | 
				
			||||||
            self.cards = dict()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            """
 | 
					 | 
				
			||||||
            #debug
 | 
					 | 
				
			||||||
            self.evidence_list.append(Evidence("WOW", "desc", "1.png"))
 | 
					 | 
				
			||||||
            self.evidence_list.append(Evidence("wewz", "desc2", "2.png"))
 | 
					 | 
				
			||||||
            self.evidence_list.append(Evidence("weeeeeew", "desc3", "3.png"))
 | 
					 | 
				
			||||||
            """
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            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 len(self.clients) == 0:
 | 
					 | 
				
			||||||
                self.change_status('IDLE')
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        def unlock(self):
 | 
					 | 
				
			||||||
            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]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def get_rand_avail_char_id(self):
 | 
					 | 
				
			||||||
            avail_set = set(range(len(self.server.char_list))) - set([x.char_id for x in self.clients])
 | 
					 | 
				
			||||||
            if len(avail_set) == 0:
 | 
					 | 
				
			||||||
                raise AreaError('No available characters.')
 | 
					 | 
				
			||||||
            return random.choice(tuple(avail_set))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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)
 | 
					 | 
				
			||||||
            self.next_message_time = round(time.time() * 1000.0 + delay)
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        def is_iniswap(self, client, anim1, anim2, char):
 | 
					 | 
				
			||||||
            if self.iniswap_allowed:
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
            if '..' in anim1 or '..' in anim2:
 | 
					 | 
				
			||||||
                return True
 | 
					 | 
				
			||||||
            for char_link in self.server.allowed_iniswaps:
 | 
					 | 
				
			||||||
                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)
 | 
					 | 
				
			||||||
            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 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.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.')
 | 
					 | 
				
			||||||
            if not 1 <= side <= 2:
 | 
					 | 
				
			||||||
                raise AreaError('Invalid penalty side.')
 | 
					 | 
				
			||||||
            if side == 1:
 | 
					 | 
				
			||||||
                self.hp_def = val
 | 
					 | 
				
			||||||
            elif side == 2:
 | 
					 | 
				
			||||||
                self.hp_pro = val
 | 
					 | 
				
			||||||
            self.send_command('HP', side, val)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def change_background(self, bg):
 | 
					 | 
				
			||||||
            if bg.lower() not in (name.lower() for name in self.server.backgrounds):
 | 
					 | 
				
			||||||
                raise AreaError('Invalid background name.')
 | 
					 | 
				
			||||||
            self.background = bg
 | 
					 | 
				
			||||||
            self.send_command('BN', self.background)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def change_status(self, value):
 | 
					 | 
				
			||||||
            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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def add_to_judgelog(self, client, msg):
 | 
					 | 
				
			||||||
            if len(self.judgelog) >= 10:
 | 
					 | 
				
			||||||
                self.judgelog = self.judgelog[1:]
 | 
					 | 
				
			||||||
            self.judgelog.append('{} ({}) {}.'.format(client.get_char_name(), client.get_ip(), msg))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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):
 | 
					 | 
				
			||||||
            client.evi_list, evi_list = self.evi_list.create_evi_list(client)
 | 
					 | 
				
			||||||
            return evi_list
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def broadcast_evidence_list(self):
 | 
					 | 
				
			||||||
            """
 | 
					 | 
				
			||||||
                LE#<name>&<desc>&<img>#<name>
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
            """
 | 
					 | 
				
			||||||
            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
 | 
					 | 
				
			||||||
        self.cur_id = 0
 | 
					 | 
				
			||||||
        self.areas = []
 | 
					 | 
				
			||||||
        self.load_areas()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def load_areas(self):
 | 
					 | 
				
			||||||
        with open('config/areas.yaml', 'r') as chars:
 | 
					 | 
				
			||||||
            areas = yaml.load(chars)
 | 
					 | 
				
			||||||
        for item in areas:
 | 
					 | 
				
			||||||
            if 'evidence_mod' not in item:
 | 
					 | 
				
			||||||
                item['evidence_mod'] = 'FFA'
 | 
					 | 
				
			||||||
            if 'locking_allowed' not in item:
 | 
					 | 
				
			||||||
                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'], item['showname_changes_allowed'], item['shouts_allowed'], item['jukebox'], item['abbreviation'], item['noninterrupting_pres']))
 | 
					 | 
				
			||||||
            self.cur_id += 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def default_area(self):
 | 
					 | 
				
			||||||
        return self.areas[0]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_area_by_name(self, name):
 | 
					 | 
				
			||||||
        for area in self.areas:
 | 
					 | 
				
			||||||
            if area.name == name:
 | 
					 | 
				
			||||||
                return area
 | 
					 | 
				
			||||||
        raise AreaError('Area not found.')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_area_by_id(self, num):
 | 
					 | 
				
			||||||
        for area in self.areas:
 | 
					 | 
				
			||||||
            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)
 | 
					 | 
				
			||||||
@ -1,54 +0,0 @@
 | 
				
			|||||||
# tsuserver3, an Attorney Online server
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Copyright (C) 2016 argoneus <argoneuscze@gmail.com>
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
# it under the terms of the GNU General Public License as published by
 | 
					 | 
				
			||||||
# the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
# (at your option) any later version.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
# GNU General Public License for more details.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# You should have received a copy of the GNU General Public License
 | 
					 | 
				
			||||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from server.exceptions import ServerError
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class BanManager:
 | 
					 | 
				
			||||||
    def __init__(self):
 | 
					 | 
				
			||||||
        self.bans = []
 | 
					 | 
				
			||||||
        self.load_banlist()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def load_banlist(self):
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            with open('storage/banlist.json', 'r') as banlist_file:
 | 
					 | 
				
			||||||
                self.bans = json.load(banlist_file)
 | 
					 | 
				
			||||||
        except FileNotFoundError:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def write_banlist(self):
 | 
					 | 
				
			||||||
        with open('storage/banlist.json', 'w') as banlist_file:
 | 
					 | 
				
			||||||
            json.dump(self.bans, banlist_file)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def add_ban(self, ip):
 | 
					 | 
				
			||||||
        if ip not in self.bans:
 | 
					 | 
				
			||||||
            self.bans.append(ip)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            raise ServerError('This IPID is already banned.')
 | 
					 | 
				
			||||||
        self.write_banlist()
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    def remove_ban(self, ip):
 | 
					 | 
				
			||||||
        if ip in self.bans:
 | 
					 | 
				
			||||||
            self.bans.remove(ip)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            raise ServerError('This IPID is not banned.')
 | 
					 | 
				
			||||||
        self.write_banlist()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def is_banned(self, ipid):
 | 
					 | 
				
			||||||
        return (ipid in self.bans)
 | 
					 | 
				
			||||||
@ -1,457 +0,0 @@
 | 
				
			|||||||
# tsuserver3, an Attorney Online server
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Copyright (C) 2016 argoneus <argoneuscze@gmail.com>
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
# it under the terms of the GNU General Public License as published by
 | 
					 | 
				
			||||||
# the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
# (at your option) any later version.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
# GNU General Public License for more details.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# You should have received a copy of the GNU General Public License
 | 
					 | 
				
			||||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from server import fantacrypt
 | 
					 | 
				
			||||||
from server import logger
 | 
					 | 
				
			||||||
from server.exceptions import ClientError, AreaError
 | 
					 | 
				
			||||||
from enum import Enum
 | 
					 | 
				
			||||||
from server.constants import TargetType
 | 
					 | 
				
			||||||
from heapq import heappop, heappush
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ClientManager:
 | 
					 | 
				
			||||||
    class Client:
 | 
					 | 
				
			||||||
        def __init__(self, server, transport, user_id, ipid):
 | 
					 | 
				
			||||||
            self.is_checked = False
 | 
					 | 
				
			||||||
            self.transport = transport
 | 
					 | 
				
			||||||
            self.hdid = ''
 | 
					 | 
				
			||||||
            self.pm_mute = False
 | 
					 | 
				
			||||||
            self.id = user_id
 | 
					 | 
				
			||||||
            self.char_id = -1
 | 
					 | 
				
			||||||
            self.area = server.area_manager.default_area()
 | 
					 | 
				
			||||||
            self.server = server
 | 
					 | 
				
			||||||
            self.name = ''
 | 
					 | 
				
			||||||
            self.fake_name = ''
 | 
					 | 
				
			||||||
            self.is_mod = False
 | 
					 | 
				
			||||||
            self.is_dj = True
 | 
					 | 
				
			||||||
            self.can_wtce = True
 | 
					 | 
				
			||||||
            self.pos = ''
 | 
					 | 
				
			||||||
            self.evi_list = []
 | 
					 | 
				
			||||||
            self.disemvowel = False
 | 
					 | 
				
			||||||
            self.shaken = False
 | 
					 | 
				
			||||||
            self.charcurse = []
 | 
					 | 
				
			||||||
            self.muted_global = False
 | 
					 | 
				
			||||||
            self.muted_adverts = False
 | 
					 | 
				
			||||||
            self.is_muted = False
 | 
					 | 
				
			||||||
            self.is_ooc_muted = False
 | 
					 | 
				
			||||||
            self.pm_mute = False
 | 
					 | 
				
			||||||
            self.mod_call_time = 0
 | 
					 | 
				
			||||||
            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
 | 
					 | 
				
			||||||
            self.mus_change_time = [x * self.server.config['music_change_floodguard']['interval_length'] for x in range(self.server.config['music_change_floodguard']['times_per_interval'])]
 | 
					 | 
				
			||||||
            self.wtce_counter = 0
 | 
					 | 
				
			||||||
            self.wtce_mute_time = 0
 | 
					 | 
				
			||||||
            self.wtce_time = [x * self.server.config['wtce_floodguard']['interval_length'] for x in range(self.server.config['wtce_floodguard']['times_per_interval'])]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def send_raw_message(self, msg):
 | 
					 | 
				
			||||||
            if self.websocket:
 | 
					 | 
				
			||||||
                self.websocket.send_text(msg.encode('utf-8'))
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                self.transport.write(msg.encode('utf-8'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def send_command(self, command, *args):
 | 
					 | 
				
			||||||
            if args:
 | 
					 | 
				
			||||||
                if command == 'MS':
 | 
					 | 
				
			||||||
                    for evi_num in range(len(self.evi_list)):
 | 
					 | 
				
			||||||
                        if self.evi_list[evi_num] == args[11]:
 | 
					 | 
				
			||||||
                            lst = list(args)
 | 
					 | 
				
			||||||
                            lst[11] = evi_num
 | 
					 | 
				
			||||||
                            args = tuple(lst)
 | 
					 | 
				
			||||||
                            break
 | 
					 | 
				
			||||||
                self.send_raw_message('{}#{}#%'.format(command, '#'.join([str(x) for x in args])))
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                self.send_raw_message('{}#%'.format(command))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def send_host_message(self, 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']))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def send_player_count(self):
 | 
					 | 
				
			||||||
            self.send_host_message('{}/{} players online.'.format(
 | 
					 | 
				
			||||||
                self.server.get_player_count(),
 | 
					 | 
				
			||||||
                self.server.config['playerlimit']))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def is_valid_name(self, name):
 | 
					 | 
				
			||||||
            name_ws = name.replace(' ', '')
 | 
					 | 
				
			||||||
            if not name_ws or name_ws.isdigit():
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
            for client in self.server.client_manager.clients:
 | 
					 | 
				
			||||||
                print(client.name == name)
 | 
					 | 
				
			||||||
                if client.name == name:
 | 
					 | 
				
			||||||
                    return False
 | 
					 | 
				
			||||||
            return True
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
        def disconnect(self):
 | 
					 | 
				
			||||||
            self.transport.close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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:
 | 
					 | 
				
			||||||
                        if client.char_id == char_id:
 | 
					 | 
				
			||||||
                            client.char_select()
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    raise ClientError('Character not available.')
 | 
					 | 
				
			||||||
            old_char = self.get_char_name()
 | 
					 | 
				
			||||||
            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.abbreviation, old_char, self.get_char_name()), self)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def change_music_cd(self):
 | 
					 | 
				
			||||||
            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']:
 | 
					 | 
				
			||||||
                    return self.server.config['music_change_floodguard']['mute_length'] - (time.time() - self.mus_mute_time)
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    self.mus_mute_time = 0
 | 
					 | 
				
			||||||
            times_per_interval = self.server.config['music_change_floodguard']['times_per_interval']
 | 
					 | 
				
			||||||
            interval_length = self.server.config['music_change_floodguard']['interval_length']
 | 
					 | 
				
			||||||
            if time.time() - self.mus_change_time[(self.mus_counter - times_per_interval + 1) % times_per_interval] < interval_length:
 | 
					 | 
				
			||||||
                self.mus_mute_time = time.time()
 | 
					 | 
				
			||||||
                return self.server.config['music_change_floodguard']['mute_length']
 | 
					 | 
				
			||||||
            self.mus_counter = (self.mus_counter + 1) % times_per_interval
 | 
					 | 
				
			||||||
            self.mus_change_time[self.mus_counter] = time.time()
 | 
					 | 
				
			||||||
            return 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def wtce_mute(self):
 | 
					 | 
				
			||||||
            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']:
 | 
					 | 
				
			||||||
                    return self.server.config['wtce_floodguard']['mute_length'] - (time.time() - self.wtce_mute_time)
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    self.wtce_mute_time = 0
 | 
					 | 
				
			||||||
            times_per_interval = self.server.config['wtce_floodguard']['times_per_interval']
 | 
					 | 
				
			||||||
            interval_length = self.server.config['wtce_floodguard']['interval_length']
 | 
					 | 
				
			||||||
            if time.time() - self.wtce_time[(self.wtce_counter - times_per_interval + 1) % times_per_interval] < interval_length:
 | 
					 | 
				
			||||||
                self.wtce_mute_time = time.time()
 | 
					 | 
				
			||||||
                return self.server.config['music_change_floodguard']['mute_length']
 | 
					 | 
				
			||||||
            self.wtce_counter = (self.wtce_counter + 1) % times_per_interval
 | 
					 | 
				
			||||||
            self.wtce_time[self.wtce_counter] = time.time()
 | 
					 | 
				
			||||||
            return 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def reload_character(self):
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                self.change_character(self.char_id, True)
 | 
					 | 
				
			||||||
            except ClientError:
 | 
					 | 
				
			||||||
                raise
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def change_area(self, area):
 | 
					 | 
				
			||||||
            if self.area == area:
 | 
					 | 
				
			||||||
                raise ClientError('User already in specified area.')
 | 
					 | 
				
			||||||
            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:
 | 
					 | 
				
			||||||
                    new_char_id = area.get_rand_avail_char_id()
 | 
					 | 
				
			||||||
                except AreaError:
 | 
					 | 
				
			||||||
                    raise ClientError('No available characters in that area.')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                self.change_character(new_char_id)
 | 
					 | 
				
			||||||
                self.send_host_message('Character taken, switched to {}.'.format(self.get_char_name()))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            self.area.remove_client(self)
 | 
					 | 
				
			||||||
            self.area = area
 | 
					 | 
				
			||||||
            area.new_client(self)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            self.send_host_message('Changed area to {}.[{}]'.format(area.name, self.area.status))
 | 
					 | 
				
			||||||
            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)
 | 
					 | 
				
			||||||
            self.send_command('LE', *self.area.get_evidence_list(self))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def send_area_list(self):
 | 
					 | 
				
			||||||
            msg = '=== Areas ==='
 | 
					 | 
				
			||||||
            for i, area in enumerate(self.server.area_manager.areas):
 | 
					 | 
				
			||||||
                owner = 'FREE'
 | 
					 | 
				
			||||||
                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 = '\r\n'
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                area = self.server.area_manager.get_area_by_id(area_id)
 | 
					 | 
				
			||||||
            except AreaError:
 | 
					 | 
				
			||||||
                raise
 | 
					 | 
				
			||||||
            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'
 | 
					 | 
				
			||||||
                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): 
 | 
					 | 
				
			||||||
            #if area_id is -1 then return all areas. If mods is True then return only mods
 | 
					 | 
				
			||||||
            info = ''
 | 
					 | 
				
			||||||
            if area_id == -1:
 | 
					 | 
				
			||||||
                # all areas info
 | 
					 | 
				
			||||||
                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 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
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    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)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def send_area_hdid(self, area_id):
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                info = self.get_area_hdid(area_id)
 | 
					 | 
				
			||||||
            except AreaError:
 | 
					 | 
				
			||||||
                raise
 | 
					 | 
				
			||||||
            self.send_host_message(info)				
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def send_all_area_hdid(self):
 | 
					 | 
				
			||||||
            info = '== HDID List =='
 | 
					 | 
				
			||||||
            for i in range (len(self.server.area_manager.areas)):
 | 
					 | 
				
			||||||
                 if len(self.server.area_manager.areas[i].clients) > 0:
 | 
					 | 
				
			||||||
                    info += '\r\n{}'.format(self.get_area_hdid(i))
 | 
					 | 
				
			||||||
            self.send_host_message(info)			
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def send_all_area_ip(self):
 | 
					 | 
				
			||||||
            info = '== IP List =='
 | 
					 | 
				
			||||||
            for i in range (len(self.server.area_manager.areas)):
 | 
					 | 
				
			||||||
                 if len(self.server.area_manager.areas[i].clients) > 0:
 | 
					 | 
				
			||||||
                    info += '\r\n{}'.format(self.get_area_ip(i))
 | 
					 | 
				
			||||||
            self.send_host_message(info)
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
        def send_done(self):
 | 
					 | 
				
			||||||
            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.')
 | 
					 | 
				
			||||||
            if password == self.server.config['modpass']:
 | 
					 | 
				
			||||||
                self.is_mod = True
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                raise ClientError('Invalid password.')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def get_ip(self):
 | 
					 | 
				
			||||||
            return self.ipid
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
        def get_char_name(self):
 | 
					 | 
				
			||||||
            if self.char_id == -1:
 | 
					 | 
				
			||||||
                return 'CHAR_SELECT'
 | 
					 | 
				
			||||||
            return self.server.char_list[self.char_id]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def change_position(self, pos=''):
 | 
					 | 
				
			||||||
            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):
 | 
					 | 
				
			||||||
            self.mod_call_time = round(time.time() * 1000.0 + 30000)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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()
 | 
					 | 
				
			||||||
        self.server = server
 | 
					 | 
				
			||||||
        self.cur_id = [i for i in range(self.server.config['playerlimit'])]
 | 
					 | 
				
			||||||
        self.clients_list = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def new_client(self, transport):
 | 
					 | 
				
			||||||
        c = self.Client(self.server, transport, heappop(self.cur_id), self.server.get_ipid(transport.get_extra_info('peername')[0]))
 | 
					 | 
				
			||||||
        self.clients.add(c)
 | 
					 | 
				
			||||||
        return c
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
    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)
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
    def get_targets(self, client, key, value, local = False):
 | 
					 | 
				
			||||||
        #possible keys: ip, OOC, id, cname, ipid, hdid
 | 
					 | 
				
			||||||
        areas = None
 | 
					 | 
				
			||||||
        if local:
 | 
					 | 
				
			||||||
            areas = [client.area]
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            areas = client.server.area_manager.areas
 | 
					 | 
				
			||||||
        targets = []
 | 
					 | 
				
			||||||
        if key == TargetType.ALL:
 | 
					 | 
				
			||||||
            for nkey in range(6):
 | 
					 | 
				
			||||||
                targets += self.get_targets(client, nkey, value, local)
 | 
					 | 
				
			||||||
        for area in areas:
 | 
					 | 
				
			||||||
            for client in area.clients:
 | 
					 | 
				
			||||||
                if key == TargetType.IP:
 | 
					 | 
				
			||||||
                    if value.lower().startswith(client.get_ip().lower()):
 | 
					 | 
				
			||||||
                        targets.append(client)
 | 
					 | 
				
			||||||
                elif key == TargetType.OOC_NAME:
 | 
					 | 
				
			||||||
                    if value.lower().startswith(client.name.lower()) and client.name:
 | 
					 | 
				
			||||||
                        targets.append(client)
 | 
					 | 
				
			||||||
                elif key == TargetType.CHAR_NAME:
 | 
					 | 
				
			||||||
                    if value.lower().startswith(client.get_char_name().lower()):
 | 
					 | 
				
			||||||
                        targets.append(client)
 | 
					 | 
				
			||||||
                elif key == TargetType.ID:
 | 
					 | 
				
			||||||
                    if client.id == value:
 | 
					 | 
				
			||||||
                        targets.append(client)
 | 
					 | 
				
			||||||
                elif key == TargetType.IPID:
 | 
					 | 
				
			||||||
                    if client.ipid == value:
 | 
					 | 
				
			||||||
                        targets.append(client)
 | 
					 | 
				
			||||||
        return targets
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    def get_muted_clients(self):
 | 
					 | 
				
			||||||
        clients = []
 | 
					 | 
				
			||||||
        for client in self.clients:
 | 
					 | 
				
			||||||
            if client.is_muted:
 | 
					 | 
				
			||||||
                clients.append(client)
 | 
					 | 
				
			||||||
        return clients
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_ooc_muted_clients(self):
 | 
					 | 
				
			||||||
        clients = []
 | 
					 | 
				
			||||||
        for client in self.clients:
 | 
					 | 
				
			||||||
            if client.is_ooc_muted:
 | 
					 | 
				
			||||||
                clients.append(client)
 | 
					 | 
				
			||||||
        return clients
 | 
					 | 
				
			||||||
							
								
								
									
										1255
									
								
								server/commands.py
									
									
									
									
									
								
							
							
						
						
									
										1255
									
								
								server/commands.py
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,11 +0,0 @@
 | 
				
			|||||||
from enum import Enum
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TargetType(Enum):
 | 
					 | 
				
			||||||
        #possible keys: ip, OOC, id, cname, ipid, hdid
 | 
					 | 
				
			||||||
        IP = 0
 | 
					 | 
				
			||||||
        OOC_NAME = 1
 | 
					 | 
				
			||||||
        ID = 2
 | 
					 | 
				
			||||||
        CHAR_NAME = 3
 | 
					 | 
				
			||||||
        IPID = 4
 | 
					 | 
				
			||||||
        HDID = 5
 | 
					 | 
				
			||||||
        ALL = 6
 | 
					 | 
				
			||||||
@ -1,79 +0,0 @@
 | 
				
			|||||||
# tsuserver3, an Attorney Online server
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Copyright (C) 2016 argoneus <argoneuscze@gmail.com>
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
# it under the terms of the GNU General Public License as published by
 | 
					 | 
				
			||||||
# the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
# (at your option) any later version.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
# GNU General Public License for more details.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# You should have received a copy of the GNU General Public License
 | 
					 | 
				
			||||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
import asyncio
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from server import logger
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DistrictClient:
 | 
					 | 
				
			||||||
    def __init__(self, server):
 | 
					 | 
				
			||||||
        self.server = server
 | 
					 | 
				
			||||||
        self.reader = None
 | 
					 | 
				
			||||||
        self.writer = None
 | 
					 | 
				
			||||||
        self.message_queue = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def connect(self):
 | 
					 | 
				
			||||||
        loop = asyncio.get_event_loop()
 | 
					 | 
				
			||||||
        while True:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                self.reader, self.writer = await asyncio.open_connection(self.server.config['district_ip'],
 | 
					 | 
				
			||||||
                                                                         self.server.config['district_port'], loop=loop)
 | 
					 | 
				
			||||||
                await self.handle_connection()
 | 
					 | 
				
			||||||
            except (ConnectionRefusedError, TimeoutError):
 | 
					 | 
				
			||||||
                pass
 | 
					 | 
				
			||||||
            except (ConnectionResetError, asyncio.IncompleteReadError):
 | 
					 | 
				
			||||||
                self.writer = None
 | 
					 | 
				
			||||||
                self.reader = None
 | 
					 | 
				
			||||||
            finally:
 | 
					 | 
				
			||||||
                logger.log_debug("Couldn't connect to the district, retrying in 30 seconds.")
 | 
					 | 
				
			||||||
                await asyncio.sleep(30)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def handle_connection(self):
 | 
					 | 
				
			||||||
        logger.log_debug('District connected.')
 | 
					 | 
				
			||||||
        self.send_raw_message('AUTH#{}'.format(self.server.config['district_password']))
 | 
					 | 
				
			||||||
        while True:
 | 
					 | 
				
			||||||
            data = await self.reader.readuntil(b'\r\n')
 | 
					 | 
				
			||||||
            if not data:
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
            raw_msg = data.decode()[:-2]
 | 
					 | 
				
			||||||
            logger.log_debug('[DISTRICT][INC][RAW]{}'.format(raw_msg))
 | 
					 | 
				
			||||||
            cmd, *args = raw_msg.split('#')
 | 
					 | 
				
			||||||
            if cmd == 'GLOBAL':
 | 
					 | 
				
			||||||
                glob_name = '{}[{}:{}][{}]'.format('<dollar>G', args[1], args[2], args[3])
 | 
					 | 
				
			||||||
                if args[0] == '1':
 | 
					 | 
				
			||||||
                    glob_name += '[M]'
 | 
					 | 
				
			||||||
                self.server.send_all_cmd_pred('CT', glob_name, args[4], pred=lambda x: not x.muted_global)
 | 
					 | 
				
			||||||
            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, '1',
 | 
					 | 
				
			||||||
                                              pred=lambda x: not x.muted_adverts)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def write_queue(self):
 | 
					 | 
				
			||||||
        while self.message_queue:
 | 
					 | 
				
			||||||
            msg = self.message_queue.pop(0)
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                self.writer.write(msg)
 | 
					 | 
				
			||||||
                await self.writer.drain()
 | 
					 | 
				
			||||||
            except ConnectionResetError:
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def send_raw_message(self, msg):
 | 
					 | 
				
			||||||
        if not self.writer:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        self.message_queue.append('{}\r\n'.format(msg).encode())
 | 
					 | 
				
			||||||
        asyncio.ensure_future(self.write_queue(), loop=asyncio.get_event_loop())
 | 
					 | 
				
			||||||
@ -1,100 +0,0 @@
 | 
				
			|||||||
class EvidenceList:
 | 
					 | 
				
			||||||
    limit = 35
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Evidence:
 | 
					 | 
				
			||||||
        def __init__(self, name, desc, image, pos):
 | 
					 | 
				
			||||||
            self.name = name
 | 
					 | 
				
			||||||
            self.desc = desc
 | 
					 | 
				
			||||||
            self.image = image
 | 
					 | 
				
			||||||
            self.public = False
 | 
					 | 
				
			||||||
            self.pos = pos
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def set_name(self, name):
 | 
					 | 
				
			||||||
            self.name = name
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def set_desc(self, desc):
 | 
					 | 
				
			||||||
            self.desc = desc
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def set_image(self, image):
 | 
					 | 
				
			||||||
            self.image = image
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def to_string(self):
 | 
					 | 
				
			||||||
            sequence = (self.name, self.desc, self.image)
 | 
					 | 
				
			||||||
            return '&'.join(sequence)
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
    def __init__(self):
 | 
					 | 
				
			||||||
        self.evidences = []
 | 
					 | 
				
			||||||
        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 in client.area.owners:
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
        if client.area.evidence_mod == 'CM':
 | 
					 | 
				
			||||||
            if not client in client.area.owners and not client.is_mod:
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
        if client.area.evidence_mod == 'HiddenCM':
 | 
					 | 
				
			||||||
            if not client in client.area.owners and not client.is_mod:
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
        return True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def correct_format(self, client, desc):
 | 
					 | 
				
			||||||
        if client.area.evidence_mod != 'HiddenCM':
 | 
					 | 
				
			||||||
            return True
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            #correct format: <owner = pos>\ndesc
 | 
					 | 
				
			||||||
            if desc[:9] == '<owner = ' and desc[9:12] in self.poses and desc[12:14] == '>\n':
 | 
					 | 
				
			||||||
                return True
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    def add_evidence(self, client, name, description, image, pos = 'all'):
 | 
					 | 
				
			||||||
        if self.login(client):
 | 
					 | 
				
			||||||
            if client.area.evidence_mod == 'HiddenCM':
 | 
					 | 
				
			||||||
                pos = 'pos'
 | 
					 | 
				
			||||||
            if len(self.evidences) >= self.limit:
 | 
					 | 
				
			||||||
                client.send_host_message('You can\'t have more than {} evidence items at a time.'.format(self.limit))
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                self.evidences.append(self.Evidence(name, description, image, pos))
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    def evidence_swap(self, client, id1, id2):
 | 
					 | 
				
			||||||
        if self.login(client):
 | 
					 | 
				
			||||||
            self.evidences[id1], self.evidences[id2] = self.evidences[id2], self.evidences[id1]
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
    def create_evi_list(self, client):
 | 
					 | 
				
			||||||
        evi_list = []
 | 
					 | 
				
			||||||
        nums_list = [0]
 | 
					 | 
				
			||||||
        for i in range(len(self.evidences)):
 | 
					 | 
				
			||||||
            if client.area.evidence_mod == 'HiddenCM' and self.login(client):
 | 
					 | 
				
			||||||
                nums_list.append(i + 1)
 | 
					 | 
				
			||||||
                evi = self.evidences[i]
 | 
					 | 
				
			||||||
                evi_list.append(self.Evidence(evi.name, '<owner = {}>\n{}'.format(evi.pos, evi.desc), evi.image, evi.pos).to_string())
 | 
					 | 
				
			||||||
            elif client.pos in self.poses[self.evidences[i].pos]:
 | 
					 | 
				
			||||||
                nums_list.append(i + 1)
 | 
					 | 
				
			||||||
                evi_list.append(self.evidences[i].to_string())
 | 
					 | 
				
			||||||
        return nums_list, evi_list
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def del_evidence(self, client, id):
 | 
					 | 
				
			||||||
        if self.login(client):
 | 
					 | 
				
			||||||
            self.evidences.pop(id)
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    def edit_evidence(self, client, id, arg):
 | 
					 | 
				
			||||||
        if self.login(client):
 | 
					 | 
				
			||||||
            if client.area.evidence_mod == 'HiddenCM' and self.correct_format(client, arg[1]):
 | 
					 | 
				
			||||||
                self.evidences[id] = self.Evidence(arg[0], arg[1][14:], arg[2], arg[1][9:12])
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
            if client.area.evidence_mod == 'HiddenCM':
 | 
					 | 
				
			||||||
                client.send_host_message('You entered a wrong pos.')
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
            self.evidences[id] = self.Evidence(arg[0], arg[1], arg[2], arg[3])
 | 
					 | 
				
			||||||
@ -1,32 +0,0 @@
 | 
				
			|||||||
# tsuserver3, an Attorney Online server
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Copyright (C) 2016 argoneus <argoneuscze@gmail.com>
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
# it under the terms of the GNU General Public License as published by
 | 
					 | 
				
			||||||
# the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
# (at your option) any later version.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
# GNU General Public License for more details.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# You should have received a copy of the GNU General Public License
 | 
					 | 
				
			||||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ClientError(Exception):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class AreaError(Exception):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ArgumentError(Exception):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ServerError(Exception):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
@ -1,45 +0,0 @@
 | 
				
			|||||||
# tsuserver3, an Attorney Online server
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Copyright (C) 2016 argoneus <argoneuscze@gmail.com>
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
# it under the terms of the GNU General Public License as published by
 | 
					 | 
				
			||||||
# the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
# (at your option) any later version.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
# GNU General Public License for more details.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# You should have received a copy of the GNU General Public License
 | 
					 | 
				
			||||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# fantacrypt was a mistake, just hardcoding some numbers is good enough
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import binascii
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CRYPT_CONST_1 = 53761
 | 
					 | 
				
			||||||
CRYPT_CONST_2 = 32618
 | 
					 | 
				
			||||||
CRYPT_KEY = 5
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def fanta_decrypt(data):
 | 
					 | 
				
			||||||
    data_bytes = [int(data[x:x + 2], 16) for x in range(0, len(data), 2)]
 | 
					 | 
				
			||||||
    key = CRYPT_KEY
 | 
					 | 
				
			||||||
    ret = ''
 | 
					 | 
				
			||||||
    for byte in data_bytes:
 | 
					 | 
				
			||||||
        val = byte ^ ((key & 0xffff) >> 8)
 | 
					 | 
				
			||||||
        ret += chr(val)
 | 
					 | 
				
			||||||
        key = ((byte + key) * CRYPT_CONST_1) + CRYPT_CONST_2
 | 
					 | 
				
			||||||
    return ret
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def fanta_encrypt(data):
 | 
					 | 
				
			||||||
    key = CRYPT_KEY
 | 
					 | 
				
			||||||
    ret = ''
 | 
					 | 
				
			||||||
    for char in data:
 | 
					 | 
				
			||||||
        val = ord(char) ^ ((key & 0xffff) >> 8)
 | 
					 | 
				
			||||||
        ret += binascii.hexlify(val.to_bytes(1, byteorder='big')).decode().upper()
 | 
					 | 
				
			||||||
        key = ((val + key) * CRYPT_CONST_1) + CRYPT_CONST_2
 | 
					 | 
				
			||||||
    return ret
 | 
					 | 
				
			||||||
@ -1,78 +0,0 @@
 | 
				
			|||||||
# tsuserver3, an Attorney Online server
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Copyright (C) 2016 argoneus <argoneuscze@gmail.com>
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
# it under the terms of the GNU General Public License as published by
 | 
					 | 
				
			||||||
# the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
# (at your option) any later version.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
# GNU General Public License for more details.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# You should have received a copy of the GNU General Public License
 | 
					 | 
				
			||||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import logging
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not debug:
 | 
					 | 
				
			||||||
        debug_log.disabled = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    server_log = logging.getLogger('server')
 | 
					 | 
				
			||||||
    server_log.setLevel(logging.INFO)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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
 | 
					 | 
				
			||||||
    logging.getLogger('debug').debug(msg)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def log_server(msg, client=None):
 | 
					 | 
				
			||||||
    msg = parse_client_info(client) + msg
 | 
					 | 
				
			||||||
    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}][{:<3}][{}][MOD]'.format(info, client.id, client.name)
 | 
					 | 
				
			||||||
    return '[{:<15}][{:<3}][{}]'.format(info, client.id, client.name)
 | 
					 | 
				
			||||||
@ -1,89 +0,0 @@
 | 
				
			|||||||
# tsuserver3, an Attorney Online server
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Copyright (C) 2016 argoneus <argoneuscze@gmail.com>
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
# it under the terms of the GNU General Public License as published by
 | 
					 | 
				
			||||||
# the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
# (at your option) any later version.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
# GNU General Public License for more details.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# You should have received a copy of the GNU General Public License
 | 
					 | 
				
			||||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import asyncio
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
from server import logger
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MasterServerClient:
 | 
					 | 
				
			||||||
    def __init__(self, server):
 | 
					 | 
				
			||||||
        self.server = server
 | 
					 | 
				
			||||||
        self.reader = None
 | 
					 | 
				
			||||||
        self.writer = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def connect(self):
 | 
					 | 
				
			||||||
        loop = asyncio.get_event_loop()
 | 
					 | 
				
			||||||
        while True:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                self.reader, self.writer = await asyncio.open_connection(self.server.config['masterserver_ip'],
 | 
					 | 
				
			||||||
                                                                         self.server.config['masterserver_port'],
 | 
					 | 
				
			||||||
                                                                         loop=loop)
 | 
					 | 
				
			||||||
                await self.handle_connection()
 | 
					 | 
				
			||||||
            except (ConnectionRefusedError, TimeoutError):
 | 
					 | 
				
			||||||
                pass
 | 
					 | 
				
			||||||
            except (ConnectionResetError, asyncio.IncompleteReadError):
 | 
					 | 
				
			||||||
                self.writer = None
 | 
					 | 
				
			||||||
                self.reader = None
 | 
					 | 
				
			||||||
            finally:
 | 
					 | 
				
			||||||
                logger.log_debug("Couldn't connect to the master server, retrying in 30 seconds.")
 | 
					 | 
				
			||||||
                print("Couldn't connect to the master server, retrying in 30 seconds.")
 | 
					 | 
				
			||||||
                await asyncio.sleep(30)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def handle_connection(self):
 | 
					 | 
				
			||||||
        logger.log_debug('Master server connected.')
 | 
					 | 
				
			||||||
        await self.send_server_info()
 | 
					 | 
				
			||||||
        fl = False
 | 
					 | 
				
			||||||
        lastping = time.time() - 20
 | 
					 | 
				
			||||||
        while True:
 | 
					 | 
				
			||||||
            self.reader.feed_data(b'END')
 | 
					 | 
				
			||||||
            full_data = await self.reader.readuntil(b'END')
 | 
					 | 
				
			||||||
            full_data = full_data[:-3]
 | 
					 | 
				
			||||||
            if len(full_data) > 0:
 | 
					 | 
				
			||||||
                data_list = list(full_data.split(b'#%'))[:-1]
 | 
					 | 
				
			||||||
                for data in data_list:
 | 
					 | 
				
			||||||
                    raw_msg = data.decode()
 | 
					 | 
				
			||||||
                    cmd, *args = raw_msg.split('#')
 | 
					 | 
				
			||||||
                    if cmd != 'CHECK' and cmd != 'PONG':
 | 
					 | 
				
			||||||
                        logger.log_debug('[MASTERSERVER][INC][RAW]{}'.format(raw_msg))
 | 
					 | 
				
			||||||
                    elif cmd == 'CHECK':
 | 
					 | 
				
			||||||
                        await self.send_raw_message('PING#%')
 | 
					 | 
				
			||||||
                    elif cmd == 'PONG':
 | 
					 | 
				
			||||||
                        fl = False
 | 
					 | 
				
			||||||
                    elif cmd == 'NOSERV':
 | 
					 | 
				
			||||||
                        await self.send_server_info()
 | 
					 | 
				
			||||||
            if time.time() - lastping > 5:
 | 
					 | 
				
			||||||
                if fl:
 | 
					 | 
				
			||||||
                    return
 | 
					 | 
				
			||||||
                lastping = time.time()
 | 
					 | 
				
			||||||
                fl = True
 | 
					 | 
				
			||||||
                await self.send_raw_message('PING#%')
 | 
					 | 
				
			||||||
            await asyncio.sleep(1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def send_server_info(self):
 | 
					 | 
				
			||||||
        cfg = self.server.config
 | 
					 | 
				
			||||||
        msg = 'SCC#{}#{}#{}#{}#%'.format(cfg['port'], cfg['masterserver_name'], cfg['masterserver_description'],
 | 
					 | 
				
			||||||
                                         self.server.software)
 | 
					 | 
				
			||||||
        await self.send_raw_message(msg)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def send_raw_message(self, msg):
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            self.writer.write(msg.encode())
 | 
					 | 
				
			||||||
            await self.writer.drain()
 | 
					 | 
				
			||||||
        except ConnectionResetError:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
@ -1,305 +0,0 @@
 | 
				
			|||||||
# tsuserver3, an Attorney Online server
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Copyright (C) 2016 argoneus <argoneuscze@gmail.com>
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
# it under the terms of the GNU General Public License as published by
 | 
					 | 
				
			||||||
# the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
# (at your option) any later version.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
# GNU General Public License for more details.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# You should have received a copy of the GNU General Public License
 | 
					 | 
				
			||||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import asyncio
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import yaml
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from server import logger
 | 
					 | 
				
			||||||
from server.aoprotocol import AOProtocol
 | 
					 | 
				
			||||||
from server.area_manager import AreaManager
 | 
					 | 
				
			||||||
from server.ban_manager import BanManager
 | 
					 | 
				
			||||||
from server.client_manager import ClientManager
 | 
					 | 
				
			||||||
from server.districtclient import DistrictClient
 | 
					 | 
				
			||||||
from server.exceptions import ServerError
 | 
					 | 
				
			||||||
from server.masterserverclient import MasterServerClient
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TsuServer3:
 | 
					 | 
				
			||||||
    def __init__(self):
 | 
					 | 
				
			||||||
        self.config = None
 | 
					 | 
				
			||||||
        self.allowed_iniswaps = None
 | 
					 | 
				
			||||||
        self.load_config()
 | 
					 | 
				
			||||||
        self.load_iniswaps()
 | 
					 | 
				
			||||||
        self.client_manager = ClientManager(self)
 | 
					 | 
				
			||||||
        self.area_manager = AreaManager(self)
 | 
					 | 
				
			||||||
        self.ban_manager = BanManager()
 | 
					 | 
				
			||||||
        self.software = 'tsuserver3'
 | 
					 | 
				
			||||||
        self.version = 'tsuserver3dev'
 | 
					 | 
				
			||||||
        self.release = 3
 | 
					 | 
				
			||||||
        self.major_version = 1
 | 
					 | 
				
			||||||
        self.minor_version = 1
 | 
					 | 
				
			||||||
        self.ipid_list = {}
 | 
					 | 
				
			||||||
        self.hdid_list = {}
 | 
					 | 
				
			||||||
        self.char_list = None
 | 
					 | 
				
			||||||
        self.char_pages_ao1 = None
 | 
					 | 
				
			||||||
        self.music_list = None
 | 
					 | 
				
			||||||
        self.music_list_ao2 = None
 | 
					 | 
				
			||||||
        self.music_pages_ao1 = None
 | 
					 | 
				
			||||||
        self.backgrounds = None
 | 
					 | 
				
			||||||
        self.load_characters()
 | 
					 | 
				
			||||||
        self.load_music()
 | 
					 | 
				
			||||||
        self.load_backgrounds()
 | 
					 | 
				
			||||||
        self.load_ids()
 | 
					 | 
				
			||||||
        self.district_client = None
 | 
					 | 
				
			||||||
        self.ms_client = None
 | 
					 | 
				
			||||||
        self.rp_mode = False
 | 
					 | 
				
			||||||
        logger.setup_logger(debug=self.config['debug'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def start(self):
 | 
					 | 
				
			||||||
        loop = asyncio.get_event_loop()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        bound_ip = '0.0.0.0'
 | 
					 | 
				
			||||||
        if self.config['local']:
 | 
					 | 
				
			||||||
            bound_ip = '127.0.0.1'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ao_server_crt = loop.create_server(lambda: AOProtocol(self), bound_ip, self.config['port'])
 | 
					 | 
				
			||||||
        ao_server = loop.run_until_complete(ao_server_crt)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self.config['use_district']:
 | 
					 | 
				
			||||||
            self.district_client = DistrictClient(self)
 | 
					 | 
				
			||||||
            asyncio.ensure_future(self.district_client.connect(), loop=loop)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self.config['use_masterserver']:
 | 
					 | 
				
			||||||
            self.ms_client = MasterServerClient(self)
 | 
					 | 
				
			||||||
            asyncio.ensure_future(self.ms_client.connect(), loop=loop)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        logger.log_debug('Server started.')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            loop.run_forever()
 | 
					 | 
				
			||||||
        except KeyboardInterrupt:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        logger.log_debug('Server shutting down.')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ao_server.close()
 | 
					 | 
				
			||||||
        loop.run_until_complete(ao_server.wait_closed())
 | 
					 | 
				
			||||||
        loop.close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_version_string(self):
 | 
					 | 
				
			||||||
        return str(self.release) + '.' + str(self.major_version) + '.' + str(self.minor_version)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def new_client(self, transport):
 | 
					 | 
				
			||||||
        c = self.client_manager.new_client(transport)
 | 
					 | 
				
			||||||
        if self.rp_mode:
 | 
					 | 
				
			||||||
            c.in_rp = True
 | 
					 | 
				
			||||||
        c.server = self
 | 
					 | 
				
			||||||
        c.area = self.area_manager.default_area()
 | 
					 | 
				
			||||||
        c.area.new_client(c)
 | 
					 | 
				
			||||||
        return c
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def remove_client(self, client):
 | 
					 | 
				
			||||||
        client.area.remove_client(client)
 | 
					 | 
				
			||||||
        self.client_manager.remove_client(client)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_player_count(self):
 | 
					 | 
				
			||||||
        return len(self.client_manager.clients)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def load_config(self):
 | 
					 | 
				
			||||||
        with open('config/config.yaml', 'r', encoding = 'utf-8') as cfg:
 | 
					 | 
				
			||||||
            self.config = yaml.load(cfg)
 | 
					 | 
				
			||||||
            self.config['motd'] = self.config['motd'].replace('\\n', ' \n') 
 | 
					 | 
				
			||||||
        if 'music_change_floodguard' not in self.config:
 | 
					 | 
				
			||||||
            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}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def load_characters(self):
 | 
					 | 
				
			||||||
        with open('config/characters.yaml', 'r', encoding = 'utf-8') as chars:
 | 
					 | 
				
			||||||
            self.char_list = yaml.load(chars)
 | 
					 | 
				
			||||||
        self.build_char_pages_ao1()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def load_music(self):
 | 
					 | 
				
			||||||
        with open('config/music.yaml', 'r', encoding = 'utf-8') as music:
 | 
					 | 
				
			||||||
            self.music_list = yaml.load(music)
 | 
					 | 
				
			||||||
        self.build_music_pages_ao1()
 | 
					 | 
				
			||||||
        self.build_music_list_ao2()
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    def load_ids(self):
 | 
					 | 
				
			||||||
        self.ipid_list = {}
 | 
					 | 
				
			||||||
        self.hdid_list = {}
 | 
					 | 
				
			||||||
        #load ipids
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            with open('storage/ip_ids.json', 'r', encoding = 'utf-8') as whole_list:
 | 
					 | 
				
			||||||
                self.ipid_list = json.loads(whole_list.read())
 | 
					 | 
				
			||||||
        except:
 | 
					 | 
				
			||||||
            logger.log_debug('Failed to load ip_ids.json from ./storage. If ip_ids.json is exist then remove it.')
 | 
					 | 
				
			||||||
        #load hdids
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            with open('storage/hd_ids.json', 'r', encoding = 'utf-8') as whole_list:
 | 
					 | 
				
			||||||
                self.hdid_list = json.loads(whole_list.read())
 | 
					 | 
				
			||||||
        except:
 | 
					 | 
				
			||||||
            logger.log_debug('Failed to load hd_ids.json from ./storage. If hd_ids.json is exist then remove it.')
 | 
					 | 
				
			||||||
           
 | 
					 | 
				
			||||||
    def dump_ipids(self):
 | 
					 | 
				
			||||||
        with open('storage/ip_ids.json', 'w') as whole_list:
 | 
					 | 
				
			||||||
            json.dump(self.ipid_list, whole_list)
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
    def dump_hdids(self):
 | 
					 | 
				
			||||||
        with open('storage/hd_ids.json', 'w') as whole_list:
 | 
					 | 
				
			||||||
            json.dump(self.hdid_list, whole_list)
 | 
					 | 
				
			||||||
         
 | 
					 | 
				
			||||||
    def get_ipid(self, ip):
 | 
					 | 
				
			||||||
        if not (ip in self.ipid_list):
 | 
					 | 
				
			||||||
            self.ipid_list[ip] = len(self.ipid_list)
 | 
					 | 
				
			||||||
            self.dump_ipids()
 | 
					 | 
				
			||||||
        return self.ipid_list[ip]
 | 
					 | 
				
			||||||
         
 | 
					 | 
				
			||||||
    def load_backgrounds(self):
 | 
					 | 
				
			||||||
        with open('config/backgrounds.yaml', 'r', encoding = 'utf-8') as bgs:
 | 
					 | 
				
			||||||
            self.backgrounds = yaml.load(bgs)
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
    def load_iniswaps(self):
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            with open('config/iniswaps.yaml', 'r', encoding = 'utf-8') as iniswaps:
 | 
					 | 
				
			||||||
                self.allowed_iniswaps = yaml.load(iniswaps)
 | 
					 | 
				
			||||||
        except:
 | 
					 | 
				
			||||||
            logger.log_debug('cannot find iniswaps.yaml')
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def build_char_pages_ao1(self):
 | 
					 | 
				
			||||||
        self.char_pages_ao1 = [self.char_list[x:x + 10] for x in range(0, len(self.char_list), 10)]
 | 
					 | 
				
			||||||
        for i in range(len(self.char_list)):
 | 
					 | 
				
			||||||
            self.char_pages_ao1[i // 10][i % 10] = '{}#{}&&0&&&0&'.format(i, self.char_list[i])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def build_music_pages_ao1(self):
 | 
					 | 
				
			||||||
        self.music_pages_ao1 = []
 | 
					 | 
				
			||||||
        index = 0
 | 
					 | 
				
			||||||
        # add areas first
 | 
					 | 
				
			||||||
        for area in self.area_manager.areas:
 | 
					 | 
				
			||||||
            self.music_pages_ao1.append('{}#{}'.format(index, area.name))
 | 
					 | 
				
			||||||
            index += 1
 | 
					 | 
				
			||||||
        # then add music
 | 
					 | 
				
			||||||
        for item in self.music_list:
 | 
					 | 
				
			||||||
            self.music_pages_ao1.append('{}#{}'.format(index, item['category']))
 | 
					 | 
				
			||||||
            index += 1
 | 
					 | 
				
			||||||
            for song in item['songs']:
 | 
					 | 
				
			||||||
                self.music_pages_ao1.append('{}#{}'.format(index, song['name']))
 | 
					 | 
				
			||||||
                index += 1
 | 
					 | 
				
			||||||
        self.music_pages_ao1 = [self.music_pages_ao1[x:x + 10] for x in range(0, len(self.music_pages_ao1), 10)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def build_music_list_ao2(self):
 | 
					 | 
				
			||||||
        self.music_list_ao2 = []
 | 
					 | 
				
			||||||
        # add areas first
 | 
					 | 
				
			||||||
        for area in self.area_manager.areas:
 | 
					 | 
				
			||||||
            self.music_list_ao2.append(area.name)
 | 
					 | 
				
			||||||
            # then add music
 | 
					 | 
				
			||||||
        for item in self.music_list:
 | 
					 | 
				
			||||||
            self.music_list_ao2.append(item['category'])
 | 
					 | 
				
			||||||
            for song in item['songs']:
 | 
					 | 
				
			||||||
                self.music_list_ao2.append(song['name'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def is_valid_char_id(self, char_id):
 | 
					 | 
				
			||||||
        return len(self.char_list) > char_id >= 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_char_id_by_name(self, name):
 | 
					 | 
				
			||||||
        for i, ch in enumerate(self.char_list):
 | 
					 | 
				
			||||||
            if ch.lower() == name.lower():
 | 
					 | 
				
			||||||
                return i
 | 
					 | 
				
			||||||
        raise ServerError('Character not found.')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_song_data(self, music):
 | 
					 | 
				
			||||||
        for item in self.music_list:
 | 
					 | 
				
			||||||
            if item['category'] == music:
 | 
					 | 
				
			||||||
                return item['category'], -1
 | 
					 | 
				
			||||||
            for song in item['songs']:
 | 
					 | 
				
			||||||
                if song['name'] == music:
 | 
					 | 
				
			||||||
                    try:
 | 
					 | 
				
			||||||
                        return song['name'], song['length']
 | 
					 | 
				
			||||||
                    except KeyError:
 | 
					 | 
				
			||||||
                        return song['name'], -1
 | 
					 | 
				
			||||||
        raise ServerError('Music not found.')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def send_all_cmd_pred(self, cmd, *args, pred=lambda x: True):
 | 
					 | 
				
			||||||
        for client in self.client_manager.clients:
 | 
					 | 
				
			||||||
            if pred(client):
 | 
					 | 
				
			||||||
                client.send_command(cmd, *args)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def broadcast_global(self, client, msg, as_mod=False):
 | 
					 | 
				
			||||||
        char_name = client.get_char_name()
 | 
					 | 
				
			||||||
        ooc_name = '{}[{}][{}]'.format('<dollar>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)
 | 
					 | 
				
			||||||
        if self.config['use_district']:
 | 
					 | 
				
			||||||
            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('<dollar>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.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), '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#<area1_p: int>#<area2_p: int>#...
 | 
					 | 
				
			||||||
        Status:
 | 
					 | 
				
			||||||
            ARUP#1##<area1_s: string>##<area2_s: string>#...
 | 
					 | 
				
			||||||
        CM:
 | 
					 | 
				
			||||||
            ARUP#2##<area1_cm: string>##<area2_cm: string>#...
 | 
					 | 
				
			||||||
        Lockedness:
 | 
					 | 
				
			||||||
            ARUP#3##<area1_l: string>##<area2_l: string>#...
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        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')
 | 
					 | 
				
			||||||
        with open('config/characters.yaml', 'r') as chars:
 | 
					 | 
				
			||||||
            self.char_list = yaml.load(chars)
 | 
					 | 
				
			||||||
        with open('config/music.yaml', 'r') as music:
 | 
					 | 
				
			||||||
            self.music_list = yaml.load(music)
 | 
					 | 
				
			||||||
        self.build_music_pages_ao1()
 | 
					 | 
				
			||||||
        self.build_music_list_ao2()
 | 
					 | 
				
			||||||
        with open('config/backgrounds.yaml', 'r') as bgs:
 | 
					 | 
				
			||||||
            self.backgrounds = yaml.load(bgs)
 | 
					 | 
				
			||||||
@ -1,215 +0,0 @@
 | 
				
			|||||||
# tsuserver3, an Attorney Online server
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Copyright (C) 2017 argoneus <argoneuscze@gmail.com>
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
# it under the terms of the GNU General Public License as published by
 | 
					 | 
				
			||||||
# the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
# (at your option) any later version.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
# GNU General Public License for more details.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# You should have received a copy of the GNU General Public License
 | 
					 | 
				
			||||||
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Partly authored by Johan Hanssen Seferidis (MIT license):
 | 
					 | 
				
			||||||
# https://github.com/Pithikos/python-websocket-server
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import asyncio
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import struct
 | 
					 | 
				
			||||||
from base64 import b64encode
 | 
					 | 
				
			||||||
from hashlib import sha1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from server import logger
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Bitmasks:
 | 
					 | 
				
			||||||
    FIN = 0x80
 | 
					 | 
				
			||||||
    OPCODE = 0x0f
 | 
					 | 
				
			||||||
    MASKED = 0x80
 | 
					 | 
				
			||||||
    PAYLOAD_LEN = 0x7f
 | 
					 | 
				
			||||||
    PAYLOAD_LEN_EXT16 = 0x7e
 | 
					 | 
				
			||||||
    PAYLOAD_LEN_EXT64 = 0x7f
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Opcode:
 | 
					 | 
				
			||||||
    CONTINUATION = 0x0
 | 
					 | 
				
			||||||
    TEXT = 0x1
 | 
					 | 
				
			||||||
    BINARY = 0x2
 | 
					 | 
				
			||||||
    CLOSE_CONN = 0x8
 | 
					 | 
				
			||||||
    PING = 0x9
 | 
					 | 
				
			||||||
    PONG = 0xA
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class WebSocket:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    State data for clients that are connected via a WebSocket that wraps
 | 
					 | 
				
			||||||
    over a conventional TCP connection.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, client, protocol):
 | 
					 | 
				
			||||||
        self.client = client
 | 
					 | 
				
			||||||
        self.transport = client.transport
 | 
					 | 
				
			||||||
        self.protocol = protocol
 | 
					 | 
				
			||||||
        self.keep_alive = True
 | 
					 | 
				
			||||||
        self.handshake_done = False
 | 
					 | 
				
			||||||
        self.valid = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def handle(self, data):
 | 
					 | 
				
			||||||
        if not self.handshake_done:
 | 
					 | 
				
			||||||
            return self.handshake(data)
 | 
					 | 
				
			||||||
        return self.parse(data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def parse(self, data):
 | 
					 | 
				
			||||||
        b1, b2 = 0, 0
 | 
					 | 
				
			||||||
        if len(data) >= 2:
 | 
					 | 
				
			||||||
            b1, b2 = data[0], data[1]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fin    = b1 & Bitmasks.FIN
 | 
					 | 
				
			||||||
        opcode = b1 & Bitmasks.OPCODE
 | 
					 | 
				
			||||||
        masked = b2 & Bitmasks.MASKED
 | 
					 | 
				
			||||||
        payload_length = b2 & Bitmasks.PAYLOAD_LEN
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not b1:
 | 
					 | 
				
			||||||
            # Connection closed
 | 
					 | 
				
			||||||
            self.keep_alive = 0
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        if opcode == Opcode.CLOSE_CONN:
 | 
					 | 
				
			||||||
            # Connection close requested
 | 
					 | 
				
			||||||
            self.keep_alive = 0
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        if not masked:
 | 
					 | 
				
			||||||
            # Client was not masked (spec violation)
 | 
					 | 
				
			||||||
            logger.log_debug("ws: client was not masked.", self.client)
 | 
					 | 
				
			||||||
            self.keep_alive = 0
 | 
					 | 
				
			||||||
            print(data)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        if opcode == Opcode.CONTINUATION:
 | 
					 | 
				
			||||||
            # No continuation frames supported
 | 
					 | 
				
			||||||
            logger.log_debug("ws: client tried to send continuation frame.", self.client)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        elif opcode == Opcode.BINARY:
 | 
					 | 
				
			||||||
            # No binary frames supported
 | 
					 | 
				
			||||||
            logger.log_debug("ws: client tried to send binary frame.", self.client)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        elif opcode == Opcode.TEXT:
 | 
					 | 
				
			||||||
            def opcode_handler(s, msg):
 | 
					 | 
				
			||||||
                return msg
 | 
					 | 
				
			||||||
        elif opcode == Opcode.PING:
 | 
					 | 
				
			||||||
            opcode_handler = self.send_pong
 | 
					 | 
				
			||||||
        elif opcode == Opcode.PONG:
 | 
					 | 
				
			||||||
            opcode_handler = lambda s, msg: None
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            # Unknown opcode
 | 
					 | 
				
			||||||
            logger.log_debug("ws: unknown opcode!", self.client)
 | 
					 | 
				
			||||||
            self.keep_alive = 0
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        mask_offset = 2
 | 
					 | 
				
			||||||
        if payload_length == 126:
 | 
					 | 
				
			||||||
            payload_length = struct.unpack(">H", data[2:4])[0]
 | 
					 | 
				
			||||||
            mask_offset = 4
 | 
					 | 
				
			||||||
        elif payload_length == 127:
 | 
					 | 
				
			||||||
            payload_length = struct.unpack(">Q", data[2:10])[0]
 | 
					 | 
				
			||||||
            mask_offset = 10
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        masks = data[mask_offset:mask_offset + 4]
 | 
					 | 
				
			||||||
        decoded = ""
 | 
					 | 
				
			||||||
        for char in data[mask_offset + 4:payload_length + mask_offset + 4]:
 | 
					 | 
				
			||||||
            char ^= masks[len(decoded) % 4]
 | 
					 | 
				
			||||||
            decoded += chr(char)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return opcode_handler(self, decoded)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def send_message(self, message):
 | 
					 | 
				
			||||||
        self.send_text(message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def send_pong(self, message):
 | 
					 | 
				
			||||||
        self.send_text(message, Opcode.PONG)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def send_text(self, message, opcode=Opcode.TEXT):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Important: Fragmented (continuation) messages are not supported since
 | 
					 | 
				
			||||||
        their usage cases are limited - when we don't know the payload length.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Validate message
 | 
					 | 
				
			||||||
        if isinstance(message, bytes):
 | 
					 | 
				
			||||||
            message = message.decode("utf-8")
 | 
					 | 
				
			||||||
        elif isinstance(message, str):
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            raise TypeError("Message must be either str or bytes")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        header  = bytearray()
 | 
					 | 
				
			||||||
        payload = message.encode("utf-8")
 | 
					 | 
				
			||||||
        payload_length = len(payload)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Normal payload
 | 
					 | 
				
			||||||
        if payload_length <= 125:
 | 
					 | 
				
			||||||
            header.append(Bitmasks.FIN | opcode)
 | 
					 | 
				
			||||||
            header.append(payload_length)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Extended payload
 | 
					 | 
				
			||||||
        elif payload_length >= 126 and payload_length <= 65535:
 | 
					 | 
				
			||||||
            header.append(Bitmasks.FIN | opcode)
 | 
					 | 
				
			||||||
            header.append(Bitmasks.PAYLOAD_LEN_EXT16)
 | 
					 | 
				
			||||||
            header.extend(struct.pack(">H", payload_length))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Huge extended payload
 | 
					 | 
				
			||||||
        elif payload_length < (1 << 64):
 | 
					 | 
				
			||||||
            header.append(Bitmasks.FIN | opcode)
 | 
					 | 
				
			||||||
            header.append(Bitmasks.PAYLOAD_LEN_EXT64)
 | 
					 | 
				
			||||||
            header.extend(struct.pack(">Q", payload_length))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            raise Exception("Message is too big")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.transport.write(header + payload)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def handshake(self, data):
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            message = data[0:1024].decode().strip()
 | 
					 | 
				
			||||||
        except UnicodeDecodeError:
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        upgrade = re.search('\nupgrade[\s]*:[\s]*websocket', message.lower())
 | 
					 | 
				
			||||||
        if not upgrade:
 | 
					 | 
				
			||||||
            self.keep_alive = False
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        key = re.search('\n[sS]ec-[wW]eb[sS]ocket-[kK]ey[\s]*:[\s]*(.*)\r\n', message)
 | 
					 | 
				
			||||||
        if key:
 | 
					 | 
				
			||||||
            key = key.group(1)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            logger.log_debug("Client tried to connect but was missing a key", self.client)
 | 
					 | 
				
			||||||
            self.keep_alive = False
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        response = self.make_handshake_response(key)
 | 
					 | 
				
			||||||
        print(response.encode())
 | 
					 | 
				
			||||||
        self.transport.write(response.encode())
 | 
					 | 
				
			||||||
        self.handshake_done = True
 | 
					 | 
				
			||||||
        self.valid = True
 | 
					 | 
				
			||||||
        return True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def make_handshake_response(self, key):
 | 
					 | 
				
			||||||
        return \
 | 
					 | 
				
			||||||
          'HTTP/1.1 101 Switching Protocols\r\n'\
 | 
					 | 
				
			||||||
          'Upgrade: websocket\r\n'              \
 | 
					 | 
				
			||||||
          'Connection: Upgrade\r\n'             \
 | 
					 | 
				
			||||||
          'Sec-WebSocket-Accept: %s\r\n'        \
 | 
					 | 
				
			||||||
          '\r\n' % self.calculate_response_key(key)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def calculate_response_key(self, key):
 | 
					 | 
				
			||||||
        GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
 | 
					 | 
				
			||||||
        hash = sha1(key.encode() + GUID.encode())
 | 
					 | 
				
			||||||
        response_key = b64encode(hash.digest()).strip()
 | 
					 | 
				
			||||||
        return response_key.decode('ASCII')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def finish(self):
 | 
					 | 
				
			||||||
        self.protocol.connection_lost(self)
 | 
					 | 
				
			||||||
							
								
								
									
										2227
									
								
								tsuserver3.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2227
									
								
								tsuserver3.patch
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user