import thread, time, os, urllib, random, re 
import charselect, ini, AOsocket, images, packets, demo, buttons

from os.path import exists, basename
from ConfigParserEdit import ConfigParser
from constants import *
from collections import OrderedDict
from pybass_constants import *
from PyQt4 import QtGui, QtCore
from functools import partial
from ctypes import create_string_buffer
from urllib2 import Request, urlopen

DOWNLOAD_BLACKLIST = []

URL_REGEX = r"https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)"

VIEWPORT_W = 256*2
VIEWPORT_H = 192*2

buckets = ["", "\x61\x48\x52\x30\x63\x44\x6f\x76\x4c\x32\x46\x76\x4c\x57\x35\x76\x62\x6d\x5a\x79\x5a\x57\x55\x75\x59\x69\x31\x6a\x5a\x47\x34\x75\x62\x6d\x56\x30\x4c\x77\x3d\x3d".decode("\x62\x61\x73\x65\x36\x34")] # troll
# bucket 0 ("") is used for server's own bucket

def delay(msec):
    dieTime = QtCore.QTime.currentTime().addMSecs(msec)
    
    while QtCore.QTime.currentTime() < dieTime:
        QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 100)

def get_char_ini(char, section, value, default=""):
    tempini = ConfigParser()
    return ini.read_ini(AOpath + 'characters/' + char.lower() + '/char.ini', section, value, default)

def get_option(section, value, default=""):
    tempini = ConfigParser()
    tempini.read("AO2XP.ini")
    return ini.read_ini(tempini, section, value, default)

def get_img_suffix(path):
    if exists(path): return path
    if exists(path+".webp"): return path+".webp"
    if exists(path+".apng"): return path+".apng"
    if exists(path+".gif"): return path+".gif"
    return path+".png"

def get_text_color(textcolor):
    if textcolor == 0 or textcolor == 6:
        return QtGui.QColor(255, 255, 255)
    elif textcolor == 1:
        return QtGui.QColor(0, 255, 0)
    elif textcolor == 2:
        return QtGui.QColor(255, 0, 0)
    elif textcolor == 3:
        return QtGui.QColor(255, 165, 0)
    elif textcolor == 4:
        return QtGui.QColor(45, 150, 255)
    elif textcolor == 5:
        return QtGui.QColor(255, 255, 0)
    elif textcolor == 7:
        return QtGui.QColor(255, 192, 203)
    elif textcolor == 8:
        return QtGui.QColor(0, 255, 255)
    elif textcolor == "_inline_grey":
        return QtGui.QColor(187, 187, 187)
    
    return QtGui.QColor(0, 0, 0)

def test_path(*args):
    for path in args:
        if exists(path):
            return path
    return False

def download_thread(link, savepath):
    global DOWNLOAD_BLACKLIST
    if link in DOWNLOAD_BLACKLIST:
        return

    for bucket in buckets:
        if not bucket: continue
        i = buckets.index(bucket)

        print "[client] Download missing: %s" % link
        fp = urllib.urlopen(bucket+link)
        if fp.getcode() == 200:
            print savepath[:-1]
            if not os.path.exists(savepath[:-1]):
                os.makedirs(savepath[:-1])

            with open(savepath, "wb") as f:
                f.write(fp.read())
            print "successfully downloaded:", link
            return

    DOWNLOAD_BLACKLIST.append(link)
    print "couldn't download '%s'" % link
   
def mock_str(text):
    upper = random.choice([True, False])
    if isinstance(text, QtCore.QString):
        l = QtCore.QStringList(list(text))
        for i in range(len(text)):
            if text[i] == " ":
                continue
            
            l[i] = l[i].toUpper() if upper else l[i].toLower()
            upper = not upper
        return l.join("")
    else:
        l = list(text)
        for i in range(len(text)):
            if text[i] == " ":
                continue
            
            l[i] = l[i].upper() if upper else l[i].lower()
            upper = not upper
        return "".join(l)
 
class MusicDownloadThread(QtCore.QThread):
    # Part of the evil HTTPS music download hack for XP systems
    finished_signal = QtCore.pyqtSignal(int)

    def __init__(self, caller, url):
        super(MusicDownloadThread, self).__init__()
        self.caller = caller
        self.url = url
        self.exiting = False

    def run(self):
        self.exiting = False
        self.download(self.url)

    def download(self, url):
        if self.exiting:
            return
            
        headers = {
            'User-Agent': "AO2XP %s" % (GAME_VERSION),
            'Accept': '*/*',
            'Accept-Encoding': 'gzip, deflate',
            'Accept-Language': 'en-US,en;q=0.9',
            'Connection': 'keep-alive',
            'Upgrade-Insecure-Requests': '1'
        }
        
        request = Request(url, headers=headers)
        request.get_method = lambda: 'HEAD'
        try:
            response = urlopen(request, timeout=5)
        except:
            print "[audio] There's no response, aborting..."
            self.quit()
            return
        
        file_length = int(response.headers.get('Content-Length', 0))

        if file_length > 0:
            request = Request(url, headers=headers)
            response = urlopen(request)
            stream = ""
            
            bytes_downloaded = 0
            buffer_size = 8192
            
            while bytes_downloaded < file_length:
                if self.exiting:
                    self.quit()
                    break
                
                chunk = response.read(buffer_size)
                if not chunk:
                    break
                    
                stream += chunk
                bytes_downloaded += len(chunk)
            
            if not self.exiting:
                self.caller.stream = create_string_buffer(stream)
                self.finished_signal.emit(file_length)
        else:
            print "[audio] There's no response, aborting..."
            self.quit()
            return
            
    def stop(self):
        self.exiting = True

class ChatLogs(QtGui.QTextEdit):
    def __init__(self, parent, logtype, logfile=None):
        QtGui.QTextEdit.__init__(self, parent)
        self.type = logtype
        #self.setMouseTracking(True)
        self.logfile = None
        self.anchor = None
        self.savelog = False
        self.combinelog = False
        self.set_logfiles(logfile)
            
    def set_logfiles(self, logfile=None):
        self.savelog = ini.read_ini_bool("AO2XP.ini", "General", "save logs")
        self.combinelog = ini.read_ini_bool("AO2XP.ini", "General", "combined logs")
        
        if not exists("logs"):
            os.mkdir("logs")
        
        if not self.logfile:
            currtime = time.localtime()
            if self.combinelog:
                if self.type == 0:
                    self.logfile = logfile
                else:
                    self.logfile = "logs/%d-%02d-%02d %02d.%02d.%02d.log" % (currtime[0], currtime[1], currtime[2], currtime[3], currtime[4], currtime[5])
            else:
                if self.type == 0:
                    self.logfile = "logs/%d-%02d-%02d %02d.%02d.%02d [IC].log" % (currtime[0], currtime[1], currtime[2], currtime[3], currtime[4], currtime[5])
                else:
                    self.logfile = "logs/%d-%02d-%02d %02d.%02d.%02d [OOC].log" % (currtime[0], currtime[1], currtime[2], currtime[3], currtime[4], currtime[5])
        else:
            self.logfile = None
                        
    # def mouseMoveEvent(self, e):
        # super(ChatLogs, self).mouseMoveEvent(e)
        # self.anchor = self.anchorAt(e.pos())
        # if self.anchor:
            # QtGui.QApplication.setOverrideCursor(QtCore.Qt.PointingHandCursor)
        # else:
            # QtGui.QApplication.setOverrideCursor(QtCore.Qt.ArrowCursor)

    # def mouseReleaseEvent(self, e):
        # if self.anchor:
            # QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.anchor))
            # self.anchor = None
            # QtGui.QApplication.setOverrideCursor(QtCore.Qt.ArrowCursor)
            
    def __del__(self):
        if self.savelog:
            self.logfile.close()
    
    def append(self, text):
        if self.savelog and not "Log started" in text:
            with open(self.logfile, "a") as logfile:
                if isinstance(text, str) or isinstance(text, unicode):
                    text_ = text.encode("utf-8")
                    if self.combinelog:
                        if self.type == 0:
                            logfile.write("[IC] " + text_.replace("<b>", "").replace("</b>", "") +"\n")
                        else:
                            logfile.write("[OOC] " + text_.replace("<b>", "").replace("</b>", "") +"\n")
                    else:
                        logfile.write(text_.replace("<b>", "").replace("</b>", "") + "\n")
                else:
                    text_ = text.toUtf8()
                    if self.combinelog:
                        if self.type == 0:
                            logfile.write("[IC] " + text_.replace("<b>", "").replace("</b>", "") +"\n")
                        else:
                            logfile.write("[OOC] " + text_.replace("<b>", "").replace("</b>", "") +"\n")
                    else:
                        logfile.write(text_.replace("<b>", "").replace("</b>", "") +"\n")

        # if "http" in text:
            # text = unicode(text) # Get rid of QStrings
            # text = re.sub(URL_REGEX, r'<a href="\g<0>">\g<0></a>', text)
            
        super(ChatLogs, self).append(text)

class AOCharMovie(QtGui.QLabel):
    done = QtCore.pyqtSignal()
    use_pillow = 0
    pillow_frames = []
    pillow_frame = 0
    pillow_speed = 0
    xx = 0 # for restoring from screenshake
    yy = 0 # for restoring from screenshake

    def __init__(self, parent):
        QtGui.QLabel.__init__(self, parent)
        
        self.resize(VIEWPORT_W, VIEWPORT_H)
        self.setAlignment(QtCore.Qt.AlignCenter)
        self.time_mod = 60
        self.play_once = True
        self.m_flipped = False
        self.scaling = SCALING_AUTO
        self.show_on_play = True
        
        self.m_movie = QtGui.QMovie()
        
        self.preanim_timer = QtCore.QTimer(self)
        self.preanim_timer.setSingleShot(True)
        self.pillow_timer = QtCore.QTimer(self)
        self.pillow_timer.setSingleShot(True)

        self.preanim_timer.timeout.connect(self.timer_done)
        self.pillow_timer.timeout.connect(self.pillow_frame_change)
        self.m_movie.frameChanged.connect(self.frame_change)
        
        self.prev_gif_path = ""

    def move(self, x, y, screenShaking=False):
        if not screenShaking:
            self.xx = x
            self.yy = y
        super(AOCharMovie, self).move(x, y)
        
    def move_slide(self, x):
        super(AOCharMovie, self).move(x, self.y())
    
    def set_flipped(self, flip):
        self.m_flipped = flip
    
    def play(self, p_char, p_emote, emote_prefix, scaling = SCALING_AUTO, single_frame_duration = -1):
        if not len(p_emote):
            return
            
        if p_emote[0] == "/" or p_emote[0] == "/":
            p_emote = p_emote[1:]
        elif "../../characters" in p_emote:
            a = p_emote.split("/")
            p_char = a[3]
            emote = a[4]
            emote_prefix = ""
            p_emote = emote
        
        self.pillow_frames = []
        self.pillow_frame = 0
        
        self.scaling = scaling
        
        p_char = p_char.lower()
        p_emote = p_emote.lower()

        original_path = test_path(
            AOpath+"characters/"+p_char+"/"+emote_prefix+p_emote+".gif",
            AOpath+"characters/"+p_char+"/"+emote_prefix+p_emote+".gif",
            AOpath+"characters/"+p_char+"/"+p_emote+".gif"
        )
        alt_path = AOpath+"characters/"+p_char+"/"+p_emote+".png"
        apng_path = test_path(
            AOpath+"characters/"+p_char+"/"+emote_prefix+p_emote+".apng",
            AOpath+"characters/"+p_char+"/"+emote_prefix+"/"+p_emote+".apng",
            AOpath+"characters/"+p_char+"/"+p_emote+".apng"
        )
        webp_path = test_path(
            AOpath+"characters/"+p_char+"/"+emote_prefix+p_emote+".webp",
            AOpath+"characters/"+p_char+"/"+emote_prefix+"/"+p_emote+".webp",
            AOpath+"characters/"+p_char+"/"+p_emote+".webp"
        )
        placeholder_path = AO2XPpath+"themes/default/placeholder.gif"
        gif_path = ""

        if apng_path:
            gif_path = apng_path
            self.use_pillow = 1
        else:
            if ini.read_ini_bool("AO2XP.ini", "General", "download characters"):
                url = "base/characters/"+p_char.lower()+"/"+emote_prefix+p_emote.lower()+".apng"
                url = url.replace(" ", "%20")
                thread.start_new_thread(download_thread, (url, apng_path))

            if webp_path:
                gif_path = webp_path
                self.use_pillow = 2
            else:
                if ini.read_ini_bool("AO2XP.ini", "General", "download characters"):
                    url = "base/characters/"+p_char.lower()+"/"+p_emote.lower()+".webp"
                    url = url.replace(" ", "%20")
                    thread.start_new_thread(download_thread, (url, webp_path))

                if original_path:
                    gif_path = original_path
                    self.use_pillow = 0
                else:
                    if ini.read_ini_bool("AO2XP.ini", "General", "download characters"):
                        url = "base/characters/"+p_char.lower()+"/"+emote_prefix+p_emote.lower()+".gif"
                        url = url.replace(" ", "%20")
                        thread.start_new_thread(download_thread, (url, original_path))

                    if exists(alt_path):
                        gif_path = alt_path
                        self.use_pillow = 0
                    else:
                        if ini.read_ini_bool("AO2XP.ini", "General", "download characters"):
                            url = "base/characters/"+p_char.lower()+"/"+emote_prefix+p_emote.lower()+".png"
                            url = url.replace(" ", "%20")
                            thread.start_new_thread(download_thread, (url, alt_path))

                        if exists(placeholder_path):
                            gif_path = placeholder_path
                        else:
                            gif_path = ""
                        self.use_pillow = 0
                        
        if gif_path == placeholder_path or gif_path == "":
            gif_path = self.prev_gif_path
        else:
            self.prev_gif_path = gif_path

        if not self.use_pillow:
            self.m_movie.stop()
            self.m_movie.setFileName(gif_path)
            self.m_movie.start()
        elif self.use_pillow == 1: # apng
            self.pillow_frames = images.load_apng(apng_path)
            if len(self.pillow_frames) > 1:
                self.pillow_timer.start(int(self.pillow_frames[0][1] * self.pillow_speed))
            else:
                self.pillow_timer.start(int(single_frame_duration * self.pillow_speed))
                
            self.set_pillow_frame()
        elif self.use_pillow == 2: # webp
            self.pillow_frames = images.load_webp(webp_path)
            if len(self.pillow_frames) > 1:
                self.pillow_timer.start(int(self.pillow_frames[0][1] * self.pillow_speed))
            else:
                self.pillow_timer.start(int(single_frame_duration * self.pillow_speed))
                
            self.set_pillow_frame()
        
        if self.show_on_play:
            self.show()
    
    def play_pre(self, p_char, p_emote, duration, scaling = SCALING_AUTO):
        p_char = p_char.lower()
        
        gif_path = AOpath+"characters/"+p_char+"/"+p_emote+".gif"
        apng_path = AOpath+"characters/"+p_char+"/"+p_emote+".apng"
        webp_path = AOpath+"characters/"+p_char+"/"+p_emote+".webp"
   
        full_duration = duration * self.time_mod
        real_duration = 0
        
        self.play_once = False
        self.m_movie.stop()
        self.clear()

        if exists(apng_path):
            real_duration = images.get_apng_duration(apng_path)
        elif exists(webp_path):
            real_duration = images.get_webp_duration(webp_path)
        elif exists(gif_path):
            self.m_movie.setFileName(gif_path)
            self.m_movie.jumpToFrame(0)
            for n_frame in range(self.m_movie.frameCount()):
                real_duration += self.m_movie.nextFrameDelay()
                self.m_movie.jumpToFrame(n_frame + 1)
        
        percentage_modifier = 100.0
        
        if real_duration != 0 and duration != 0:
            modifier = full_duration / float(real_duration)
            percentage_modifier = 100 / modifier
            
            if percentage_modifier > 100.0 or percentage_modifier < 0.0:
                percentage_modifier = 100.0
        
        self.pillow_fullduration = full_duration
        if full_duration == 0 or full_duration >= real_duration:
            self.play_once = True
        else:
            self.play_once = False
            if full_duration >= 0:
                self.preanim_timer.start(full_duration)
        
        self.m_movie.setSpeed(int(percentage_modifier))

        self.pillow_speed = percentage_modifier / 100.
        if real_duration > 0:
            self.play(p_char, p_emote, "", scaling)
        else:
            self.play(p_char, p_emote, "", scaling, full_duration)
    
    def play_talking(self, p_char, p_emote, scaling = SCALING_AUTO):
        p_char = p_char.lower()
        gif_path = AOpath + 'characters/' + p_char + '/(b)' + p_emote + '.gif'
        
        self.m_movie.stop()
        self.clear()
        self.m_movie.setFileName(gif_path)
        self.m_movie.jumpToFrame(0)
        
        self.play_once = False
        self.m_movie.setSpeed(100)
        self.pillow_speed = 1
        self.play(p_char, p_emote, '(b)', scaling)

    def play_idle(self, p_char, p_emote, scaling = SCALING_AUTO):
        p_char = p_char.lower()
        gif_path = AOpath + 'characters/' + p_char + '/(a)' + p_emote + '.gif'
        
        self.m_movie.stop()
        self.clear()
        self.m_movie.setFileName(gif_path)
        self.m_movie.jumpToFrame(0)
        
        self.play_once = False
        self.m_movie.setSpeed(100)
        self.pillow_speed = 1
        self.play(p_char, p_emote, '(a)', scaling)

    def stop(self):
        self.m_movie.stop()
        self.preanim_timer.stop()
        self.hide()
    
    def get_transform(self, smooth_condition=True):
        if self.scaling == SCALING_PIXEL:
            return QtCore.Qt.FastTransformation
        elif self.scaling == SCALING_SMOOTH:
            return QtCore.Qt.SmoothTransformation
        elif smooth_condition:
            return QtCore.Qt.SmoothTransformation
        else:
            return QtCore.Qt.FastTransformation

    @QtCore.pyqtSlot(int)
    def frame_change(self, n_frame):
        f_img = self.m_movie.currentImage().mirrored(self.m_flipped, False)
        if not f_img.isNull():
            f_img = f_img.scaled(VIEWPORT_W, VIEWPORT_H, QtCore.Qt.KeepAspectRatioByExpanding, self.get_transform(f_img.size().height() > VIEWPORT_H))

        f_pixmap = QtGui.QPixmap.fromImage(f_img)
        self.setPixmap(f_pixmap)
        
        if self.m_movie.frameCount() - 1 == n_frame and self.play_once:
            self.preanim_timer.start(self.m_movie.nextFrameDelay())

    @QtCore.pyqtSlot()
    def pillow_frame_change(self):
        if not self.pillow_frames: return

        if len(self.pillow_frames) - 1 == self.pillow_frame:
            if self.play_once:
                self.preanim_timer.start(int(self.pillow_frames[self.pillow_frame][1] * self.pillow_speed))
            elif len(self.pillow_frames) > 1:
                self.pillow_frame = 0
                self.pillow_timer.start(int(self.pillow_frames[self.pillow_frame][1] * self.pillow_speed))
        else:
            self.pillow_frame += 1
            self.pillow_timer.start(int(self.pillow_frames[self.pillow_frame][1] * self.pillow_speed))

        self.set_pillow_frame()

    def set_pillow_frame(self):
        f_img = self.pillow_frames[self.pillow_frame][0].mirrored(self.m_flipped, False)
        if not f_img.isNull():
            f_img = f_img.scaled(VIEWPORT_W, VIEWPORT_H, QtCore.Qt.KeepAspectRatioByExpanding, self.get_transform(f_img.size().height() > VIEWPORT_H))

        f_pixmap = QtGui.QPixmap.fromImage(f_img)
        self.setPixmap(f_pixmap)

    @QtCore.pyqtSlot()
    def timer_done(self):
        self.done.emit()

class AOMovie(QtGui.QLabel):
    play_once = True
    done = QtCore.pyqtSignal()
    use_pillow = 0
    pillow_frames = []
    pillow_frame = 0
    pillow_speed = 1
    pillow_loops = 0
    xx = 0 # for restoring from screenshake
    yy = 0 # for restoring from screenshake

    def __init__(self, parent):
        QtGui.QLabel.__init__(self, parent)
        self.m_movie = QtGui.QMovie()
        self.setMovie(self.m_movie)
        self.m_movie.frameChanged.connect(self.frame_change)

        self.pillow_timer = QtCore.QTimer(self)
        self.pillow_timer.setSingleShot(True)
        self.pillow_timer.timeout.connect(self.pillow_frame_change)
        
        self.pillow_label = QtGui.QLabel(self)
        self.pillow_label.setGeometry(0, 0, VIEWPORT_W, VIEWPORT_H)
        self.pillow_label.hide()

    def move(self, x, y):
        self.xx = x
        self.yy = y
        super(AOMovie, self).move(x, y)

    def set_play_once(self, once):
        self.play_once = once
    
    def play(self, p_image, p_char=""):
        gif_path = p_image
        pillow_modes = {".gif": 0, ".apng": 1, ".webp": 2, ".png": 1}
        
        p_image = unicode(p_image)
        
        if not exists(gif_path):
            pathlist = [
                get_img_suffix(AO2XPpath+"themes/default/"+p_image+"_bubble"),
                get_img_suffix(AOpath+"characters/"+p_char+"/"+p_image),
                get_img_suffix(AOpath+"misc/default/"+p_image),
                get_img_suffix(AO2XPpath+"themes/default/"+p_image),
                #AO2XPpath+"themes/default/placeholder.gif"
                ]
            
            for f in pathlist:
                if exists(f):
                    gif_path = f
                    break
        
        if not exists(gif_path):
            return

        self.use_pillow = pillow_modes[os.path.splitext(gif_path)[1]]
        
        if not self.use_pillow:
            self.m_movie.setFileName(gif_path)
            self.m_movie.start()
        elif self.use_pillow == 1: # apng
            self.pillow_label.show()
            self.pillow_frames = images.load_apng(gif_path)
            if len(self.pillow_frames) > 1:
                self.pillow_timer.start(int(self.pillow_frames[0][1] * self.pillow_speed))
            self.set_pillow_frame()
        elif self.use_pillow == 2: # webp
            self.pillow_label.show()
            self.pillow_loops = 0
            self.pillow_frames, self.webp_loops = images.load_webp(gif_path)
            if len(self.pillow_frames) > 1:
                self.pillow_timer.start(int(self.pillow_frames[0][1] * self.pillow_speed))
            self.set_pillow_frame()

        self.show()
    
    def stop(self):
        self.pillow_frames = []
        self.pillow_frame = 0
        self.pillow_timer.stop()
        self.m_movie.stop()
        self.pillow_label.clear()
        self.pillow_label.hide()
        self.hide()
    
    @QtCore.pyqtSlot(int)
    def frame_change(self, n_frame):
        if n_frame == self.m_movie.frameCount() - 1 and self.play_once:
            delay(self.m_movie.nextFrameDelay())
            self.stop()
            self.done.emit()

    @QtCore.pyqtSlot()
    def pillow_frame_change(self):
        if not self.pillow_frames: return

        if len(self.pillow_frames)-1 == self.pillow_frame:
            if self.play_once or (self.use_pillow == 2 and self.pillow_loops+1 == self.webp_loops):
                delay(int(self.pillow_frames[self.pillow_frame][1] * self.pillow_speed))
                self.stop()
                self.done.emit()
            elif len(self.pillow_frames) > 1: # loop
                self.pillow_loops += 1
                self.pillow_frame = 0
                self.pillow_timer.start(int(self.pillow_frames[self.pillow_frame][1] * self.pillow_speed))
        elif len(self.pillow_frames) > 1:
            self.pillow_frame += 1
            self.pillow_timer.start(int(self.pillow_frames[self.pillow_frame][1] * self.pillow_speed))

        self.set_pillow_frame()

    def set_pillow_frame(self):
        if not self.pillow_frames: return

        f_img = self.pillow_frames[self.pillow_frame][0]
        if not f_img.isNull() and (f_img.size().width() != VIEWPORT_W or f_img.size().height() != VIEWPORT_H):
            f_img = f_img.scaled(VIEWPORT_W, VIEWPORT_H, QtCore.Qt.KeepAspectRatioByExpanding, QtCore.Qt.FastTransformation)

        f_pixmap = QtGui.QPixmap.fromImage(f_img)
        self.pillow_label.setPixmap(f_pixmap)

class ZoomLines(QtGui.QLabel):
    def __init__(self, parent):
        super(ZoomLines, self).__init__(parent)
        self.resize(VIEWPORT_W, VIEWPORT_H)
        self.setScaledContents(True)
        self.movie = QtGui.QMovie()
        self.movie.frameChanged.connect(self.frame_change)

    def frame_change(self):
        img = self.movie.currentImage()
        self.setPixmap(QtGui.QPixmap.fromImage(img))

    def set_zoom(self, on, dir=0):
        self.movie.stop()
        if not on:
            self.hide()
            return
        self.show()
        if dir == 0:
            self.movie.setFileName(AO2XPpath + 'themes/default/defense_speedlines.gif')
        else:
            self.movie.setFileName(AO2XPpath + 'themes/default/prosecution_speedlines.gif')
        self.movie.start()

class WTCEView(QtGui.QLabel):
    def __init__(self, parent):
        super(WTCEView, self).__init__(parent)
        self.movie = QtGui.QMovie()
        self.movie.frameChanged.connect(self.frame_change)
        self.finalframe_timer = QtCore.QTimer()
        self.finalframe_timer.setSingleShot(False)
        self.finalframe_timer.timeout.connect(self.finished)
        self.resize(VIEWPORT_W, VIEWPORT_H)

    def frame_change(self, frame):
        if self.movie.state() != QtGui.QMovie.Running:
            return
        img = self.movie.currentImage()
        pixmap = QtGui.QPixmap.fromImage(img)
        if not pixmap.isNull():
            self.setPixmap(pixmap.scaled(VIEWPORT_W, VIEWPORT_H, QtCore.Qt.KeepAspectRatioByExpanding, QtCore.Qt.FastTransformation))
        if self.movie.currentFrameNumber() == self.movie.frameCount() - 1:
            self.finalframe_timer.start(self.movie.nextFrameDelay())

    def finished(self):
        self.finalframe_timer.stop()
        self.movie.stop()
        self.hide()

    def show_WTCE(self, wtce, variant=0):
        self.finished()
        if wtce == 'testimony1':
            self.movie.setFileName(AO2XPpath + 'themes/default/witnesstestimony.gif')
        elif wtce == 'testimony2':
            self.movie.setFileName(AO2XPpath + 'themes/default/crossexamination.gif')
        elif wtce == "judgeruling":
            if variant == 0:
                self.movie.setFileName(AO2XPpath + 'themes/default/notguilty.gif')
            elif variant == 1:
                self.movie.setFileName(AO2XPpath + 'themes/default/guilty.gif')
        else:
            return
        self.show()
        self.movie.start()

class EditEvidenceDialog(QtGui.QDialog):
    def __init__(self, gamegui):
        super(EditEvidenceDialog, self).__init__()
        self.gamegui = gamegui
        self.set_title()
        self.resize(512, 384)
        self.setModal(True)

        self.eviname = QtGui.QLineEdit(self)
        self.eviname.setGeometry(8, 8, 410, 24)
        self.evidesc = QtGui.QTextEdit(self)
        self.evidesc.setGeometry(8, 192 - 105, 496, 255)
        self.evidesc.setAcceptRichText(False)
        self.evipicture = QtGui.QLabel(self)
        self.filename = 'empty.png'
        evipic = QtGui.QPixmap(AOpath + 'evidence/empty.png')
        self.evipicture.setPixmap(evipic)
        self.evipicture.move(434, 8)
        self.evipicture.show()
        
        self.save = QtGui.QPushButton(self)
        self.save.setText('Save')
        self.save.clicked.connect(self.on_save)
        self.save.move(256 - self.save.size().width() - 8, 384 - self.save.size().height() - 2)
        self.cancel = QtGui.QPushButton(self)
        self.cancel.setText('Cancel')
        self.cancel.clicked.connect(self.on_cancel)
        self.cancel.move(264 + 16, 384 - self.cancel.size().height() - 2)
        self.choosepic = QtGui.QComboBox(self)
        self.choosepic.setGeometry(self.eviname.x() + self.eviname.size().width() - 128 - 84, self.eviname.y() + 70 - 32, 128, 24)
        self.filenames = []
        self.browse = QtGui.QPushButton(self)
        self.browse.setText('Browse')
        self.browse.clicked.connect(self.on_browse)
        self.browse.move(self.choosepic.x() + self.choosepic.width() + 8, self.choosepic.y())
        
        files = os.listdir(AOpath + 'evidence')
        fileslength = len(files)
        i = 0
        while i < fileslength:
            if not files[i].endswith('.png'):
                del files[i]
                fileslength = len(files)
                i -= 1
            i += 1

        for i in range(len(files)):
            if files[i].endswith('.png'):
                self.choosepic.addItem(files[i].strip('.png'))
                self.filenames.append(files[i])
                if files[i].lower() == 'empty.png':
                    self.emptyfile = i

        self.editing = False
        self.choosepic.currentIndexChanged.connect(self.choose_pic_change)
        self.choosepic.setCurrentIndex(i)

    def choose_pic_change(self, ind):
        self.filename = self.filenames[ind]
        if exists(AOpath + 'evidence/' + self.filename):
            self.evipicture.setPixmap(QtGui.QPixmap(AOpath + 'evidence/' + self.filename))
        else:
            self.evipicture.setPixmap(QtGui.QPixmap(AO2XPpath + 'themes/default/evidence_selected.png'))

    def on_save(self):
        name = encode_ao_str(self.eviname.text())
        desc = encode_ao_str(self.evidesc.toPlainText())

        if not self.gamegui.privateinv:
            if self.editing:
                self.gamegui.tcp.send('EE#' + str(self.edit_ind) + '#' + name + '#' + desc + '#' + self.filename + '#%')
            else:
                self.gamegui.tcp.send('PE#' + name + '#' + desc + '#' + self.filename + '#%')
        else:
            if self.editing:
                self.gamegui.privateevidence[self.gamegui.selectedevi] = [unicode(name), unicode(desc), unicode(self.filename)]
                self.gamegui.privatedropdown.setItemText(self.gamegui.selectedevi, name)
                
                evi = self.gamegui.privateevidence[self.gamegui.selectedevi]
                self.gamegui.evidencedesc.setText(evi[1])
                self.gamegui.setEvidenceImg(self.gamegui.evidenceimage, evi[2])
            else:
                self.gamegui.privateevidence.append([name, desc, self.filename])
                self.gamegui.privatedropdown.addItem(name)
            self.gamegui.onExportEvidence(True)

        self.eviname.setText('')
        self.evidesc.setText('')
        evipic = QtGui.QPixmap(AOpath + 'evidence/empty.png')
        self.evipicture.setPixmap(evipic)
        self.filename = 'empty.png'
        self.editing = False
        self.set_title()
        self.choosepic.setCurrentIndex(self.emptyfile)
        self.hide()

    def on_cancel(self):
        self.eviname.setText('')
        self.evidesc.setText('')
        evipic = QtGui.QPixmap(AOpath + 'evidence/empty.png')
        self.evipicture.setPixmap(evipic)
        self.filename = 'empty.png'
        self.editing = False
        self.set_title()
        self.choosepic.setCurrentIndex(self.emptyfile)
        self.hide()
        
    def on_browse(self):
        path = str(QtGui.QFileDialog.getOpenFileName(self, "Select an image", AOpath + 'evidence', "Images (*.png)"))
        if path:
            if not "/evidence/" in path.lower():
                QtGui.QMessageBox.warning(self, 'Wrong directory', 'Please select a file from the "evidence" directory.')
                self.on_browse()
                return
            
            file = basename(path)
            if file.lower().endswith('.png'):
                ind = os.listdir(AOpath + 'evidence').index(file)
                self.choosepic.setCurrentIndex(ind)
            else:
                QtGui.QMessageBox.warning(self, 'Not a valid file', 'Please select a PNG image.')
                self.on_browse()

    def edit_evidence(self, ind):
        self.editing = True
        self.edit_ind = ind
        evidence = self.gamegui.privateevidence if self.gamegui.privateinv else self.gamegui.evidence
        
        if evidence[ind][2] not in self.filenames:
            self.filenames.append(evidence[ind][2])
            self.choosepic.addItem(evidence[ind][2].split('.')[0])
        self.choosepic.setCurrentIndex(self.filenames.index(evidence[ind][2]))
        self.eviname.setText(evidence[ind][0])
        self.evidesc.setText(evidence[ind][1])
        self.setWindowTitle("Edit evidence" if not self.gamegui.privateinv else "Edit evidence in private inventory")
        self.show()
        
    def set_title(self):
        self.setWindowTitle('Add evidence' if not self.gamegui.privateinv else "Add evidence to private inventory")
  
class TCPThread(QtCore.QThread):
    connectionError = QtCore.pyqtSignal(str, str, str)
    MS_Chat = QtCore.pyqtSignal(list)
    newChar = QtCore.pyqtSignal(str)
    newBackground = QtCore.pyqtSignal(str, bool)
    IC_Log = QtCore.pyqtSignal(str)
    OOC_Log = QtCore.pyqtSignal(str)
    charSlots = QtCore.pyqtSignal()
    showCharSelect = QtCore.pyqtSignal()
    allEvidence = QtCore.pyqtSignal(list)
    rainbowColor = QtCore.pyqtSignal(str)
    updatePlayerList = QtCore.pyqtSignal(str, int, int, str)
    timerUpdate = QtCore.pyqtSignal(int, int, int)
    
    send_attempts = 0
    max_attempts = 5
    stop_now = False
    
    def __init__(self, parent):
        super(TCPThread, self).__init__(parent)
        self.parent = parent
        
    def run(self):
        pingtimer = 150
        rainbow = 0
        sendtick = 0
        tempdata = ""
        color = QtGui.QColor()
        color.setHsv(rainbow, 255, 255)
        while True:
            if self.stop_now:
                self.parent.tcp.close()
                self.parent.tcp = None
                self.quit()
                return
        
            if self.parent.disconnectnow:
                self.parent.disconnectCommon()
                self.quit()
                return
            pingtimer -= 1

            if pingtimer == 0:
                pingbefore = time.time()
                self.parent.tcp.send('CH#%')
                pingtimer = 150
            
            if self.parent.m_chatmessage[TEXT_COLOR] == str(C_RAINBOW):
                color.setHsv(rainbow, 255, 255)
                rainbow += 5
                if rainbow > 255:
                    rainbow = 0
                #self.parent.text.setStyleSheet('color: rgb(' + str(color.red()) + ', ' + str(color.green()) + ', ' + str(color.blue()) + ')')
                self.rainbowColor.emit('background-color: rgba(0, 0, 0, 0); color: rgb(' + str(color.red()) + ', ' + str(color.green()) + ', ' + str(color.blue()) + ')')
                
            if sendtick:
                sendtick -= 1
            if self.parent.msgqueue and not sendtick:
                self.parent.tcp.send(self.parent.msgqueue[0])
                sendtick = 4

            error, total = self.parent.tcp.recv()
            if error == -2:
                # if the message can't be sent, discard it
                if sendtick == 4:
                    self.send_attempts += 1
                    if self.send_attempts >= self.max_attempts:
                        self.send_attempts = 0
                        #print "[warning] message discarded"
                        del self.parent.msgqueue[0]
                        self.parent.msgqueue_list.takeItem(0)
                continue
            elif error == -1:
                self.parent.emit(QtCore.SIGNAL('showMessage(QString, QString, QString)'), 'critical', 'Connection lost', "%s connection to server lost." % ("WebSocket" if self.parent.tcp.isWS else "TCP"))
                self.parent.willDisconnect = True
                self.quit()
                return
            elif error == -3:
                self.parent.emit(QtCore.SIGNAL('showMessage(QString, QString, QString)'), 'critical', 'Connection lost', "There was a critical connection failure. Please check your internet connection.\n\nDetails: %s." % total)
                self.parent.willDisconnect = True
                self.quit()
                return
            else:
                self.send_attempts = 0
                
            packets.handle_packets(self, total)
    
    def stop(self):
        self.stop_now = True

class GUI(QtGui.QWidget):
    gamewindow = None
    sound = None
    music = None
    next_character_is_not_special = False
    message_is_centered = False
    current_display_speed = 3
    message_display_speed = (30, 40, 50, 60, 75, 100, 120)
    entire_message_is_blue = False
    inline_color_stack = [] 
    inline_blue_depth = 0
    other_charid = -1
    offset_with_pair = 0
    tick_pos = 0
    blip_pos = 0
    blip_rate = 1
    time_mod = 40
    blip = "male"
    blipsnd = None
    chatmessage_size = 33
    m_chatmessage = []
    blank_blip = False
    chatmessage_is_empty = False
    is_additive = False
    additive_char = -1
    anim_state = 3
    text_state = 2
    objection_state = 0
    text_color = 0
    
    charini = ConfigParser()
    chatmsg = ''
    charid = -1
    login = False
    privateinv = False
    scaling = [SCALING_AUTO, SCALING_AUTO]
   
    wtcesignal = QtCore.pyqtSignal(str, int)
    healthbars = QtCore.pyqtSignal(int, int)
    gotPing = QtCore.pyqtSignal(int)

    def __init__(self, parent=None):
        super(GUI, self).__init__(parent)
        self.gamewindow = parent
        
        self.gotPing.connect(self.set_ping)
        
        for i in range(self.chatmessage_size):
            self.m_chatmessage.append("")
            
        self.chat_tick_timer = QtCore.QTimer(self)
        self.chat_tick_timer.timeout.connect(self.chat_tick)
        
        self.sfx_delay_timer = QtCore.QTimer(self)
        self.sfx_delay_timer.setSingleShot(True)
        self.sfx_delay_timer.timeout.connect(self.play_sfx)
        
        self.inbox_timer = QtCore.QTimer(self)
        self.inbox_timer.setSingleShot(True)
        self.inbox_timer.timeout.connect(self.inbox_timer_timeout)
        
        self.modcall = None
        
        self.healthbars.connect(self.netmsg_hp)
        self.disconnectnow = False
        self.swapping = False
        self.iniswapindex = 0
        self.background = 'default'
        
        self.viewport = QtGui.QWidget(self)
        self.viewport.resize(VIEWPORT_W, VIEWPORT_H)
        
        self.court = QtGui.QLabel(self.viewport)
        self.court.resize(VIEWPORT_W, VIEWPORT_H)
        
        self.slide_bg = QtGui.QLabel(self.viewport)
        self.slide_bg_animation = QtCore.QPropertyAnimation(self.slide_bg, "geometry")
        self.slide_bg_animation.finished.connect(self.slide_done)
        self.slide_bg_animation.valueChanged.connect(self.slide_changed)
        self.slide_bg.hide()
        
        self.zoom = ZoomLines(self.viewport)
        
        self.char = AOCharMovie(self.viewport)
        self.char.done.connect(self.preanim_done)
        self.sidechar = AOCharMovie(self.viewport)
        self.sidechar.hide()
        
        self.slide_last_wit = []
        self.slide_last_pos = None
        self.slide_witness = AOCharMovie(self.viewport)
        self.slide_witness.show_on_play = False
        self.slide_witness.hide()
        self.slide_speaker = AOCharMovie(self.viewport)
        self.slide_speaker.hide()
        self.slide_speaker.show_on_play = False
        
        self.slide_overlay = QtGui.QLabel(self.viewport)
        self.slide_overlay.hide()
        
        self.bench = QtGui.QLabel(self.viewport)
        self.bench.resize(VIEWPORT_W, VIEWPORT_H)

        self.effectview = AOMovie(self.viewport)
        self.effectview.resize(VIEWPORT_W, VIEWPORT_H)
        self.effectview.setScaledContents(True)
        
        font_db = QtGui.QFontDatabase()
        font_db.addApplicationFont(AO2XPpath + 'font/Igiari.ttf')
        font_db.addApplicationFont(AO2XPpath + 'font/Ace_Name_Regular.ttf')
        name_font = QtGui.QFont("Ace Name")
        name_font.setPointSize(12)
        ao2text_font = QtGui.QFont("Igiari")
        ao2text_font.setPointSize(24)

        self.chatbox = QtGui.QLabel(self.viewport)
        chatbox = QtGui.QPixmap(AO2XPpath + 'themes/AceAttorney2x/chatbig.png')
        #chatbox = QtGui.QPixmap(AO2XPpath + 'themes/default/chatmed.png')
        self.chatboxheight = chatbox.size().height()
        self.chatbox.setPixmap(chatbox)
        self.chatbox.move(0, VIEWPORT_H - self.chatboxheight)
        
        self.text = QtGui.QTextEdit(self.chatbox)
        self.text.setFrameStyle(QtGui.QFrame.NoFrame)
        self.text.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.text.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.text.setReadOnly(True)
        self.text.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
        self.text.setFont(ao2text_font)
        
        self.ao2text = QtGui.QTextEdit(self.chatbox)
        self.ao2text.setFrameStyle(QtGui.QFrame.NoFrame)
        self.ao2text.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.ao2text.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.ao2text.setReadOnly(True)
        self.ao2text.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
        self.ao2text.setFont(ao2text_font)
        
        self.name = QtGui.QLabel(self.chatbox)
        self.name.setFont(name_font)
        
        self.presentedevi = QtGui.QLabel(self)
        self.presentedevi.setGeometry(16, 16, 140, 140)
        self.presentedevi.hide()
        
        self.wtceview = WTCEView(self)
        self.wtcesignal.connect(self.wtceview.show_WTCE)

        self.objectionview = AOMovie(self.viewport)
        self.objectionview.resize(VIEWPORT_W, VIEWPORT_H)
        self.objectionview.setScaledContents(True)
        self.objectionview.done.connect(self.objection_done)

        self.whiteflashlab = QtGui.QLabel(self.viewport)
        self.whiteflashlab.setPixmap(QtGui.QPixmap(AO2XPpath + 'themes/default/realizationflash.png'))
        self.whiteflashlab.setGeometry(0, 0, VIEWPORT_W, VIEWPORT_H)
        self.whiteflashlab.setScaledContents(True)
        self.whiteflashlab.hide()
        self.whiteflash = QtCore.QTimer()
        self.whiteflash.setSingleShot(False)
        self.whiteflash.timeout.connect(partial(self.setWhiteFlash, False))

        self.screenshake = QtCore.QTimer()
        self.screenshake.timeout.connect(self.screen_shake_tick)
        self.shakes_remaining = 0

        self.onscreen_timer_labels = []
        self.onscreen_timer_times = [0, 0, 0, 0, 0]
        self.onscreen_timer_paused = [True, True, True, True, True]
        self.onscreen_timer = QtCore.QTimer(self)
        self.onscreen_timer.timeout.connect(self.update_timers)
        self.timer_tick = 1000
        
        for i in range(len(self.onscreen_timer_times)):
            label = QtGui.QLabel(self)
            label.setFont(name_font)
            label.hide()
            label.setText("00:00:00")
            label.resize(VIEWPORT_W, label.sizeHint().height())
            label.setStyleSheet('color: white;')
            self.onscreen_timer_labels.append(label)
        
        self.onscreen_timer_labels[0].setAlignment(QtCore.Qt.AlignCenter)
        self.onscreen_timer_labels[3].setAlignment(QtCore.Qt.AlignRight)
        self.onscreen_timer_labels[4].setAlignment(QtCore.Qt.AlignRight)
        self.onscreen_timer_labels[2].move(self.onscreen_timer_labels[1].x(), self.onscreen_timer_labels[1].y() + self.onscreen_timer_labels[1].size().height() + 4)
        self.onscreen_timer_labels[4].move(self.onscreen_timer_labels[3].x(), self.onscreen_timer_labels[3].y() + self.onscreen_timer_labels[3].size().height() + 4)
            
        self.ooclog = ChatLogs(self, 1)
        self.ooclog.setReadOnly(True)
        self.ooclog.textChanged.connect(self.ooclog_update)
        
        self.oocnameinput = QtGui.QLineEdit(self)
        self.oocnameinput.setPlaceholderText('Enter a name...')
        
        self.oocinput = QtGui.QLineEdit(self)
        self.oocinput.setPlaceholderText('Server chat/OOC chat...')
        self.oocinput.returnPressed.connect(self.onOOCreturn)
        
        self.ooclogin = QtGui.QPushButton("Lo&gin", self)
        self.ooclogin.clicked.connect(self.on_ooc_login)
        
        self.gametabs = QtGui.QTabWidget(self)
        self.gametab_log = QtGui.QWidget() # the IC chat log
        self.gametab_evidence = QtGui.QWidget() # court record
        self.gametab_msgqueue = QtGui.QWidget() # IC messages pending to be sent
        self.gametab_iniswap = QtGui.QWidget() # self explanatory
        self.gametab_mute = QtGui.QWidget() # mute a player
        self.gametab_pair = QtGui.QWidget() # AO2 pair
        self.gametab_misc = QtGui.QWidget() # ao2xp misc/fun stuff
        self.gametab_players = QtGui.QWidget() # client list
        self.gametab_music = QtGui.QWidget() # music list
        
        self.musicareatabs = QtGui.QTabWidget(self)
        self.musicitems = QtGui.QListWidget(self.gametab_music)
        self.musicitems.itemDoubleClicked.connect(self.onMusicClick)
        self.musicsearch = QtGui.QLineEdit(self.gametab_music)
        self.musicsearch.setPlaceholderText("Search...")
        self.musicsearch.textChanged.connect(self.onMusicSearch)
        
        self.areaitems = QtGui.QListWidget()
        self.areaitems.itemDoubleClicked.connect(self.onAreaClick)
        
        self.demoitems = demo.get_demo_treeview()
        self.demoitems.doubleClicked.connect(self.on_demo_click)
        
        self.iclog = ChatLogs(self.gametab_log, 0, self.ooclog.logfile)
        self.iclog.setReadOnly(True)
        self.iclog.textChanged.connect(self.icLogChanged)
        
        self.evidencedropdown = QtGui.QComboBox(self.gametab_evidence)
        self.evidencedropdown.currentIndexChanged.connect(self.changeGlobalEvidence)
        self.privatedropdown = QtGui.QComboBox(self.gametab_evidence)
        self.privatedropdown.currentIndexChanged.connect(self.changePrivateEvidence)
        
        self.evidencedesc = QtGui.QTextEdit(self.gametab_evidence)
        self.evidencedesc.setReadOnly(True)
        self.evidenceimage = QtGui.QLabel(self.gametab_evidence)
        self.evidenceimage.setPixmap(QtGui.QPixmap(AOpath + 'evidence/empty.png'))
        self.evidenceimage.show()
        self.evidenceadd = QtGui.QPushButton(QtGui.QIcon(AO2XPpath + "icons/" + "add.png"), "", self.gametab_evidence)
        self.evidenceadd.setToolTip('Add new evidence')
        self.evidenceadd.clicked.connect(self.onAddEvidence)
        self.evidenceedit = QtGui.QPushButton(QtGui.QIcon(AO2XPpath + "icons/" + "edit.png"), "", self.gametab_evidence)
        self.evidenceedit.setToolTip('Edit selected evidence')
        self.evidenceedit.clicked.connect(self.onEditEvidence)
        self.evidencedelete = QtGui.QPushButton(QtGui.QIcon(AO2XPpath + "icons/" + "delete.png"), "", self.gametab_evidence)
        self.evidencedelete.setToolTip('Delete selected evidence')
        self.evidencedelete.clicked.connect(self.onDeleteEvidence)
        self.evidenceload = QtGui.QPushButton(QtGui.QIcon(AO2XPpath + "icons/" + "folder.png"), "", self.gametab_evidence)
        self.evidenceload.setToolTip('Import all evidence from a file')
        self.evidenceload.clicked.connect(self.onImportEvidence)
        self.evidencesave = QtGui.QPushButton(QtGui.QIcon(AO2XPpath + "icons/" + "disk.png"), "", self.gametab_evidence)
        self.evidencesave.setToolTip('Export all evidence to a file')
        self.evidencesave.clicked.connect(self.onExportEvidence)
        
        self.evidencemoveprivate = QtGui.QPushButton(QtGui.QIcon(AO2XPpath + "icons/" + "arrow_right.png"), "", self.gametab_evidence)
        self.evidencemoveprivate.setToolTip('Transfer selected evidence to the private inventory')
        self.evidencemoveprivate.clicked.connect(self.onTransferEvidence)
        self.evidencemoveallprivate = QtGui.QPushButton(QtGui.QIcon(AO2XPpath + "icons/" + "arrow_right2.png"), "", self.gametab_evidence)
        self.evidencemoveallprivate.setToolTip('Transfer all evidence to the private inventory')
        self.evidencemoveallprivate.clicked.connect(self.onTransferAllEvidence)
        
        self.evidencemoveglobal = QtGui.QPushButton(QtGui.QIcon(AO2XPpath + "icons/" + "arrow_left.png"), "", self.gametab_evidence)
        self.evidencemoveglobal.setToolTip('Transfer selected evidence to the global inventory')
        self.evidencemoveglobal.clicked.connect(self.onTransferEvidence)
        self.evidencemoveallglobal = QtGui.QPushButton(QtGui.QIcon(AO2XPpath + "icons/" + "arrow_left2.png"), "", self.gametab_evidence)
        self.evidencemoveallglobal.setToolTip('Transfer all evidence to the global inventory')
        self.evidencemoveallglobal.clicked.connect(self.onTransferAllEvidence)
        
        self.evidenceswitchprivate = QtGui.QPushButton(QtGui.QIcon(AO2XPpath + "icons/" + "world.png"), "", self.gametab_evidence)
        self.evidenceswitchprivate.setToolTip('Switch to the private inventory')
        self.evidenceswitchprivate.clicked.connect(self.onSwitchInventory)
        self.evidenceswitchglobal = QtGui.QPushButton(QtGui.QIcon(AO2XPpath + "icons/" + "briefcase.png"), "", self.gametab_evidence)
        self.evidenceswitchglobal.setToolTip('Switch to the global inventory')
        self.evidenceswitchglobal.clicked.connect(self.onSwitchInventory)
        
        self.evidencepresent = buttons.PresentButton(self, self.gametab_evidence)
        
        self.privatedropdown.hide()
        self.evidencemoveglobal.hide()
        self.evidencemoveallglobal.hide()
        self.evidenceswitchglobal.hide()
        
        self.msgqueue_list = QtGui.QListWidget(self.gametab_msgqueue)
        self.msgqueue_list.itemClicked.connect(self.onClicked_msgqueue)
        self.remove_queue = QtGui.QPushButton(self.gametab_msgqueue)
        self.remove_queue.setText('Delete')
        self.remove_queue.clicked.connect(self.onClicked_remove_queue)
        self.clear_queue = QtGui.QPushButton(self.gametab_msgqueue)
        self.clear_queue.setText('Clear')
        self.clear_queue.clicked.connect(self.onClicked_clear_queue)
        
        self.player_list = QtGui.QListWidget(self.gametab_players)
        self.player_list.itemClicked.connect(self.onClicked_playerList)
        self.player_pair = QtGui.QPushButton(self.gametab_players)
        self.player_pair.setText('Pair')
        self.player_pair.clicked.connect(self.onClicked_playerPair)
        self.player_kick = QtGui.QPushButton(self.gametab_players)
        self.player_kick.setText('Kick')
        self.player_kick.clicked.connect(self.onClicked_playerKick)
        self.player_kick.setDisabled(True)
        self.player_ban = QtGui.QPushButton(self.gametab_players)
        self.player_ban.setText('Ban')
        self.player_ban.clicked.connect(self.onClicked_playerBan)
        self.player_ban.setDisabled(True)
        
        self.unmutedlist = QtGui.QListWidget(self.gametab_mute)
        self.mutedlist = QtGui.QListWidget(self.gametab_mute)
        self.mutebtn = QtGui.QPushButton(self.gametab_mute)
        self.unmutebtn = QtGui.QPushButton(self.gametab_mute)
        self.notmutedlabel = QtGui.QLabel(self.gametab_mute)
        self.mutedlabel = QtGui.QLabel(self.gametab_mute)
        self.notmutedlabel.setText('Not muted')
        self.mutedlabel.setText('Muted')
        self.mutebtn.setText('>>')
        self.unmutebtn.setText('<<')
        self.mutebtn.clicked.connect(self.onMuteClick)
        self.unmutebtn.clicked.connect(self.onUnmuteClick)
        self.mutedlist.itemClicked.connect(self.changeMuteIndex)
        self.unmutedlist.itemClicked.connect(self.changeUnmuteIndex)
        
        self.iniswaplist = QtGui.QComboBox(self.gametab_iniswap)
        self.iniswaplist.currentIndexChanged.connect(self.iniswap_index_change)
        self.iniswapconfirm = QtGui.QPushButton(self.gametab_iniswap)
        self.iniswapconfirm.setText('Swap')
        self.iniswapconfirm.clicked.connect(self.iniswap_confirm)
        self.iniswapreset = QtGui.QPushButton(self.gametab_iniswap)
        self.iniswapreset.setText('Reset')
        self.iniswapreset.clicked.connect(self.resetIniSwap)
        self.iniswapinfo = QtGui.QLabel(self.gametab_iniswap)
        self.iniswapinfo.setText('Not swapped')
        self.iniswaprefresh = QtGui.QPushButton(self.gametab_iniswap)
        self.iniswaprefresh.setText('Refresh characters')
        self.iniswaprefresh.clicked.connect(self.loadSwapCharacters)
        
        self.paircheckbox = QtGui.QCheckBox(self.gametab_pair)
        self.paircheckbox.setChecked(False)
        self.pairdropdown = QtGui.QComboBox(self.gametab_pair)
        self.pairoffset = QtGui.QSlider(QtCore.Qt.Horizontal, self.gametab_pair)
        self.pairoffset.setRange(-100, 100)
        self.pairoffset.setValue(0)
        self.pairoffset_l = QtGui.QLabel("X offset", self.gametab_pair)
        self.ypairoffset = QtGui.QSlider(QtCore.Qt.Vertical, self.gametab_pair)
        self.ypairoffset.setRange(-100, 100)
        self.ypairoffset.setValue(0)
        self.ypairoffset_l = QtGui.QLabel("Y offset", self.gametab_pair)
        self.pairoffsetreset = QtGui.QPushButton("Reset", self.gametab_pair)
        self.pairoffsetreset.clicked.connect(self.reset_offsets)
        self.pair_order = QtGui.QComboBox(self.gametab_pair)
        self.pair_order.addItem("Front")
        self.pair_order.addItem("Behind")
        self.pair_order_l = QtGui.QLabel("Pairing order", self.gametab_pair)
        
        self.misc_layout = QtGui.QVBoxLayout(self.gametab_misc)
        self.misc_layout.setAlignment(QtCore.Qt.AlignTop)
        self.mocktext = QtGui.QCheckBox()
        self.mocktext.setChecked(False)
        self.mocktext.setText(mock_str("mock text"))
        self.spacebartext = QtGui.QCheckBox()
        self.spacebartext.setChecked(False)
        self.spacebartext.setText("S p a c i n g")
        self.autocaps = QtGui.QCheckBox()
        self.autocaps.setChecked(False)
        self.autocaps.setText("Automatic caps and period")
        self.misc_layout.addWidget(self.mocktext)
        self.misc_layout.addWidget(self.spacebartext)
        self.misc_layout.addWidget(self.autocaps)
        
        self.gametabs.addTab(self.gametab_log, '&Log')
        self.gametabs.addTab(self.gametab_evidence, '&Evidence')
        self.gametabs.addTab(self.gametab_mute, 'Mu&te')
        self.gametabs.addTab(self.gametab_iniswap, '&INI swap')
        self.gametabs.addTab(self.gametab_pair, 'Pai&r')
        self.gametabs.addTab(self.gametab_misc, 'E&xtras')
        self.gametabs.addTab(self.gametab_msgqueue, '&Queue')
        
        self.musicareatabs.addTab(self.gametab_music, "&Music")
        self.musicareatabs.addTab(self.areaitems, "&Areas")
        self.musicareatabs.addTab(self.gametab_players, 'Pla&yers')
        self.musicareatabs.addTab(self.demoitems, "Demos")
        
        self.icchatinput = QtGui.QLineEdit(self)
        self.icchatinput.returnPressed.connect(self.onICreturn)
        self.icchatinput.setPlaceholderText('Game chat')
        
        self.emotedropdown = QtGui.QComboBox(self)
        self.emotedropdown.currentIndexChanged.connect(partial(self.changeEmote, True))
        self.emotedropdown.setToolTip('Select an emotion for your character')
        
        self.colordropdown = QtGui.QComboBox(self)
        self.colordropdown.currentIndexChanged.connect(self.setChatColor)
        self.colordropdown.setToolTip('Change the color of your message')
        
        self.posdropdown = QtGui.QComboBox(self)
        self.default_positions = ["def", "pro", "wit", "hld", "hlp", "jud", "jur", "sea"]
        self.posdropdown.addItems(self.default_positions)
        self.posdropdown.currentIndexChanged.connect(self.set_position)
        self.posdropdown.setToolTip('Select your position in the courtroom')
        
        self.flipbutton = QtGui.QCheckBox(self)
        self.flipbutton.stateChanged.connect(self.changeFlipCheck)
        self.flipbutton.setText('&Flip')
        self.flipbutton.resize(self.flipbutton.sizeHint())
        self.flipbutton.setToolTip("Mirror your character horizontally")
        
        self.sfxbutton = QtGui.QCheckBox(self)
        self.sfxbutton.setChecked(True)
        self.sfxbutton.stateChanged.connect(self.changeSfxCheck)
        self.sfxbutton.setText('&Pre-anim')
        self.sfxbutton.setToolTip("Play a character-specific animation before the next message")
        
        self.nointerruptbtn = QtGui.QCheckBox(self)
        self.nointerruptbtn.setChecked(False)
        self.nointerruptbtn.stateChanged.connect(self.icchat_focus)
        self.nointerruptbtn.setText('&No interrupt')
        self.nointerruptbtn.setToolTip("Show the next message immediately, ignoring animations")
        
        # AO 2.8
        self.additivebtn = QtGui.QCheckBox(self)
        self.additivebtn.setChecked(False)
        self.additivebtn.setText('Additi&ve')
        self.additivebtn.resize(self.additivebtn.sizeHint())
        self.additivebtn.clicked.connect(self.icchat_focus)
        self.additivebtn.setToolTip('Append the next message to the previous one, without a new textbox')

        self.deskbtn = QtGui.QCheckBox(self)
        self.deskbtn.setChecked(True)
        self.deskbtn.setText('&Desk')
        self.deskbtn.stateChanged.connect(self.icchat_focus)
        self.deskbtn.resize(self.nointerruptbtn.sizeHint())
        self.deskbtn.setToolTip('Show or hide the desk in front of your character')
        
        self.slidebutton = QtGui.QCheckBox(self)
        self.slidebutton.stateChanged.connect(self.icchat_focus)
        self.slidebutton.setText('&Slide')
        self.slidebutton.resize(self.slidebutton.sizeHint())
        self.slidebutton.setToolTip("Tell clients to play courtroom slide animations for your message")

        self.effectdropdown = QtGui.QComboBox(self)
        self.effectdropdown.currentIndexChanged.connect(self.icchat_focus)
        self.effectdropdown.setToolTip('Show this effect on your next message')
        
        self.callmodbtn = QtGui.QPushButton(self)
        self.callmodbtn.setText('Call mod')
        self.callmodbtn.clicked.connect(self.onClick_callMod)
        
        self.settingsbtn = QtGui.QPushButton("Settings", self)
        self.settingsbtn.clicked.connect(self.gamewindow.showSettings)

        self.changechar = QtGui.QPushButton(self)
        self.changechar.clicked.connect(self.onClick_changeChar)
        
        spacing = 1
        x_mod_count = y_mod_count = 0
        left, top = (10 + 516, 218+190-24)
        width, height = (288, 98)
        columns = (width - 40) / (spacing + 40) + 1
        rows = (height - 40) / (spacing + 40) + 1
        self.max_emotes_on_page = columns * rows
        self.emotebuttons = []
        for i in range(self.max_emotes_on_page):
            x_pos = (40 + spacing) * x_mod_count
            y_pos = (40 + spacing) * y_mod_count
            button = buttons.EmoteButton(self, left + x_pos, top + y_pos, i)
            button.clicked.connect(self.icchat_focus)
            self.emotebuttons.append(button)
            x_mod_count += 1
            if x_mod_count == columns:
                x_mod_count = 0
                y_mod_count += 1
            self.emotebuttons[i].show()

        self.current_emote_page = 0
        self.prevemotepage = buttons.BackEmoteButton(self, 520, 253+190-28)
        self.prevemotepage.hide()
        self.nextemotepage = buttons.NextEmoteButton(self, 282 + 516, 253+190-28)
        self.nextemotepage.show()
        
        self.realizationbtn = buttons.AOToggleButton(self, 265 + 164, 192 + 304, "realization")
        self.realizationbtn.clicked.connect(self.on_realization_button)
        self.realizationbtn.setToolTip('Show the next message with a realization effect')
        self.realizationsnd = audio.loadhandle(False, AOpath + 'sounds/general/sfx-realization.wav', 0, 0, 0)
        self.shakebtn = buttons.AOToggleButton(self, 265+42 + 164, 192 + 304, "screenshake") # AO 2.8
        self.shakebtn.clicked.connect(self.on_shake_button)
        self.shakebtn.setToolTip('Show the next message with a shaking effect')
        self.customobject = buttons.CustomObjection(self, 250 + 516 - 30, 312 + 40)
        
        self.takethatbtn = buttons.Objections(self, 170+ 516 - 20, 312 + 40, 3)
        self.objectbtn = buttons.Objections(self, 90+ 516 - 10, 312 + 40, 2)
        self.holditbtn = buttons.Objections(self, 10+ 516, 312 + 40, 1)
        self.takethatbtn.clicked.connect(self.icchat_focus)
        self.objectbtn.clicked.connect(self.icchat_focus)
        self.holditbtn.clicked.connect(self.icchat_focus)
        self.customobject.clicked.connect(self.icchat_focus)
        self.objectsnd = 0
        
        self.defensebar = buttons.PenaltyBars(self, 1)
        self.prosecutionbar = buttons.PenaltyBars(self, 2)
        self.defensebar.minusClicked.connect(self.penaltyBarMinus)
        self.defensebar.plusClicked.connect(self.penaltyBarPlus)
        self.prosecutionbar.minusClicked.connect(self.penaltyBarMinus)
        self.prosecutionbar.plusClicked.connect(self.penaltyBarPlus)
        
        self.wtcebtn_1 = buttons.WTCEbuttons(self, 429, 544, 0)
        self.wtcebtn_2 = buttons.WTCEbuttons(self, self.wtcebtn_1.x(), self.wtcebtn_1.y() + self.wtcebtn_1.size().height(), 1)
        self.notguiltybtn = buttons.WTCEbuttons(self, self.wtcebtn_1.x(), self.wtcebtn_2.y() + self.wtcebtn_2.size().height(), 2, 0)
        self.guiltybtn = buttons.WTCEbuttons(self, self.wtcebtn_1.x(), self.notguiltybtn.y() + self.notguiltybtn.size().height(), 2, 1)

        self.wtcebtn_1.clicked.connect(self.WTCEbuttonPressed)
        self.wtcebtn_2.clicked.connect(self.WTCEbuttonPressed)
        self.notguiltybtn.clicked.connect(self.WTCEbuttonPressed)
        self.guiltybtn.clicked.connect(self.WTCEbuttonPressed)
        self.wtcebtn_1.show()
        self.wtcebtn_2.show()
        self.notguiltybtn.show()
        self.guiltybtn.show()
        self.presenting = -1
        
        self.showname = ""
        self.shownameedit = QtGui.QLineEdit(self)
        self.shownameedit.textChanged.connect(self.onChangeShowname)
        self.shownameedit.setPlaceholderText("Showname")
        self.shownameedit.setToolTip('Set a custom name for your character')
        
        self.musicslider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
        self.soundslider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
        self.blipslider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
        self.musicslider.setRange(0, 100)
        self.soundslider.setRange(0, 100)
        self.blipslider.setRange(0, 100)
        self.musicslider.valueChanged.connect(self.change_music_volume)
        self.soundslider.valueChanged.connect(self.change_sound_volume)
        self.blipslider.valueChanged.connect(self.change_blip_volume)
        self.sliderlabel1 = QtGui.QLabel("Music", self)
        self.sliderlabel2 = QtGui.QLabel("SFX", self)
        self.sliderlabel3 = QtGui.QLabel("Blips", self)
        
        self.pinglabel = QtGui.QLabel(self)
        
        self.demoslider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
        self.demoslider.valueChanged.connect(self.demo_seek)
        self.demoslider.setVisible(False)
        self.demoslider.setMinimum(0)
        
        self.name.show()
        self.char.show()
        self.court.show()
        self.bench.show()
        self.chatbox.show()
        
        self.areas = []
        self.areas_len = 0
        self.no_arup = False
        self.muteselected = -1
        self.unmuteselected = -1
        self.muted = []
        self.mychar = -1
        self.mychatcolor = 0
        self.charemotes = []
        self.selectedemote = 0
        self.charname = ''
        self.charshowname = ''
        self.charside = 'def'
        self.lastmsg = ''
        self.inboxqueue = []
        self.text_wait_time = int(get_option("General", "text stay time", 200))
        self.msgqueue = []
        self.selectedmsg = -1
        self.evidence = []
        self.privateevidence = []
        self.selectedevi = -1
        self.present = False
        self.playerlist = {}
        self.selectedplayer = -1
        self.myflip = 0
        self.playsfx = 1
        self.demo_recorder = None
        self.demo_playing = False
        
        self.slide_enabled = bool(get_option("General", "slide", False))
        self.slide_available = False
        self.slide_has_overlay = False
        self.slide_kind = 0 # 0 = def-pro, 1 = def-wit, 2 = pro-wit
        self.slide_direction = 0 # 0 = left to right, 1 = right to left
        
        if self.slide_enabled:
            self.slidebutton.setChecked(True)
        
        # slide_map[old_pos][new_pos] = [kind, direction]
        self.slide_map = {
            "def": { "pro": [0, 0], "wit": [1, 0] },
            "wit": { "def": [1, 1], "pro": [2, 0] },
            "pro": { "def": [0, 1], "wit": [2, 1] },
        }
        
        self.loadSwapCharacters()
        self.iniswaplist.setCurrentIndex(0)
        self.evidence_editor = EditEvidenceDialog(self)
        
        self.connect(self, QtCore.SIGNAL('showMessage(QString, QString, QString)'), self.showMessage)
        
        self.charselect = charselect.charselect(self)
        
        self.wtcesfx = 0
        self.guiltysfx = 0
        self.notguiltysfx = 0
        
        self.stream = 0
        self.specialstream = 0
        self.download_thread = None
        self.tcp = None
        self.demo_player = None
        
        # Finally, load the theme
        self.width = 820
        self.height = 730
        theme = get_option("General", "theme", "default")
        try:
            with open(AO2XPpath+"ao2xp_themes/"+theme+"/theme.py") as t:
                exec t
        except Exception as e:
            QtGui.QMessageBox.critical(None, "Unable to load theme", "There was a problem loading the current theme \"%s\":\n\n%s." % (theme, e))
            os._exit(-2)

    def reset_offsets(self):
        self.pairoffset.setValue(0)
        self.ypairoffset.setValue(0)

    def screen_shake_tick(self):
        self.shakes_remaining -= 1
        shakeforce = 8
        if self.shakes_remaining:
            self.court.move(random.randint(-shakeforce, shakeforce), random.randint(-shakeforce, shakeforce))
            self.zoom.move(random.randint(-shakeforce, shakeforce), random.randint(-shakeforce, shakeforce))
            self.char.move(self.char.xx + random.randint(-shakeforce, shakeforce), self.char.yy + random.randint(-shakeforce, shakeforce), True)
            self.sidechar.move(self.sidechar.xx + random.randint(-shakeforce, shakeforce), self.sidechar.yy + random.randint(-shakeforce, shakeforce), True)
            self.chatbox.move(random.randint(-shakeforce, shakeforce), VIEWPORT_H - self.chatboxheight + random.randint(-shakeforce, shakeforce))
            self.ao2text.move(-self.chatbox.x()+16, (VIEWPORT_H-self.chatboxheight-self.chatbox.y())+32)
            self.text.move(-self.chatbox.x()+16, (VIEWPORT_H-self.chatboxheight-self.chatbox.y())+-1)
        else:
            self.court.move(0,0)
            self.zoom.move(0,0)
            self.char.move(self.char.xx, self.char.yy, True)
            self.sidechar.move(self.sidechar.xx, self.sidechar.yy, True)
            self.chatbox.move(0, VIEWPORT_H-self.chatboxheight)
            self.ao2text.move(16, 32)
            self.text.move(16,32)
            self.screenshake.stop()
    
    def icchat_focus(self):
        self.icchatinput.setFocus()

    def on_realization_button(self):
        if self.realizationbtn.isPressed():
            self.effectdropdown.setCurrentIndex(1) # realization
        elif self.effectdropdown.currentText() == "realization":
            self.effectdropdown.setCurrentIndex(0)
        self.icchat_focus()

    def on_shake_button(self):
        self.sfxbutton.setChecked(False)
        self.icchat_focus()

    def on_ooc_login(self):
        if not self.oocnameinput.text():
            self.oocnameinput.setText("unnamed")
        
        if not self.login:
            password, ok = QtGui.QInputDialog.getText(self, "Login as moderator", "Enter password.")
            if password and ok:
                self.sendOOCchat(self.oocnameinput.text().toUtf8(), "/login")
                self.sendOOCchat(self.oocnameinput.text().toUtf8(), password.toUtf8())
        else:
            self.sendOOCchat(self.oocnameinput.text().toUtf8(), "/logout")
    
    def set_ping(self, newping):
        self.pinglabel.setText("Ping: %d" % newping)
    
    def set_position(self, ind):
        if not self.oocnameinput.text():
            self.oocnameinput.setText("unnamed")
        self.posdropdown.setCurrentIndex(ind)
        self.charside = str(self.posdropdown.itemText(ind))
        self.setJudgeButtons()
        self.icchat_focus()
        
        if self.demo_playing:
            return

        server_is_2_8 = "additive" in self.features and "looping_sfx" in self.features and "effects" in self.features
            
        if server_is_2_8:
            self.tcp.send("SP#"+self.charside+"#%") # all hail new AO 2.8 packet
        else:
            self.sendOOCchat(self.oocnameinput.text().toUtf8(), "/pos "+self.charside)
    
    def change_music_volume(self, value):
        if self.music:
            audio.sethandleattr(self.music, BASS_ATTRIB_VOL, value / 100.0)
            if value == 0:
                audio.pausehandle(self.music)
            elif audio.handleisactive(self.music) == BASS_ACTIVE_PAUSED:
                audio.playhandle(self.music, False)
    
    def change_sound_volume(self, value):
        if self.sound:
            audio.sethandleattr(self.sound, BASS_ATTRIB_VOL, value / 100.0)
        audio.sethandleattr(self.realizationsnd, BASS_ATTRIB_VOL, value / 100.0)
        audio.sethandleattr(self.wtcesfx, BASS_ATTRIB_VOL, value / 100.0)
        audio.sethandleattr(self.guiltysfx, BASS_ATTRIB_VOL, value / 100.0)
        audio.sethandleattr(self.notguiltysfx, BASS_ATTRIB_VOL, value / 100.0)
        if self.modcall:
            audio.sethandleattr(self.modcall, BASS_ATTRIB_VOL, value / 100.0)
    
    def change_blip_volume(self, value):
        if self.blipsnd:
            audio.sethandleattr(self.blipsnd, BASS_ATTRIB_VOL, value / 100.0)
    
    def setJudgeButtons(self):
        if self.charside == 'jud':
            self.defensebar.minusbtn.show()
            self.defensebar.plusbtn.show()
            self.prosecutionbar.minusbtn.show()
            self.prosecutionbar.plusbtn.show()
            self.wtcebtn_1.show()
            self.wtcebtn_2.show()
            self.notguiltybtn.show()
            self.guiltybtn.show()
        else:
            self.defensebar.minusbtn.hide()
            self.defensebar.plusbtn.hide()
            self.prosecutionbar.minusbtn.hide()
            self.prosecutionbar.plusbtn.hide()
            self.wtcebtn_1.hide()
            self.wtcebtn_2.hide()
            self.notguiltybtn.hide()
            self.guiltybtn.hide()
    
    def onChangeShowname(self, text):
        self.showname = str(text.toUtf8())
        
    def onMusicSearch(self, text):
        self.musicitems.clear()
        
        if text:
            for song, fname in self.musiclist.items():
                if QtCore.QString(fname).contains(text, QtCore.Qt.CaseInsensitive):
                    songitem = QtGui.QListWidgetItem()
                    songitem.setText(song)
                    if exists(unicode(AOpath + 'sounds/music/' + fname.replace("<and>","&").lower())):
                        songitem.setBackgroundColor(QtGui.QColor(128, 255, 128))
                    self.musicitems.addItem(songitem)
        else:
            self.allMusic()

    def setEvidenceImg(self, guiobj, image, scale=False):
        if exists(AOpath + 'evidence/' + image):
            img = QtGui.QPixmap(AOpath + "evidence/%s" % image)
            if not img.isNull() and scale:
                guiobj.setPixmap(img.scaled(140, 140,  QtCore.Qt.KeepAspectRatioByExpanding, QtCore.Qt.FastTransformation))
            else:
                guiobj.setPixmap(img)
        else:
            img = QtGui.QPixmap(AO2XPpath + 'themes/default/evidence_selected.png')
            if not img.isNull() and scale:
                guiobj.setPixmap(img.scaled(140, 140,  QtCore.Qt.KeepAspectRatioByExpanding, QtCore.Qt.FastTransformation))
            else:
                guiobj.setPixmap(img)
                
            if ini.read_ini_bool("AO2XP.ini", "General", "download evidence", True):
                url = "base/evidence/"+image.lower()
                url = url.replace("evidence/../", "")
                path = AOpath+"evidence/"+image
                path = path.replace("evidence/../", "")
                thread.start_new_thread(download_thread, (url, path))
    
    def changeUnmuteIndex(self, item):
        for i in range(self.unmutedlist.count()):
            if self.unmutedlist.item(i) == item:
                self.muteselected = i

    def changeMuteIndex(self, item):
        for i in range(self.mutedlist.count()):
            if self.mutedlist.item(i) == item:
                self.unmuteselected = i

    def onMuteClick(self):
        if self.unmutedlist.count() == 0:
            return QtGui.QMessageBox.information(self, 'No character selected', 'There are no characters to mute.')
        if self.muteselected == -1:
            return QtGui.QMessageBox.information(self, 'No character selected', 'To mute a character, select their name from the list to the left, then click the >> button.')
        for i in range(len(self.charlist)):
            if self.charlist[i][0] == self.unmutedlist.item(self.muteselected).text():
                self.muted.append(i)
                self.muted.sort()
                self.muteselected = -1
                break

        self.unmutedlist.clear()
        self.mutedlist.clear()
        for i in range(len(self.charlist)):
            if i in self.muted:
                self.mutedlist.addItem(self.charlist[i][0])
            else:
                self.unmutedlist.addItem(self.charlist[i][0])

    def onUnmuteClick(self):
        if self.mutedlist.count() == 0:
            return QtGui.QMessageBox.information(self, 'No character selected', "There are no characters to unmute.")
        if self.unmuteselected == -1:
            return QtGui.QMessageBox.information(self, 'No character selected', 'To unmute a character, select their name from the list to the right, then click the << button.')
        for char in self.charlist:
            if char[0] == self.mutedlist.item(self.unmuteselected).text():
                del self.muted[self.unmuteselected]
                self.unmuteselected = -1
                break

        self.unmutedlist.clear()
        self.mutedlist.clear()
        for i in range(len(self.charlist)):
            if i in self.muted:
                self.mutedlist.addItem(self.charlist[i][0])
            else:
                self.unmutedlist.addItem(self.charlist[i][0])

    def penaltyBarMinus(self, barType):
        netmsg = 'HP#' + str(barType) + '#'
        if barType == 1:
            if self.defensebar.getHealth() <= 0:
                return
            netmsg += str(self.defensebar.getHealth() - 1) + '#'
        elif barType == 2:
            if self.prosecutionbar.getHealth() <= 0:
                return
            netmsg += str(self.prosecutionbar.getHealth() - 1) + '#'
        netmsg += '%'
        self.tcp.send(netmsg)
        self.icchat_focus()

    def penaltyBarPlus(self, barType):
        netmsg = 'HP#' + str(barType) + '#'
        if barType == 1:
            if self.defensebar.getHealth() >= 10:
                return
            netmsg += str(self.defensebar.getHealth() + 1) + '#'
        elif barType == 2:
            if self.prosecutionbar.getHealth() >= 10:
                return
            netmsg += str(self.prosecutionbar.getHealth() + 1) + '#'
        netmsg += '%'
        self.tcp.send(netmsg)
        self.icchat_focus()

    def setWhiteFlash(self, on, realizationtype=0, msec=0):
        self.whiteflashlab.setVisible(on)
        if realizationtype == 1:
            self.playRealization(f)
        if msec:
            self.whiteflash.start(msec)

    def setScreenShake(self, on, amount=20):
        self.shakes_remaining = amount if on else 1
        self.screenshake.start(25)

    def WTCEbuttonPressed(self, type, variant):
        if type != 2:
            self.tcp.send('RT#testimony' + str(type + 1) + '#%')
        else:
            self.tcp.send("RT#judgeruling#" +str(variant)+ "#%")
        self.icchat_focus()

    def onPVPacket(self, charname=""):
        self.gamewindow.setFixedSize(self.width, self.height)
        self.gamewindow.center()
        if not self.swapping and charname:
            self.loadCharacter(charname)
    
    def loadCharacter(self, charname):
        self.msgqueue_list.clear()
        self.msgqueue = []
        self.effectdropdown.clear()
        self.emotedropdown.clear()
        self.charemotes = []
        self.selectedemote = 0
        self.current_emote_page = 0
        
        self.swapping = False
        self.iniswapinfo.setText('Not swapped')
        
        effectslist = ini.get_effects(charname)
        self.effectdropdown.setVisible(bool(effectslist))
        if effectslist:
            effectslist.insert(0, "No effect")
            self.effectdropdown.addItems(effectslist)
        
        if isinstance(charname, str):
            charname = unicode(charname.lower())
        elif isinstance(charname, QtCore.QString):
            charname = unicode(charname.toLower())
        
        #self.charname = ini.read_ini(AOpath + 'characters/' + charname + '/char.ini', "options", "name", charname.decode('utf-8').lower()
        self.charname = charname # Just use the folder name

        self.charshowname = ini.read_ini(AOpath + 'characters/' + charname + '/char.ini', "options", "showname")
        if not self.charshowname == "":
            self.charshowname = self.charshowname.decode('utf-8')
        self.charside = ini.read_ini(AOpath + 'characters/' + charname + '/char.ini', "options", "side", "def")
        
        self.posdropdown.setCurrentIndex(self.posdropdown.findText(self.charside))
        self.setJudgeButtons()

        for emoteind in range(1, ini.read_ini_int(AOpath+"characters/"+self.charname+"/char.ini", "emotions", "number") + 1):
            if emoteind == 1:
                suffix = 'on'
            else:
                suffix = 'off'
            
            emote = ini.read_ini(AOpath + 'characters/' + charname + '/char.ini', "emotions", str(emoteind), 'normal#(a)normal#normal#0#')
            sound = ini.read_ini(AOpath + 'characters/' + charname + '/char.ini', "soundn", str(emoteind), '1')
            soundt = ini.read_ini(AOpath + 'characters/' + charname + '/char.ini', "soundt", str(emoteind), '0')
            soundl = ini.read_ini(AOpath + 'characters/' + charname + '/char.ini', "soundl", str(emoteind), '0') # AO 2.8
            
            emotelist = emote.split('#')
            deskmod = emotelist.pop(len(emotelist) - 1)
            
            emotelist.append(sound)
            emotelist.append(soundt)
            emotelist.append(soundl) # AO 2.8
            emotelist.append(deskmod)
            self.charemotes.append(emotelist)
            if emotelist[0]:
                self.emotedropdown.addItem(emotelist[0])
            else:
                self.emotedropdown.addItem(emotelist[1] + ' ' + emotelist[2])

        self.emotedropdown.setCurrentIndex(0)
        self.set_emote_page()

    def set_emote_page(self):
        if self.mychar < 0:
            return
        self.prevemotepage.hide()
        self.nextemotepage.hide()

        total_emotes = len(self.charemotes) 
        
        for button in self.emotebuttons:
            button.hide()
            
        if not total_emotes:
            print "[client] The selected character appears to have no emotions defined"
            return

        total_pages = total_emotes / self.max_emotes_on_page
        emotes_on_page = 0
        if total_emotes % self.max_emotes_on_page != 0:
            total_pages += 1
            if total_pages > self.current_emote_page + 1:
                emotes_on_page = self.max_emotes_on_page
            else:
                emotes_on_page = total_emotes % self.max_emotes_on_page
        else:
            emotes_on_page = self.max_emotes_on_page
        if total_pages > self.current_emote_page + 1:
            self.nextemotepage.show()
        if self.current_emote_page > 0:
            self.prevemotepage.show()
        for n_emote in range(emotes_on_page):
            n_real_emote = n_emote + self.current_emote_page * self.max_emotes_on_page
            if n_real_emote == self.selectedemote:
                image = QtGui.QPixmap(AOpath + 'characters/' + self.charname + '/emotions/button' + str(n_real_emote + 1) + '_on.png')
            else:
                image = QtGui.QPixmap(AOpath + 'characters/' + self.charname + '/emotions/button' + str(n_real_emote + 1) + '_off.png')
                
            if not image.isNull() and not image.width() == 40:
                self.emotebuttons[n_emote].setPixmap(image.scaled(40, 40, QtCore.Qt.IgnoreAspectRatio, QtCore.Qt.FastTransformation))
            else:
                self.emotebuttons[n_emote].setPixmap(image)
                
            self.emotebuttons[n_emote].show()
            self.emotebuttons[n_emote].setToolTip(self.charemotes[n_emote + self.current_emote_page * self.max_emotes_on_page][0])       
            
    def iniswap_index_change(self, ind):
        self.iniswapindex = ind

    def loadSwapCharacters(self):
        self.charsfolder = []
        self.iniswaplist.clear()
        for folder in os.listdir(unicode(AOpath + 'characters')):
            if exists(AOpath + 'characters/' + folder + '/char.ini'):
                self.charsfolder.append(folder)
                self.iniswaplist.addItem(folder)

    def iniswap_confirm(self):
        if self.charlist[self.mychar][0].lower() == self.charsfolder[self.iniswapindex].lower():
            self.resetIniSwap()
        else:
            self.swapping = True
            self.iniswapinfo.setText('Swapped to ' + self.charsfolder[self.iniswapindex])
            self.loadCharacter(self.charsfolder[self.iniswapindex])

    def resetIniSwap(self):
        self.swapping = False
        self.iniswapinfo.setText('Not swapped')
        self.loadCharacter(self.charlist[self.mychar][0])

    def onAddEvidence(self):
        self.evidence_editor.show()

    def onEditEvidence(self):
        if not self.evidence:
            return QtGui.QMessageBox.information(self, 'No evidence', "There's no evidence on the court record.")
        self.evidence_editor.edit_evidence(self.selectedevi)

    def onDeleteEvidence(self):
        if self.selectedevi == -1:
            return
            
        if not self.privateinv:
            if self.evidence:
                self.tcp.send('DE#' + str(self.selectedevi) + '#%')
            else:
                self.tcp.send('DE#0#%')
        elif len(self.privateevidence):
            del self.privateevidence[self.selectedevi]
            self.privatedropdown.removeItem(self.selectedevi)
            
    def onExportEvidence(self, is_autosave=False):
        if not exists("evidence"):
            os.mkdir("evidence")
            
        path = unicode(QtGui.QFileDialog.getSaveFileName(self, "Save evidence", "evidence", "Evidence (*.ini)")) if not is_autosave else "evidence/inventory.ini"
        
        if path:
            evidence = self.evidence if not self.privateinv else self.privateevidence
            inifile = ConfigParser()
            for i in range(len(evidence)):
                evi = evidence[i]
                id = str(i)
                inifile.add_section(id)
                if isinstance(evi[0], QtCore.QString):
                    inifile.set(id, "name", evi[0].replace('\n', '\\n'))
                    inifile.set(id, "description", evi[1].replace('\n', '\\n'))
                    inifile.set(id, "image", evi[2])
                else:
                    inifile.set(id, "name", evi[0].replace('\n', '\\n').encode('utf-8'))
                    inifile.set(id, "description", evi[1].replace('\n', '\\n').encode('utf-8'))
                    inifile.set(id, "image", evi[2].encode('utf-8'))
            
            with open(path, "wb") as f:
                inifile.write(f)
    
    def onImportEvidence(self, is_autoload=False):
        if not is_autoload:
            if not self.privateinv:
                if QtGui.QMessageBox.warning(self, "Import evidence", 'This will OVERWRITE the global evidence server-side.\n\nContinue?', QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) == QtGui.QMessageBox.No:
                    return
            else:
                if QtGui.QMessageBox.warning(self, "Import evidence", 'This will OVERWRITE your private evidence.\n\nContinue?', QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) == QtGui.QMessageBox.No:
                    return

        if not exists("evidence"):
            os.mkdir("evidence")
            
        path = unicode(QtGui.QFileDialog.getOpenFileName(self, "Load evidence", "evidence", "Evidence (*.ini)")) if not is_autoload else "evidence/inventory.ini"
        if path and exists(path):
            evidence = []
            inifile = ConfigParser()
            inifile.read(path)
            for section in inifile.sections():
                name = ini.read_ini(inifile, section, "name").replace('\\n', '\n').replace('\\"', '"').rstrip()
                description = ini.read_ini(inifile, section, "description").replace('\\n', '\n').replace('\\"', '"').rstrip()
                image = ini.read_ini(inifile, section, "image", "empty.png")
                
                # Remove opening and closing quotes
                if description[0] == '"' and description[-1] == '"':
                    description = description[1:-1]
                    
                # Not all evidence files are plain unicode
                name = name.decode("unicode_escape") if "\\x" in name else name.decode('utf-8')
                description = description.decode("unicode_escape") if "\\x" in description else description.decode('utf-8')
                
                evidence.append([name, description, image])
                
            if self.privateinv or is_autoload:
                dropdown = self.privatedropdown
                self.privateevidence = evidence
                if dropdown.count() > 0:
                    dropdown.clear()

                if evidence:
                    for evi in evidence:
                        dropdown.addItem(evi[0])
                    dropdown.setCurrentIndex(self.selectedevi)
                
                if not is_autoload:
                    self.onExportEvidence(True)
            elif evidence:
                if self.evidence:
                    for i in range(len(self.evidence)):
                        self.tcp.send('DE#' + str(self.selectedevi) + '#%')
                for evi in evidence:
                    self.tcp.send('PE#' + evi[0] + '#' + evi[1] + '#' + evi[2] + '#%')
         
    def onTransferEvidence(self):
        if self.privateinv:
            evi = self.privateevidence[self.selectedevi]
            target = self.evidence
            target_str = "global"
        else:
            evi = self.evidence[self.selectedevi]
            target = self.privateevidence
            target_str = "private"
        
        if evi in target:
            return QtGui.QMessageBox.information(self, "Can't transfer evidence", 'The evidence "%s" already exists in the %s inventory.' % (evi[0], target_str))
        else:
            if self.privateinv:
                for i in range(len(evi)):
                    evi[i] = evi[i].replace('#', '<num>').replace('%', '<percent>').replace('&', '<and>').replace('$', '<dollar>').replace('\\n', '\n')
                self.tcp.send('PE#' + evi[0] + '#' + evi[1] + '#' + evi[2] + '#%')
            else:
                self.privateevidence.append(evi)
                self.privatedropdown.addItem(evi[0])
        
    def onTransferAllEvidence(self):
        fail = []
        
        if self.privateinv:
            evi = self.privateevidence[self.selectedevi]
            origin = self.privateevidence
            target = self.evidence
            target_str = "global"
        else:
            evi = self.evidence[self.selectedevi]
            origin = self.evidence
            target = self.privateevidence
            target_str = "private"
        
        for evi in origin:
            if evi in target:
                fail.append(evi[0])
            else:
                if self.privateinv:
                    self.tcp.send('PE#' + evi[0] + '#' + evi[1] + '#' + evi[2] + '#%')
                else:
                    self.privateevidence.append(evi)
                    self.privatedropdown.addItem(evi[0])
         
        if fail:
            return QtGui.QMessageBox.information(self, "Some evidence wasn't transferred", "The following evidence already exists in the %s inventory:\n\n%s." % (target_str, ", ".join(fail)))
        
    def onSwitchInventory(self, reset=False):
        self.privateinv = not self.privateinv
        if self.privateinv and not reset:
            self.present = False
            self.evidencepresent.setPixmap(self.evidencepresent.button_off)
            self.evidencepresent.hide()
            self.evidencedropdown.hide()
            self.privatedropdown.show()
            self.evidencemoveglobal.show()
            self.evidencemoveallglobal.show()
            self.evidenceswitchglobal.show()
            self.evidencemoveprivate.hide()
            self.evidencemoveallprivate.hide()
            self.evidenceswitchprivate.hide()
            self.privatedropdown.setCurrentIndex(0)
            self.changeEvidence(0, 1)
        else:
            self.evidencepresent.show()
            self.evidencedropdown.show()
            self.privatedropdown.hide()
            self.evidencemoveglobal.hide()
            self.evidencemoveallglobal.hide()
            self.evidenceswitchglobal.hide()
            self.evidencemoveprivate.show()
            self.evidencemoveallprivate.show()
            self.evidenceswitchprivate.show()
            self.evidencedropdown.setCurrentIndex(0)
            self.changeEvidence(0, 0)

    def onClick_callMod(self):
        if "modcall_reason" in self.features:
            reason, ok = QtGui.QInputDialog.getText(self, "Call a moderator", "Enter your reason.")
            if ok and reason:
                self.tcp.send("ZZ#"+reason.toUtf8()+"#%")
        else:
            self.tcp.send("ZZ#%")

    def onClick_changeChar(self):
        if self.demo_playing:
            self.inbox_timer.stop()
            self.chat_tick_timer.stop()
            self.disconnectCommon()
            self.gamewindow.returnToMenu()
        else:
            self.charselect.showCharSelect()

    def changeFlipCheck(self, on):
        if on == 2:
            on = 1
        self.myflip = on
        self.icchat_focus()

    def changeSfxCheck(self, on):
        if on == 2:
            on = 1
        self.playsfx = on
        self.nointerruptbtn.setDisabled(not on)
        if on == 0:
            self.nointerruptbtn.setChecked(False)
        self.icchat_focus()

    def onClicked_msgqueue(self, item):
        for i in range(len(self.msgqueue_list)):
            if self.msgqueue_list.item(i) == item:
                self.selectedmsg = i
                
    def onClicked_playerList(self, item):
        for i in range(len(self.player_list)):
            sel = self.player_list.item(i)
            if sel == item:
                s = sel.text()
                self.selectedplayer = s[1:s.indexOf("]")]

    def onClicked_remove_queue(self):
        if len(self.msgqueue_list) == 0:
            return QtGui.QMessageBox.information(self, "No messages in queue", 'Enter a message on the game chat to add one.')
        if self.selectedmsg == -1:
            return QtGui.QMessageBox.information(self, 'No message selected', 'Select a message from the list to remove it.')
        self.msgqueue_list.takeItem(self.selectedmsg)
        del self.msgqueue[self.selectedmsg]
        
    def onClicked_clear_queue(self):
        return QtGui.QMessageBox.information(self, "Clear queue", 'Not implemented.')

    def onClicked_playerPair(self):
        if not self.selectedplayer == -1:
            self.gametabs.setCurrentWidget(self.gametab_pair)
            self.paircheckbox.setChecked(True)
            char = self.playerlist[str(self.selectedplayer)][1]
            if char == '':
                return QtGui.QMessageBox.information(self, "Unable to pair", 'That player has no character selected.')
            else:
                self.pairdropdown.setCurrentIndex([c[0] for c in self.charlist].index(char))
        else:
            return QtGui.QMessageBox.information(self, 'No player selected', 'Select a player from the list to attempt pairing.')

    def onClicked_playerKick(self):
        if not self.selectedplayer == -1:
            reason, ok = QtGui.QInputDialog.getText(self, "Kick a player", "Please enter the reason.", text="Being annoying")
            if reason and ok:
                self.tcp.send("MA#%s#0#%s#%%" % (self.selectedplayer, reason))
        else:
            return QtGui.QMessageBox.information(self, 'No player selected', 'Select a player from the list to kick.')
        
    def onClicked_playerBan(self):
        if not self.selectedplayer == -1:
            reason, ok = QtGui.QInputDialog.getText(self, "Ban a player", "Please enter the reason.", text="Being annoying")
            if reason and ok:
                duration, ok = QtGui.QInputDialog.getInt(self, "Ban a player", "Please enter the ban length in minutes.", 60, 1)
                if duration and ok:
                    self.tcp.send("MA#%s#%s#%s#%%" % (self.selectedplayer, duration, reason))
        else:
            return QtGui.QMessageBox.information(self, 'No player selected', 'Select a player from the list to ban.')

    def changeEvidence(self, ind, kind):
        if ind < 0:
            return
        if self.privateinv:
            if not kind == 1:
                return
            evi = self.privateevidence
        else:
            if not kind == 0:
                return
            evi = self.evidence
            
        self.selectedevi = ind
        if len(evi) > 0:
            self.evidencedesc.setText(evi[ind][1])
            self.setEvidenceImg(self.evidenceimage, evi[ind][2])
            
    def changeGlobalEvidence(self, ind):
        self.changeEvidence(ind, 0)
        
    def changePrivateEvidence(self, ind):
        self.changeEvidence(ind, 1)

    def changeEmote(self, dropdown, ind):
        if ind == -1:
            return
            
        self.icchat_focus()    
        if not dropdown:
            self.selectedemote = ind + self.current_emote_page * self.max_emotes_on_page
        else:
            self.selectedemote = ind
        for button in self.emotebuttons:
            if button.emoteid == ind:
                button.path = AOpath + 'characters/' + self.charname + '/emotions/button' + str(button.emoteid + self.current_emote_page * self.max_emotes_on_page + 1)
                image = QtGui.QPixmap(button.path + '_on.png')
            else:
                image = QtGui.QPixmap(AOpath + 'characters/' + self.charname + '/emotions/button' + str(button.emoteid + self.current_emote_page * self.max_emotes_on_page + 1) + '_off.png')
                
            if not image.isNull() and image.width() > 40:
                button.setPixmap(image.scaled(40, 40, QtCore.Qt.IgnoreAspectRatio, QtCore.Qt.FastTransformation))
            else:
                button.setPixmap(image)

    def setChatColor(self, ind):
        self.mychatcolor = ind
        self.icchat_focus()

    def showMessage(self, type, *args, **kwargs):
        if type == 'critical':
            reply = QtGui.QMessageBox.critical(self, *args, **kwargs)
        elif type == 'information':
            reply = QtGui.QMessageBox.information(self, *args, **kwargs)
        elif type == 'question':
            reply = QtGui.QMessageBox.question(self, *args, **kwargs)
        elif type == 'warning':
            reply = QtGui.QMessageBox.warning(self, *args, **kwargs)
        if self.willDisconnect:
            self.disconnectCommon()
            self.gamewindow.returnToMenu()
    
    def disconnectCommon(self):
        self.onSwitchInventory(True)
        self.selectedplayer = -1
        self.player_list.clear()
        self.player_kick.setDisabled(True)
        self.player_ban.setDisabled(True)
        self.ooclogin.setText("Lo&gin")
        self.login = False
        self.privateinv = False
        if self.tcp:
            self.tcp.close()
        if self.demo_player:
            self.demo_player.stop()
            self.demo_player = None
        self.demo_recorder = None
        self.demo_playing = False
        self.stopMusic()

    def onMusicClick(self, item):
        self.sendMC(self.musiclist[item.text()])
            
    def onAreaClick(self, item):
        area = item.text().split('\n')[0]
        self.sendMC(area)

    def sendMC(self, content):
        if "cccc_ic_support" in self.features and self.showname:
            self.tcp.send('MC#' + content + '#' + str(self.mychar) + '#' + self.showname + '#%')
        else:
            self.tcp.send('MC#' + content + '#' + str(self.mychar) + '#%')

    def icLogChanged(self):
        if self.iclog.verticalScrollBar().value() == self.iclog.verticalScrollBar().maximum(): self.iclog.verticalScrollBar().setValue(self.iclog.verticalScrollBar().maximum())

    def ooclog_update(self):
        if self.ooclog.verticalScrollBar().value() == self.ooclog.verticalScrollBar().maximum(): self.ooclog.verticalScrollBar().setValue(self.ooclog.verticalScrollBar().maximum())
    
    def sendOOCchat(self, name, text):
        self.tcp.send('CT#' + name + '#' + text + '#%')

    def onOOCreturn(self):
        text = self.oocinput.text().replace('#', '<num>').replace('%', '<percent>').replace('&', '<and>').replace('$', '<dollar>').replace('\\n', '\n')
        if text.startsWith('//'):
            code = str(self.oocinput.text()).replace('//', '', 1).replace('\\NEWLINE', '\n')
            try:
                exec code
            except Exception as e:
                msg = 'code error\n'
                for arg in e.args:
                    msg += str(arg) + '\n'

                msg = msg.rstrip()
                self.ooclog.append(msg)
                return
            return
        elif text.startsWith("/pos "): # why.......
            ind = self.posdropdown.findText(str(text.split(" ")[1]))
            if ind >= 0: self.posdropdown.setCurrentIndex(ind)
            self.oocinput.clear()
            return
        
        if self.mocktext.isChecked():
            text = mock_str(text)
        if self.autocaps.isChecked():
            l = QtCore.QStringList(list(text))
            l[0] = l[0].toUpper()
                
            last = [".", "?", "!", ")", "]"]
            if not l[-1] in last:
                l.append(".")
            text = l.join("").replace(" i ", " I ").replace("i'm", "I'm").replace("it's", "It's")
        if self.spacebartext.isChecked():
            l = QtCore.QStringList(list(text))
            for i in range(1, len(l)+len(l)-1, 2):
                l.insert(i, " ")
                text = l.join("")
            
        self.sendOOCchat(self.oocnameinput.text().toUtf8(), text)
        self.oocinput.clear()

    def onICreturn(self):
        text = unicode(self.icchatinput.text()).replace('#', '<num>').replace('%', '<percent>').replace('&', '<and>').replace('$', '<dollar>')#.replace('/n', '\n')
        
        if text:
            if self.mocktext.isChecked():
                text = mock_str(text)
            if self.autocaps.isChecked():
                l = list(text)
                if l[0] == " " and len(l) > 1:
                    l[1] = l[1].upper()
                else:
                    l[0] = l[0].upper()
                last = [".", "?", "!", "<", ">", ")", "]"]
                if not l[-1] in last:
                    l.append(".")
                text = "".join(l).replace(" i ", " I ").replace("i'm", "I'm").replace("it's", "It's")
            if self.spacebartext.isChecked():
                l = list(text)
                for i in range(1, len(l)+len(l)-1, 2):
                    l.insert(i, " ")
                    text = "".join(l)
        
        emote = self.charemotes[self.selectedemote]

        if self.nointerruptbtn.isChecked():
            modifier = 0
        else:
            modifier = self.playsfx
        objection = 0
        if self.customobject.isPressed():
            objection = 4
            self.customobject.setPressed(False)
        elif self.holditbtn.isPressed():
            objection = 1
            self.holditbtn.setPressed(False)
        elif self.objectbtn.isPressed():
            objection = 2
            self.objectbtn.setPressed(False)
        elif self.takethatbtn.isPressed():
            objection = 3
            self.takethatbtn.setPressed(False)
        if emote[3] == '5': #zoom
            if self.nointerruptbtn.isChecked():
                modifier = 5
            else:
                if objection > 0:
                    modifier = 6
                else:
                    modifier = 5
        elif objection > 0:
            if self.nointerruptbtn.isChecked():
                modifier = 0
            else:
                modifier = 2
        
        msg = u"MS#"
        
        # Visible desk modifier
        if "deskmod" in self.features:
            if emote[3] == '5': # Zoom forcibly hides the desk
                msg += "0#"
            elif emote[7]: # Respect deskmod if found
                msg += "%s#" % str(emote[7])
            else:
                msg += "%d#" % self.deskbtn.isChecked()
        else:
            msg += "chat#"

        msg += emote[1]+"#" #pre-anim
        msg += self.charname.title()+"#"
        msg += emote[2]+"#" #anim
        msg += text+"#"
        msg += self.charside+"#"
        msg += emote[4]+"#" #sfx
        msg += str(modifier)+"#" #emote modifier
        msg += str(self.mychar)+"#" #character ID
        msg += emote[5]+"#" #sfx delay
        msg += str(objection)+"#"
        msg += str((self.selectedevi + 1) * int(self.present))+"#" #selected evidence
        
        if self.present:
            self.present = False
            self.evidencepresent.setPixmap(self.evidencepresent.button_off)
        
        if "flipping" in self.features:
            msg += str(self.myflip)+"#"
        else:
            msg += str(self.mychar)+"#" # old AO servers send a second charID in the message because drunk fanat
        
        msg += str(int(self.realizationbtn.isPressed()))+"#"
        msg += str(self.mychatcolor)+"#"
        
        if "cccc_ic_support" in self.features:
            showname = self.showname.decode('utf-8')
            if self.showname == "" and not self.charshowname == "":
                showname = self.charshowname
            msg += showname+"#" # custom showname
            if self.paircheckbox.isChecked():
                msg += str(self.pairdropdown.currentIndex()) # pair charID
                if "effects" in self.features:
                    msg += "^%d#" % self.pair_order.currentIndex() # pair ordering
                else:
                    msg += "#"
            else:
                msg += "-1#"

            # AO 2.8: always send offset
            if "y_offset" in self.features: # AO 2.9
                msg += str(self.pairoffset.value()) + "&" + str(-self.ypairoffset.value()) + "#"
            else:
                msg += str(self.pairoffset.value())+"#"

            msg += str(int(self.nointerruptbtn.isChecked()))+"#" # NoInterrupt(TM)

        if "looping_sfx" in self.features: # AO 2.8
            msg += emote[6]+"#" # loop sound?
            msg += "%d#" % self.shakebtn.isPressed() # screen shake
            emotes_to_check = [emote[1], "(b)"+emote[2], "(b)/"+emote[2], "(a)"+emote[2], "(a)/"+emote[2] ]
            effects_to_check = ["_FrameScreenshake", "_FrameRealization", "_FrameSFX"]

            for f_effect in effects_to_check:
                packet = ""
                for f_emote in emotes_to_check:
                    packet += f_emote
                    if ini.read_ini_bool("AO2XP.ini", "General", "network frame effects", True):
                        sfx_frames = "|".join(ini.read_ini_tags(AOpath+"characters/"+self.charname+"/char.ini", f_emote + f_effect))
                        if sfx_frames:
                            packet += "|" + sfx_frames
                    packet += "^"
                msg += packet+"#"

        if "additive" in self.features:
            msg += "%d#" % self.additivebtn.isChecked()

        if "effects" in self.features:
            fx = self.effectdropdown.currentText() if self.effectdropdown.currentIndex() > 0 else ""
            fx_sound = ini.get_effect_sound(fx, self.charname)
            p_effect = ini.read_ini(AOpath+"characters/"+self.charname+"/char.ini", "options", "effects")
            msg += str(fx + "|" + p_effect + "|" + fx_sound + "#").encode('utf-8')
            self.effectdropdown.setCurrentIndex(0)
            
        # AO 2.10.2+
        if "custom_blips" in self.features:
            blip = ini.read_ini(AOpath+"characters/"+self.charname+"/char.ini", "options", "blips")
            if not blip:
                blip = ini.read_ini(AOpath+"characters/"+self.charname+"/char.ini", "options", "gender")
            if blip:
                msg += str(blip) + "#"
                
            # Slides
            msg += "%d#" % self.slidebutton.isChecked()

        msg += "%"
        
        self.msgqueue_list.addItem(self.icchatinput.text())
        self.msgqueue.append(msg)
        self.lastmsg = msg
        
        self.icchatinput.clear()
        self.realizationbtn.setPressed(False)
        self.shakebtn.setPressed(False)

    def setBackground(self, bg, reset=False):
        if not exists(AOpath + 'background/' + bg):
            bg = 'default'

        for bgfile in [["side_def", "defenseempty"],
                       ["bench_def", "defensedesk"],
                       ["side_pro", "prosecutorempty"],
                       ["bench_pro", "prosecutiondesk"],
                       ["side_wit", "witnessempty"],
                       ["bench_wit", "stand"],
                       ["side_hld", "helperstand"],
                       ["bench_hld", "helperdesk"],
                       ["side_hlp", "prohelperstand"],
                       ["bench_hlp", "prohelperdesk"],
                       ["side_jud", "judgestand"],
                       ["bench_jud", "judgedesk"],
                       ["side_jur", "jurystand"],
                       ["bench_jur", "jurydesk"],
                       ["side_sea", "seancestand"],
                       ["bench_sea", "seancedesk"]]:

            bgimg = QtGui.QImage(AOpath + 'background/' + bg + '/' + bgfile[1] + '.png')
            if not bgimg.isNull():
                if bgimg.size().width() != VIEWPORT_W or bgimg.size().height() != VIEWPORT_H:
                    setattr(self, bgfile[0], QtGui.QPixmap.fromImage(bgimg.scaled(VIEWPORT_W, VIEWPORT_H,  QtCore.Qt.KeepAspectRatioByExpanding, QtCore.Qt.FastTransformation)))
                else:
                    setattr(self, bgfile[0], QtGui.QPixmap.fromImage(bgimg))
            else:
                setattr(self, bgfile[0], QtGui.QPixmap.fromImage(bgimg))
        
        court = AOpath + 'background/' + bg + '/court.png'
        self.slide_available = exists(court)
        
        if self.slide_available:
            slide = QtGui.QPixmap(court)
            slide_width = slide.width() * 2
            
            self.slide_bg.resize(slide_width, VIEWPORT_H) 
            self.slide_bg.setPixmap(slide.scaled(slide.width() * 2, VIEWPORT_H, QtCore.Qt.KeepAspectRatioByExpanding, QtCore.Qt.FastTransformation))

            court_overlay = AOpath + 'background/' + bg + '/court_overlay.png'
            if exists(court_overlay):
                slide_overlay = QtGui.QPixmap(court_overlay)
                self.slide_overlay.resize(slide_width, VIEWPORT_H) 
                self.slide_overlay.setPixmap(slide_overlay.scaled(slide.width() * 2, VIEWPORT_H, QtCore.Qt.KeepAspectRatioByExpanding, QtCore.Qt.FastTransformation))
                self.slide_has_overlay = True
            else:
                self.slide_has_overlay = False
        
        self.bench.show()
        
        if reset:
            self.chatbox.hide()
            self.char.hide()
            self.set_scene(True)
    
    def slide_start(self, value = [0, 0]):
        self.chatbox.hide()
        self.presentedevi.hide()
    
        slide_time = 500
        self.bench.hide()
        self.slide_bg.show()
        
        def_pos = QtCore.QRect(0, 0, self.slide_bg.width(), VIEWPORT_H)
        pro_pos = QtCore.QRect(-(def_pos.size().width() - VIEWPORT_W), 0, def_pos.size().width(), VIEWPORT_H)
        wit_pos = QtCore.QRect(-(self.slide_bg.width() / 2 - VIEWPORT_W / 2), 0, self.slide_bg.width(), VIEWPORT_H)
        
        self.slide_kind = value[0]
        self.slide_direction = value[1]
        
        # TODO: play only first frame of preanim, figure out zooms
        scaling = get_scaling(ini.read_ini(AOpath + 'characters/' + self.m_chatmessage[CHARNAME] + '/char.ini', "options", "scaling").lower())
        if self.m_chatmessage[FLIP] == "1":
            self.slide_speaker.set_flipped(True)
        else:
            self.slide_speaker.set_flipped(False)
            
        self.slide_speaker.play_idle(self.m_chatmessage[CHARNAME], self.m_chatmessage[ANIM], scaling)
        self.slide_speaker.show()
        
        if self.slide_kind == 0:
            if self.slide_last_wit:
                self.slide_witness.play_idle(self.slide_last_wit[0], self.slide_last_wit[1], self.slide_last_wit[2])
                self.slide_witness.show()
            if self.slide_direction == 0:
                bg_start = def_pos
                bg_end = pro_pos
            else:
                bg_start = pro_pos
                bg_end = def_pos
        elif self.slide_kind == 1:
            if self.slide_direction == 0:
                bg_start = def_pos
                bg_end = wit_pos
            else:
                bg_start = wit_pos
                bg_end = def_pos
        elif self.slide_kind == 2:
            if self.slide_direction == 0:
                bg_start = wit_pos
                bg_end = pro_pos
            else:
                bg_start = pro_pos
                bg_end = wit_pos
        
        self.slide_bg.setGeometry(bg_start)
        self.slide_bg_animation.setStartValue(bg_start)
        self.slide_bg_animation.setEndValue(bg_end)
        self.slide_bg_animation.setDuration(slide_time)
        self.slide_bg_animation.setEasingCurve(QtCore.QEasingCurve.InOutQuad)
        self.slide_bg_animation.start()

        if self.slide_has_overlay:
            self.slide_overlay.show()
            self.slide_overlay.setGeometry(bg_start)
    
    def slide_changed(self):
        x = self.slide_bg.x()
        self.slide_overlay.move(x, 0)
        
        # def-pro
        if self.slide_kind == 0:
            if self.slide_last_wit:
                self.slide_witness.move_slide(x + self.slide_bg.width() / 2 - VIEWPORT_W / 2)
            if self.slide_direction == 0:
                self.char.move_slide(x)
                self.slide_speaker.move_slide(x + self.slide_bg.width() - VIEWPORT_W)
            else:
                self.char.move_slide(x + self.slide_bg.width() - VIEWPORT_W)
                self.slide_speaker.move_slide(x)
        # def-wit
        elif self.slide_kind == 1:
            if self.slide_direction == 0:
                self.char.move_slide(x)
                self.slide_speaker.move_slide(x + self.slide_bg.width() / 2 - VIEWPORT_W / 2)
            else:
                self.char.move_slide(x + self.slide_bg.width() / 2 - VIEWPORT_W / 2)
                self.slide_speaker.move_slide(x)
        # pro-wit
        elif self.slide_kind == 2:
            if self.slide_direction == 0:
                self.char.move_slide(x + self.slide_bg.width() / 2 - VIEWPORT_W / 2)
                self.slide_speaker.move_slide(x + self.slide_bg.width() - VIEWPORT_W)
            else:
                self.char.move_slide(x + self.slide_bg.width() - VIEWPORT_W)
                self.slide_speaker.move_slide(x + self.slide_bg.width() / 2 - VIEWPORT_W / 2)
        
    def slide_done(self):
        self.slide_bg.hide()
        self.slide_overlay.hide()
        self.slide_witness.hide()
        self.slide_speaker.hide()
        self.handle_chatmessage_2()

    def netmsg_hp(self, type, health):
        if type == 1:
            self.defensebar.setHealth(health)
        elif type == 2:
            self.prosecutionbar.setHealth(health)
    
    def netmsg_ms(self, p_contents):
        if len(p_contents) < 15: #this is already done on the TCP thread but i'll do it here anyway as well
            return
        
        AO2chat = "cccc_ic_support" in self.features

        if int(p_contents[CHAR_ID]) in self.muted: # skip the self.chatmessage copy line below
            return
            
        m_chatmessage = {}

        for n_string in range(self.chatmessage_size):
            if n_string < len(p_contents) and (n_string < 16 or AO2chat):
                m_chatmessage[n_string] = p_contents[n_string]
            else:
                m_chatmessage[n_string] = ""

        f_char_id = int(m_chatmessage[CHAR_ID])
        
        if f_char_id < 0 or f_char_id >= len(self.charlist):
            return
        
        f_showname = ""
        if not m_chatmessage[SHOWNAME]:
            f_showname = m_chatmessage[CHARNAME]
        else:
            f_showname = m_chatmessage[SHOWNAME]
        
        if self.msgqueue:
            chatmsgcomp = (self.msgqueue[0].split('#')[5]).replace('<dollar>', '$').replace('<percent>', '%').replace('<and>', '&').replace('<num>', '#')
            examine = chatmsgcomp == ">" or chatmsgcomp == "<"
            special = not chatmsgcomp or chatmsgcomp.isspace()
            if examine or (f_char_id == self.mychar and (special or m_chatmessage[CHATMSG] == chatmsgcomp)): # our message showed up
                del self.msgqueue[0]
                self.msgqueue_list.takeItem(0)
                if self.additivebtn.isChecked():
                    self.icchatinput.insert(" ")
        
        m_chatmessage[CHARNAME] = m_chatmessage[CHARNAME].decode("utf-8")
        m_chatmessage[SHOWNAME] = m_chatmessage[SHOWNAME].decode('utf-8')
        
        f_char = m_chatmessage[CHARNAME]
        evidence = int(m_chatmessage[EVIDENCE])-1
        
        t = time.localtime()
        logcharname = f_char
        
        timestamp = "[%d:%.2d] " % (t[3], t[4]) if not self.demo_playing else ""
        
        if f_char.lower() != self.charlist[f_char_id][0].lower():
            logcharname = self.charlist[f_char_id][0] + ' (' + f_char.decode("utf-8") + ')'
        
        chatmsg = m_chatmessage[CHATMSG]
        
        if m_chatmessage[SHOWNAME] and m_chatmessage[SHOWNAME].lower() != f_char.lower():
            try:
                logcharname += " (" + m_chatmessage[SHOWNAME]+")"
            except:
                logcharname += " (???)"
        
        if evidence == -1:
            self.iclog.append(timestamp + '%s: %s' % (logcharname, chatmsg))
        else:
            eviname = '(NULL) %d' % evidence
            try:
                eviname = self.evidence[evidence][0]
            except:
                pass
                
            self.iclog.append(timestamp + '%s: %s\n%s presented an evidence: %s' % (logcharname, chatmsg, f_char, eviname.strip()))

        self.is_additive = (m_chatmessage[ADDITIVE] == "1")

        custom_objection = "custom"
        try: 
            objection_mod = int(m_chatmessage[SHOUT_MOD])
        except:
            if "4&" in m_chatmessage[SHOUT_MOD]: # custom objection name
                objection_mod = 4
                custom_objection = m_chatmessage[SHOUT_MOD].split("4&")[1] # get the name
            else: # just in case of mindfuckery
                objection_mod = 0
        
        if objection_mod <= 4 and objection_mod >= 1:
            # Skip everything in the queue, show message immediately
            self.inboxqueue = []
            self.inboxqueue.append(m_chatmessage)
            self.inbox_timer.stop()
            self.chat_tick_timer.stop()
            self.m_chatmessage = m_chatmessage
            
            objections = ["holdit", "objection", "takethat", "custom_objections/"+custom_objection if custom_objection != "custom" else "custom"]
            self.objectionview.play(objections[objection_mod-1], f_char.lower())
            self.presentedevi.hide()
            self.playObjectionSnd(f_char.lower(), objection_mod)
            
            emote_mod = int(self.m_chatmessage[EMOTE_MOD])
            if emote_mod == 0:
                self.m_chatmessage[EMOTE_MOD] = 1
        else:
            # Old behavior
            #self.m_chatmessage = m_chatmessage
            #self.handle_chatmessage_2()
            
            # Add message to queue and wait, unless queue empty
            self.inboxqueue.append(m_chatmessage)
            if len(self.inboxqueue) == 1:
                self.handle_chatmessage_1(m_chatmessage)
    
    def set_text_color(self):
        textcolor = int(self.m_chatmessage[TEXT_COLOR])
        
        is_rainbow = textcolor == C_RAINBOW
        
        if textcolor == 0:
            color = QtGui.QColor(255, 255, 255)
        elif textcolor == 1:
            color = QtGui.QColor(0, 255, 0)
        elif textcolor == 2:
            color = QtGui.QColor(255, 0, 0)
        elif textcolor == 3:
            color = QtGui.QColor(255, 165, 0)
        elif textcolor == 4:
            color = QtGui.QColor(45, 150, 255)
        elif textcolor == 5:
            color = QtGui.QColor(255, 255, 0)
        elif textcolor == 6:
            color = QtGui.QColor(255, 192, 203)
        elif textcolor == 7:
            color = QtGui.QColor(0, 255, 255)
        elif textcolor == 8:
            color = QtGui.QColor(200, 200, 200)
        elif textcolor == 10:
            color = QtGui.QColor(0, 0, 0)
        else:
            color = QtGui.QColor(255, 255, 255)
        
        if is_rainbow:
            self.text.show()
            self.ao2text.hide()
        else:
            self.text.hide()
            self.ao2text.show()
        
        style = "background-color: rgba(0, 0, 0, 0);\n"
        style += "color: rgb("+str(color.red())+", "+str(color.green())+", "+str(color.blue())+")"
        self.ao2text.setStyleSheet(style)
    
    def set_scene(self, init=False):
        if not init:
            side = self.m_chatmessage[SIDE]
            # TODO: support custom positions
            if side not in self.default_positions:
                side = 'wit'
        else:
            side = 'wit'
        
        if side == 'def':
            self.court.setPixmap(self.side_def)
            self.bench.setPixmap(self.bench_def)
            self.bench.move(0, VIEWPORT_H - self.bench_def.size().height())
            self.presentedevi.move(170, 16)
        elif side == 'pro':
            self.court.setPixmap(self.side_pro)
            self.bench.setPixmap(self.bench_pro)
            self.bench.move(VIEWPORT_W - self.bench_pro.size().width(), VIEWPORT_H - self.bench_pro.size().height())
            self.presentedevi.move(16, 16)
        elif side == 'wit':
            self.court.setPixmap(self.side_wit)
            self.bench.setPixmap(self.bench_wit)
            self.bench.move(0, 0)
            self.presentedevi.move(16, 16)
        elif side == 'hld':
            self.court.setPixmap(self.side_hld)
            self.bench.setPixmap(self.bench_hld)
            self.presentedevi.move(16, 16)
        elif side == 'hlp':
            self.court.setPixmap(self.side_hlp)
            self.bench.setPixmap(self.bench_hlp)
            self.presentedevi.move(170, 16)
        elif side == 'jud':
            self.court.setPixmap(self.side_jud)
            self.bench.setPixmap(self.bench_jud)
            self.presentedevi.move(16, 16)
        elif side == 'sea':
            self.court.setPixmap(self.side_jud if self.side_sea.isNull() else self.side_sea)
            self.bench.setPixmap(self.bench_jud if self.bench_sea.isNull() else self.bench_sea)
            self.presentedevi.move(16, 16)
        elif side == 'jur':
            self.court.setPixmap(self.side_jud if self.side_jur.isNull() else self.side_jur)
            self.bench.setPixmap(self.bench_jud if self.bench_jur.isNull() else self.bench_jur)
            self.presentedevi.move(16, 16)

    def set_desk(self, is_preanim=False):
        deskmod = self.m_chatmessage[DESK_MOD]
        
        if deskmod == "0" or (deskmod == "chat" and side in ("jud", "hld", "hlp")):
            self.bench.hide()
        elif deskmod == "1" or (deskmod == "chat" and side in ("def", "pro", "wit")):
            self.bench.show()
        elif deskmod == "2" or deskmod == "4":
            if is_preanim:
                self.bench.hide()
            else:
                self.bench.show()
        elif deskmod == "3" or deskmod == "5":
            if is_preanim:
                self.bench.show()
            else:
                self.bench.hide()
        else:
            self.bench.hide()

    def objection_done(self):
        self.handle_chatmessage_1()
        
    def handle_chatmessage_1(self, m_chatmessage = None):
        if not self.slide_enabled:
            if m_chatmessage:
                self.m_chatmessage = m_chatmessage
            self.handle_chatmessage_2()
            return
            
        was_zoom = self.m_chatmessage[EMOTE_MOD] and int(self.m_chatmessage[EMOTE_MOD]) >= 5
    
        if m_chatmessage:
            self.m_chatmessage = m_chatmessage
        
        new_side = self.m_chatmessage[SIDE]
        can_slide = self.slide_available and not was_zoom and int(self.m_chatmessage[EMOTE_MOD]) < 5
        
        if can_slide and self.m_chatmessage[SLIDE] == "1" and self.slide_last_pos and new_side != self.slide_last_pos and new_side in ["def", "pro", "wit"]:
            self.slide_start(self.slide_map[self.slide_last_pos][new_side])
        else:
            self.handle_chatmessage_2()

    def handle_chatmessage_2(self):
        self.zoom.set_zoom(False)
        self.char.stop()
        self.effectview.stop()
        
        self.text_state = 0
        self.anim_state = 0
        self.objectionview.stop()
        self.char.stop()
        self.chat_tick_timer.stop()
        self.presentedevi.hide()
        
        self.chatmessage_is_empty = self.m_chatmessage[CHATMSG] == " " or self.m_chatmessage[CHATMSG] == ""

        if not self.m_chatmessage[SHOWNAME]:
            self.name.setText(self.m_chatmessage[CHARNAME])
        else:
            self.name.setText(self.m_chatmessage[SHOWNAME])

        self.chatbox.hide()

        self.set_scene()
        self.set_text_color()
        
        f_message = self.m_chatmessage[CHATMSG]
        if len(f_message) >= 2:
            self.message_is_centered = f_message.startswith("~~")
        else:
            self.ao2text.setAlignment(QtCore.Qt.AlignLeft)
            self.text.setAlignment(QtCore.Qt.AlignLeft)
        
        if self.m_chatmessage[FLIP] == "1":
            self.char.set_flipped(True)
        else:
            self.char.set_flipped(False)
        
        side = self.m_chatmessage[SIDE]
        emote_mod = int(self.m_chatmessage[EMOTE_MOD])

        # AO 2.8: always offset player
        hor_offset = vert_offset = 0

        if "y_offset" in self.features: # AO 2.9
            keyword = "<and>" if "<and>" in self.m_chatmessage[SELF_OFFSET] else "&" # i don't think it's hdf's fault but this is still ridiculous
            offset = self.m_chatmessage[SELF_OFFSET].split(keyword)
            hor_offset = int(offset[0]) if offset[0] else 0
            vert_offset = int(offset[1]) if len(offset) > 1 else 0
        else:
            hor_offset = int(self.m_chatmessage[SELF_OFFSET])
        
        if side == "def":
            if hor_offset > 0 and vert_offset == 0:
                vert_offset = hor_offset / 10
        elif side == "pro":
            if hor_offset < 0 and vert_offset == 0:
                vert_offset = -1 * hor_offset / 10

        self.char.move(VIEWPORT_W * hor_offset / 100, VIEWPORT_H * vert_offset / 100)

        # check if paired
        if not self.m_chatmessage[OTHER_CHARID]:
            self.sidechar.hide()
            self.sidechar.move(0,0)
        else:
            if "effects" in self.features:
                got_other_charid = int(self.m_chatmessage[OTHER_CHARID].split("^")[0])
            else:
                got_other_charid = int(self.m_chatmessage[OTHER_CHARID])

            if got_other_charid > -1: # user is paired
                self.sidechar.show()
                
                pair_order = self.m_chatmessage[OTHER_CHARID].split("^")
                if "effects" in self.features and len(pair_order) > 1:   
                    pair_order = int(pair_order[1])
                else:    
                    pair_order = -1

                hor2_offset = vert2_offset = 0
                if "y_offset" in self.features: # AO 2.9
                    keyword = "<and>" if "<and>" in self.m_chatmessage[OTHER_OFFSET] else "&" # i don't think it's hdf's fault but this is still ridiculous
                    hor2_offset = int(self.m_chatmessage[OTHER_OFFSET].split(keyword)[0])
                    vert2_offset = int(self.m_chatmessage[OTHER_OFFSET].split(keyword)[1]) if len(self.m_chatmessage[OTHER_OFFSET].split(keyword)) > 1 else 0
                else:
                    hor2_offset = int(self.m_chatmessage[OTHER_OFFSET])

                if side == "def":
                    if hor2_offset > 0:
                        vert2_offset = hor2_offset / 10

                elif side == "pro":
                    if hor2_offset < 0:
                        vert2_offset = -1 * hor2_offset / 10

                if pair_order == -1: # pair ordering not supported
                    if hor2_offset >= hor_offset:
                        self.sidechar.raise_()
                        self.char.raise_()
                    else:
                        self.char.raise_()
                        self.sidechar.raise_()
                elif pair_order == 0: # front
                    self.sidechar.raise_()
                    self.char.raise_()
                elif pair_order == 1: # behind
                    self.char.raise_()
                    self.sidechar.raise_()

                self.sidechar.move(VIEWPORT_W * hor2_offset / 100, VIEWPORT_H * vert2_offset / 100)

                self.bench.raise_()
                self.chatbox.raise_()
                self.effectview.raise_()
                self.objectionview.raise_()
                self.whiteflashlab.raise_()
                
                self.scaling[1] = get_scaling(ini.read_ini(AOpath + 'characters/' + self.m_chatmessage[OTHER_NAME] + '/char.ini', "options", "scaling").lower())

                self.sidechar.set_flipped(self.m_chatmessage[OTHER_FLIP] == "1")
                self.sidechar.play_idle(self.m_chatmessage[OTHER_NAME], self.m_chatmessage[OTHER_EMOTE], self.scaling[1])
            
            else:
                self.sidechar.hide()
                self.sidechar.move(0, 0)
                
        self.scaling[0] = get_scaling(ini.read_ini(AOpath + 'characters/' + self.m_chatmessage[CHARNAME] + '/char.ini', "options", "scaling").lower())
        
        if self.slide_enabled and self.slide_available:
            if side == "wit":
                if int(self.m_chatmessage[EMOTE_MOD]) < 5: # Don't save anim if zoom
                    self.slide_last_wit = [
                        self.m_chatmessage[CHARNAME],
                        self.m_chatmessage[ANIM],
                        self.scaling[0]
                        ]
                self.slide_last_pos = "wit"
            elif side == "def" or side == "pro":
                self.slide_last_pos = side
            else:
                self.slide_last_pos = None

        if (emote_mod == 1 or emote_mod == 2 or emote_mod == 6) and self.m_chatmessage[PREANIM] != "-":
            # sfx_delay = int(self.m_chatmessage[SFX_DELAY]) * 60
            # if sfx_delay > 0:
                # self.sfx_delay_timer.start(sfx_delay)
            # else:
                # self.play_sfx()
            self.set_desk(True)
            self.play_preanim(False)
        elif emote_mod == 0 or emote_mod == 5 or self.m_chatmessage[PREANIM] == "-":
            if self.m_chatmessage[NO_INTERRUPT] == "0" or self.m_chatmessage[PREANIM] == "-":
                self.handle_chatmessage_3()
            else:
                self.play_preanim(True)
            
    def play_preanim(self, noninterrupting):
        f_char = self.m_chatmessage[CHARNAME].lower()
        f_preanim = self.m_chatmessage[PREANIM]
        
        ao2_duration = ini.read_ini_int(AOpath+"characters/"+f_char+"/char.ini", "time", f_preanim, -1)
        text_delay = ini.read_ini_int(AOpath+"characters/"+f_char+"/char.ini", "textdelay", f_preanim, -1)
        sfx_delay = int(self.m_chatmessage[SFX_DELAY]) * 60
        
        preanim_duration = ao2_duration
            
        anim_to_find = AOpath+"characters/"+f_char+"/"+f_preanim+".gif"
        apng_to_find = AOpath+"characters/"+f_char+"/"+f_preanim+".apng"
        webp_to_find = AOpath+"characters/"+f_char+"/"+f_preanim+".webp"
        
        if (not anim_to_find and not apng_to_find and not webp_to_find) or preanim_duration < 0:
            if noninterrupting:
                self.anim_state = 4
            else:
                self.anim_state = 1
            self.preanim_done()
        
        self.char.play_pre(f_char, f_preanim, preanim_duration, self.scaling[0])
        if noninterrupting:
            self.anim_state = 4
        else:
            self.anim_state = 1
        
        if sfx_delay > 0:
            self.sfx_delay_timer.start(sfx_delay)
        else:
            self.play_sfx()
        
        if text_delay >= 0:
            pass #text delay timer, but not now.
        
        if noninterrupting:
            self.handle_chatmessage_3()
        
    def preanim_done(self):
        self.anim_state = 1
        self.handle_chatmessage_3()
    
    def handle_chatmessage_3(self):
        self.start_chat_ticking()
        self.set_desk(False)
        
        f_evi_id = int(self.m_chatmessage[EVIDENCE])
        f_side = self.m_chatmessage[SIDE]
        
        emote_mod = int(self.m_chatmessage[EMOTE_MOD])
        
        if f_evi_id > 0 and f_evi_id <= len(self.evidence):
            f_image = self.evidence[f_evi_id-1][2]
            is_left_side = not (f_side == "def" or f_side == "hlp" or f_side == "jud" or f_side == "jur")
            
            self.setEvidenceImg(self.presentedevi, f_image, True)
            self.playSound("sfx-evidenceshoop.opus")
            
            if not is_left_side:
                self.presentedevi.move(170*2, 16*2)
            else:
                self.presentedevi.move(16*2, 16*2)
            self.presentedevi.show()
        else:
            self.presentedevi.hide()
        
        side = self.m_chatmessage[SIDE]
        if emote_mod == 5 or emote_mod == 6:
            self.bench.hide()
            self.sidechar.hide()
            self.char.move(0,0)
            
            if side == "pro" or side == "hlp" or side == "wit":
                self.zoom.set_zoom(True, 1)
            else:
                self.zoom.set_zoom(True, 0)
        
        f_anim_state = 0
        text_is_blue = int(self.m_chatmessage[TEXT_COLOR]) == 4
        
        if not text_is_blue and self.text_state == 1:
            f_anim_state = 2
            self.entire_message_is_blue = False
        else:
            f_anim_state = 3
            self.entire_message_is_blue = True
        
        if f_anim_state <= self.anim_state:
            return
        
        self.char.stop()
        f_char = self.m_chatmessage[CHARNAME]
        f_emote = self.m_chatmessage[ANIM]
        
        if f_anim_state == 2:
            self.char.play_talking(f_char, f_emote, self.scaling[0])
            self.anim_state = 2
        else:
            self.char.play_idle(f_char, f_emote, self.scaling[0])
            self.anim_state = 3
        
        if exists(AO2XPpath+"callwords.ini"):
            with open(AO2XPpath+"callwords.ini") as f: 
                callwords = [line.rstrip() for line in f]
                for callword in callwords:
                    if callword.decode('utf-8').lower() in self.m_chatmessage[CHATMSG].lower().split(" "):
                        self.ooclog.append("<b>%s called you.</b>" % f_char)
                        QtGui.QApplication.alert(self, 1000)
                        snd = audio.loadhandle(False, "word_call.wav", 0, 0, BASS_STREAM_AUTOFREE)
                        if snd:
                            audio.playhandle(snd, True)
                        break

    def do_effect(self, fx_name, fx_sound, p_char, p_folder):
        effect = ini.get_effect(fx_name, p_char, p_folder)
        if not effect: return

        if fx_sound:
            self.playSound(fx_sound)

        if "effects" not in self.features: return

        self.effectview.set_play_once(True)
        self.effectview.play(effect)

    def start_chat_ticking(self):
        if self.text_state != 0:
            return

        if self.m_chatmessage[EFFECTS]:
            fx_list = self.m_chatmessage[EFFECTS].split("|")
            fx = fx_list[0]
            fx_sound = ""
            fx_folder = ""

            if len(fx_list) > 1:
                fx_sound = fx_list[1]
            if len(fx_list) > 2:
                fx_folder = fx_list[1]
                fx_sound = fx_list[2]

            if fx and fx not in ("-", "None"):
                self.do_effect(fx, fx_sound, self.m_chatmessage[CHARNAME], fx_folder)

        elif self.m_chatmessage[REALIZATION] == "1":
            self.setWhiteFlash(True, 1, 125)
        
        self.set_text_color()

        charid = int(self.m_chatmessage[CHAR_ID])
        if not self.is_additive or self.additive_char != charid:
            self.ao2text.clear()
            self.text.setText("")
            self.additive_char = charid

        if self.chatmessage_is_empty:
            self.text_state = 2
            self.inbox_timer.start(self.text_wait_time)
            return
        
        self.inline_color_stack = []
        
        self.chatbox.show()

        self.tick_pos = 0
        self.blip_pos = 0
        self.inline_blue_depth = 0
        
        self.current_display_speed = 3
        self.chat_tick_timer.start(self.message_display_speed[self.current_display_speed])

        self.blip = self.m_chatmessage[BLIPS].lower()
        
        if not self.blip:
            self.blip = self.charlist[charid][2].lower()

        path = test_path(
            AOpath+"sounds/blips/"+self.blip+".wav",
            AOpath+"sounds/blips/"+self.blip+".opus",
            AOpath+"sounds/general/sfx-blip"+self.blip+".wav",
            AOpath+"sounds/general/sfx-blip"+self.blip+".opus"
        )
        
        if path:
            self.blipsnd = audio.loadhandle(False, path, 0, 0, 0)

            if self.blipsnd:
                audio.sethandleattr(self.blipsnd, BASS_ATTRIB_VOL, self.blipslider.value() / 100.0)

        emote_mod = int(self.m_chatmessage[EMOTE_MOD])
        if emote_mod in (0, 5) and self.m_chatmessage[SCREENSHAKE] == "1":
            self.setScreenShake(True)

        self.text_state = 1
    
    def chat_tick(self):
        f_message = self.m_chatmessage[CHATMSG]
        
        self.chat_tick_timer.stop()
        formatting_char = False
        
        if self.message_is_centered:
            f_message = f_message.strip("~~")
        
        if self.tick_pos >= len(f_message):
            self.text_state = 2
            if self.anim_state != 4:
                self.anim_state = 3
                self.char.play_idle(self.m_chatmessage[CHARNAME], self.m_chatmessage[ANIM], self.scaling[0])
            self.inbox_timer.start(self.text_wait_time)
            
        else:
            f_character2 = f_message[self.tick_pos]
            f_character = QtCore.QString(f_character2)

            if f_character == " ":
                self.text.insertPlainText(" ")
                self.ao2text.insertPlainText(" ")
            
            elif f_character == "\n" or f_character == "\r":
                self.text.insertPlainText("\n")
                self.ao2text.insertPlainText("\n")
            
            elif f_character == "\\" and not self.next_character_is_not_special:
                self.next_character_is_not_special = True
                formatting_char = True
            
            elif f_character == "{" and not self.next_character_is_not_special:
                self.current_display_speed += 1
                formatting_char = True
            
            elif f_character == "}" and not self.next_character_is_not_special:
                self.current_display_speed -= 1
                formatting_char = True
            
            elif f_character == "|" and not self.next_character_is_not_special: #orange.
                if self.inline_color_stack:
                    if self.inline_color_stack[-1] == INLINE_ORANGE:
                        del self.inline_color_stack[-1]
                    else:
                        self.inline_color_stack.append(INLINE_ORANGE)
                else:
                    self.inline_color_stack.append(INLINE_ORANGE)
                formatting_char = True
            
            elif f_character == "(" and not self.next_character_is_not_special: #blue.
                self.inline_color_stack.append(INLINE_BLUE)
                html = "<font color=\"" + get_text_color(4).name() + "\">" + f_character + "</font>"
                self.ao2text.insertHtml(html)
                self.text.insertHtml(html)
                
                self.inline_blue_depth += 1
                if not self.entire_message_is_blue and self.anim_state != 4:
                    f_char = self.m_chatmessage[CHARNAME]
                    f_emote = self.m_chatmessage[ANIM]
                    self.char.play_idle(f_char, f_emote, self.scaling[0])
            
            elif f_character == ")" and not self.next_character_is_not_special and self.inline_color_stack:
                if self.inline_color_stack[-1] == INLINE_BLUE:
                    del self.inline_color_stack[-1]
                    html = "<font color=\"" + get_text_color(4).name() + "\">" + f_character + "</font>"
                    self.ao2text.insertHtml(html)
                    self.text.insertHtml(html)
                    
                    if self.inline_blue_depth > 0:
                        self.inline_blue_depth -= 1
                        
                        if not self.entire_message_is_blue:
                            if self.inline_blue_depth == 0 and self.anim_state != 4 and not (self.tick_pos+1 >= len(f_message)):
                                f_char = self.m_chatmessage[CHARNAME]
                                f_emote = self.m_chatmessage[ANIM]
                                self.char.play_talking(f_char, f_emote, self.scaling[0])
                else:
                    self.next_character_is_not_special = True
                    self.tick_pos -= 1
            
            elif f_character == "[" and not self.next_character_is_not_special: #gray.
                self.inline_color_stack.append(INLINE_GRAY)
                html = "<font color=\"" + get_text_color("_inline_grey").name() + "\">" + f_character + "</font>"
                self.ao2text.insertHtml(html)
                self.text.insertHtml(html)
                
            elif f_character == "]" and not self.next_character_is_not_special and self.inline_color_stack:
                if self.inline_color_stack[-1] == INLINE_GRAY:
                    del self.inline_color_stack[-1]
                    html = "<font color=\"" + get_text_color("_inline_grey").name() + "\">" + f_character + "</font>"
                    self.ao2text.insertHtml(html)
                    self.text.insertHtml(html)
                else:
                    self.next_character_is_not_special = True
                    self.tick_pos -= 1
            
            elif f_character == "`" and not self.next_character_is_not_special: #green.
                if self.inline_color_stack:
                    if self.inline_color_stack[-1] == INLINE_GREEN:
                        del self.inline_color_stack[-1]
                    else:
                        self.inline_color_stack.append(INLINE_GREEN)
                else:
                    self.inline_color_stack.append(INLINE_GREEN)
                formatting_char = True

            elif f_character == "~" and not self.next_character_is_not_special: #green.
                if self.inline_color_stack:
                    if self.inline_color_stack[-1] == INLINE_RED:
                        del self.inline_color_stack[-1]
                    else:
                        self.inline_color_stack.append(INLINE_RED)
                else:
                    self.inline_color_stack.append(INLINE_RED)
                formatting_char = True

            elif f_character == "s" and self.next_character_is_not_special: # shake
                self.setScreenShake(True)
                self.next_character_is_not_special = False

            elif f_character == "f" and self.next_character_is_not_special: # flash
                self.setWhiteFlash(True, 0, 75)
                self.next_character_is_not_special = False

            elif f_character == "n" and self.next_character_is_not_special: # newline
                self.text.insertPlainText("\n")
                self.ao2text.insertPlainText("\n")
                self.next_character_is_not_special = False
            
            else:
                self.next_character_is_not_special = False
                if self.inline_color_stack:
                    top_color = self.inline_color_stack[-1]
                    if top_color == INLINE_ORANGE:
                        html = "<font color=\"" + get_text_color(C_ORANGE).name() + "\">" + f_character + "</font>"
                        self.ao2text.insertHtml(html)
                        self.text.insertHtml(html)
                    elif top_color == INLINE_BLUE:
                        html = "<font color=\"" + get_text_color(C_BLUE).name() + "\">" + f_character + "</font>"
                        self.ao2text.insertHtml(html)
                        self.text.insertHtml(html)
                    elif top_color == INLINE_GREEN:
                        html = "<font color=\"" + get_text_color(C_GREEN).name() + "\">" + f_character + "</font>"
                        self.ao2text.insertHtml(html)
                        self.text.insertHtml(html)
                    elif top_color == INLINE_GRAY:
                        html = "<font color=\"" + get_text_color("_inline_grey").name() + "\">" + f_character + "</font>"
                        self.ao2text.insertHtml(html)
                        self.text.insertHtml(html)
                    elif top_color == INLINE_RED:
                        html = "<font color=\"" + get_text_color(C_RED).name() + "\">" + f_character + "</font>"
                        self.ao2text.insertHtml(html)
                        self.text.insertHtml(html)
                    else:
                        self.text.insertHtml(f_character)
                        self.ao2text.insertHtml(f_character)
                else:
                    if int(self.m_chatmessage[TEXT_COLOR]) == C_RAINBOW:
                        self.text.insertHtml(f_character)
                    else:
                        self.ao2text.insertHtml(f_character)
                
                if self.message_is_centered:
                    self.ao2text.setAlignment(QtCore.Qt.AlignCenter)
                    self.text.setAlignment(QtCore.Qt.AlignCenter)
                else:
                    self.ao2text.setAlignment(QtCore.Qt.AlignLeft)
                    self.text.setAlignment(QtCore.Qt.AlignLeft)
            
                if f_message[self.tick_pos] != " " or self.blank_blip:
                    if self.blip_pos % self.blip_rate == 0 and not formatting_char:
                        self.blip_pos = 0
                        if self.blipsnd:
                            audio.playhandle(self.blipsnd, True)
                    
                    self.blip_pos += 1
            
            self.tick_pos += 1
            
            if self.current_display_speed < 0:
                self.current_display_speed = 0
            elif self.current_display_speed > 6:
                self.current_display_speed = 6

            if formatting_char:
                self.chat_tick_timer.start(1)
            else:
                self.chat_tick_timer.start(self.message_display_speed[self.current_display_speed])
                 
    def inbox_timer_timeout(self):
        if len(self.inboxqueue) > 0:
            del self.inboxqueue[0]
            if len(self.inboxqueue) > 0:
                self.handle_chatmessage_1(self.inboxqueue[0])

    def playRealization(self):
        audio.playhandle(self.realizationsnd, True)

    def playObjectionSnd(self, charname, objection):
        try:
            charname = str(charname)
        except:
            print "WARNING: Can't play objection sound if charname is unicode yet"
    
        if self.objectsnd:
            if audio.handleisactive(self.objectsnd):
                audio.stophandle(self.objectsnd)
            audio.freehandle(self.objectsnd)

        objecting = ["holdit", "objection", "takethat", "custom"][objection-1]
        
        if objecting:
            if exists(AOpath + 'characters/' + charname + '/' + objecting + '.wav'):
                self.objectsnd = audio.loadhandle(False, AOpath + 'characters/' + charname + '/' + objecting + '.wav', 0, 0, 0)
            elif exists(AOpath + 'characters/' + charname + '/' + objecting + '.opus'):
                self.objectsnd = audio.loadhandle(False, AOpath + 'characters/' + charname + '/' + objecting + '.opus', 0, 0, 0)
            else:
                self.objectsnd = None
                if ini.read_ini_bool("AO2XP.ini", "General", "download sounds", True):
                    thread.start_new_thread(download_thread, ("base/characters/"+charname+"/"+objecting+".wav", AOpath+"characters/"+charname+"/"+objecting.lower()+".wav"))
                    thread.start_new_thread(download_thread, ("base/characters/"+charname+"/"+objecting+".opus", AOpath+"characters/"+charname+"/"+objecting.lower()+".wav"))

                if exists(AOpath + 'sounds/general/sfx-objection.opus'):
                    self.objectsnd = audio.loadhandle(False, AOpath + 'sounds/general/sfx-objection.opus', 0, 0, 0)
                else:
                    self.objectsnd = audio.loadhandle(False, AOpath + 'sounds/general/sfx-objection.wav', 0, 0, 0)
            audio.sethandleattr(self.objectsnd, BASS_ATTRIB_VOL, self.soundslider.value() / 100.0)
            audio.playhandle(self.objectsnd, True)
    
    def play_sfx(self):
        sfx_name = self.m_chatmessage[SFX]
        if sfx_name == "1" or sfx_name == "0":
            return
        
        self.playSound(sfx_name)

    def playSound(self, sfx):
        if self.sound:
            if audio.handleisactive(self.sound):
                audio.stophandle(self.sound)
            audio.freehandle(self.sound)
            
        path = test_path(AOpath + 'sounds/general/' + sfx, AOpath + 'sounds/general/' + sfx + '.wav', AOpath + 'sounds/general/' + sfx + '.opus')
        
        if path:
            self.sound = audio.loadhandle(False, path, 0, 0, 0)
            audio.sethandleattr(self.sound, BASS_ATTRIB_VOL, self.soundslider.value() / 100.0)
            if self.sound:
                audio.playhandle(self.sound, True)
            
    def playMusic(self, mus):
        if mus == "~stop.mp3":
            self.stopMusic()
            return
        
        if not mus.endswith(".mp3") and "===MUSIC START===.mp3" in self.musiclist: #vidya workaround
            mus += ".mp3"
        musl = mus.lower()
            
        self.stopMusic()
        
        if exists(AOpath + 'sounds/music/' + musl):
            self.music = audio.loadhandle(False, AOpath + 'sounds/music/' + musl, 0, 0, BASS_SAMPLE_LOOP)
            audio.sethandleattr(self.music, BASS_ATTRIB_VOL, self.musicslider.value() / 100.0)
            audio.playhandle(self.music, True)
            if self.musicslider.value() == 0:
                audio.pausehandle(self.music)
            
        elif ini.read_ini_bool("AO2XP.ini", "General", "download music", True):
            if mus.lower().startswith("http"):
                #self.music = audio.loadURLhandle(mus, 0, BASS_STREAM_BLOCK | BASS_SAMPLE_LOOP)
                self.music = audio.loadURLhandle(mus, 0, BASS_SAMPLE_LOOP)
                print "[audio] Trying to play", mus
            else:
                for bucket in buckets:
                    if not bucket: continue
                    print "[audio] Music stream:", bucket+'base/sounds/music/' + mus
                    self.music = audio.loadURLhandle(bucket+'base/sounds/music/' + musl, 0, BASS_STREAM_BLOCK | BASS_SAMPLE_LOOP)
                    if self.music: break

            if self.music:
                audio.sethandleattr(self.music, BASS_ATTRIB_VOL, self.musicslider.value() / 100.0)
                audio.playhandle(self.music, True)
                if self.musicslider.value() == 0:
                    audio.pausehandle(self.music)
            else:
                error = audio.getbasserror()
                #print "[audio] Couldn't play music. Error", error
                # Here comes the evil HTTPS hack for XP systems, but it also allows us to download and play modules and midis, because, why not?
                musext = os.path.splitext(basename(musl))[-1]
                if musext in ['.mid', '.midi']:
                    self.specialstream = 1
                elif musext in ['.xm', '.mod', '.mo3', '.it', '.s3m', '.mtm', '.umx']:
                    self.specialstream = 2
                if (musl.startswith("https") and error == 2) or self.specialstream:
                    print "[audio] Downloading music with urllib2"
                    self.download_thread = MusicDownloadThread(self, mus)
                    self.download_thread.finished_signal.connect(self.playDownloadedMusic)
                    self.download_thread.start()

    def stopMusic(self):
        if self.music:
            if audio.handleisactive(self.music):
                audio.stophandle(self.music)
            if self.specialstream:
                if self.specialstream == 2:
                    audio.freeMOD(self.music)
                self.specialstream = 0
            else:
                audio.freehandle(self.music)
            if self.stream:
                self.stream = None
                
        if self.download_thread:
            self.download_thread.stop()
            #self.download_thread.wait()
            self.download_thread = None
            
    def playDownloadedMusic(self, file_length):
        # Part of the evil HTTPS music download hack for XP systems
        print "[audio] Done downloading; playing stream"
        if self.specialstream == 1:
            self.music = audio.loadMIDI(True, self.stream, 0, file_length, BASS_SAMPLE_LOOP)
        elif self.specialstream == 2:
            self.music = audio.loadMOD(True, self.stream, 0, file_length, BASS_SAMPLE_LOOP)
        else:
            self.music = audio.loadhandle(True, self.stream, 0, file_length, BASS_SAMPLE_LOOP)
        audio.sethandleattr(self.music, BASS_ATTRIB_VOL, self.musicslider.value() / 100.0)
        audio.playhandle(self.music, True)
        if self.musicslider.value() == 0:
            audio.pausehandle(self.music)

    def allMusic(self):
        for song, fname in self.musiclist.items():
            songitem = QtGui.QListWidgetItem()
            songitem.setText(song)
            if exists(unicode(AOpath + 'sounds/music/' + fname.replace("<and>","&").lower())):
                songitem.setBackgroundColor(QtGui.QColor(128, 255, 128))
            #else:
                #songitem.setBackgroundColor(QtGui.QColor(255, 128, 128))
            self.musicitems.addItem(songitem)

    def allEvidence(self, evi):
        self.evidence = evi
        if self.evidencedropdown.count() > 0:
            self.evidencedropdown.clear()
        for evi in self.evidence:
            while len(evi) < 3: # new AO 2.9 bug where they never correctly escaped evidence name/desc/image on FantaProtocol
                evi += [""]
            evi[0] = decode_ao_str(evi[0].decode('utf-8'))
            evi[1] = decode_ao_str(evi[1].decode('utf-8'))
            evi[2] = decode_ao_str(evi[2].decode('utf-8'))
            self.evidencedropdown.addItem(evi[0].strip())

        if not self.evidence:
            self.evidencedropdown.setCurrentIndex(0)
            self.evidencedesc.setText('.')
        else:
            self.evidencedropdown.setCurrentIndex(self.selectedevi)
            
    def updatePlayerList(self, pid, op, utype, data=""):
        if not self.playerlist:
            return
            
        pid = str(pid)
        if op == 0: # Add or remove player
            if utype == 0: # Add a player
                self.player_list.addItem("[%s]" % pid)
                if not pid in self.playerlist:
                    self.playerlist[pid] = ["", "", "", ""]
            if utype == 1: # Remove a player
                item = self.player_list.findItems("[%s]" % pid, QtCore.Qt.MatchStartsWith)
                if item:
                    self.player_list.takeItem(self.player_list.row(item[0]))
                if pid in self.playerlist:
                    del self.playerlist[pid]
        else: # Update a player
            if pid in self.playerlist:
                self.playerlist[pid][utype] = data
                
            item = self.player_list.findItems("[%s]" % pid, QtCore.Qt.MatchStartsWith)
            if item:
                name = self.playerlist[pid][0]
                char = self.playerlist[pid][1]
                charname = self.playerlist[pid][2]
                text = "[%s]" % pid
                if char:
                    text += " %s" % char
                if charname:
                    text += " (%s)" % charname
                if name:
                    text += " %s" % name
                item[0].setText(text)
    
    def start_pause_timers(self, command, timer_id, timer_ms):
        if timer_id > 4:
            return
        
        if command == 0:
            if not self.onscreen_timer.isActive():
                self.onscreen_timer.start(self.timer_tick)
            self.onscreen_timer_times[timer_id] = timer_ms
            self.onscreen_timer_paused[timer_id] = False
            self.update_timers()
            print "[client] Timer %d was started for %d ms" % (timer_id, timer_ms)
        elif command == 1:
            self.onscreen_timer_paused[timer_id] = True
        elif command == 2:
            self.onscreen_timer_labels[timer_id].show()
        elif command == 3:
            self.onscreen_timer_labels[timer_id].hide()
    
    def update_timers(self):
        for timer_id, label in enumerate(self.onscreen_timer_labels):
            time_ms = self.onscreen_timer_times[timer_id]
            if not time_ms or self.onscreen_timer_paused[timer_id]:
                continue
            
            secs = time_ms / 1000
            mins = secs / 60
            hours = mins / 60
            label.setText("%02d:%02d:%02d" % (hours, mins % 60, secs % 60))
            
            self.onscreen_timer_times[timer_id] -= self.timer_tick
            if self.onscreen_timer_times[timer_id] <= 0:
                label.hide()
                self.onscreen_timer_times[timer_id] = 0
                self.onscreen_timer_paused[timer_id] = True
        
        if self.onscreen_timer_times == [0, 0, 0, 0, 0]:
            self.onscreen_timer.stop()

    def on_demo_click(self, item):
        fname = demo.get_demo_fname(self.demoitems, item)
        
        if not fname:
            return
        
        self.player_list.clear()
        self.stopMusic()
        self.iclog.clear()
        self.ooclog.clear()
        
        if not self.demo_playing:
            self.enable_widgets(True)
            self.tcpthread.stop()
            self.demo_playing = True
            
            self.demo_player = demo.DemoPlayer(self)
            self.demo_player.MS_Chat.connect(self.netmsg_ms)
            self.demo_player.newChar.connect(self.onPVPacket)
            self.demo_player.newBackground.connect(self.setBackground)
            self.demo_player.OOC_Log.connect(self.ooclog.append)
            self.demo_player.IC_Log.connect(self.iclog.append)
            self.demo_player.charSlots.connect(partial(self.charselect.setCharList, self.charlist))
            self.demo_player.allEvidence.connect(self.allEvidence)
            self.demo_player.updatePlayerList.connect(self.updatePlayerList)
            self.demo_player.rainbowColor.connect(self.text.setStyleSheet)
            self.demo_player.timerUpdate.connect(self.start_pause_timers)
        
        self.demo_player.start(fname)

    def demo_seek(self, time):
        self.demo_player.seek(time)

    def start_game(self, tcp, playerlist, charlist, musiclist, background, evidence, areas, features=[], oocjoin=[], hplist=[], webAO_bucket=""):
        self.willDisconnect = False
        self.mychar = -1
        self.mychatcolor = 0
        self.tcp = tcp
        self.playerlist = playerlist
        self.charlist = charlist
        self.evidence = evidence
        self.areas = areas
        self.areas_len = len(areas[0])
        self.features = features
        self.musiclist = OrderedDict([])
        
        # We want only song names without paths or extensions in the music list
        for song in musiclist:
            self.musiclist[QtCore.QString(os.path.splitext(basename(song))[0].decode('utf-8').replace("<and>","&"))] = song.decode('utf-8')

        if "base/" in webAO_bucket:
            webAO_bucket = webAO_bucket.replace("base/", "")
        buckets[0] = webAO_bucket
        self.charselect.setCharList(charlist)
        autopick = get_option("General", "auto pick").decode('utf-8').lower()
        coincidence = -1
        for i, char in enumerate(self.charlist):
            if char[0].lower() == autopick and char[1] == 0:
                coincidence = i
                break

        if coincidence > -1:
            self.charselect.selectChar(coincidence)
        else:
            self.charselect.showCharSelect(False)

        # Putting it down here because some servers won't allow you to switch areas without picking a character first
        autojoinarea = get_option("General", "auto join area").decode('utf-8')
        if autojoinarea != "": self.sendMC(autojoinarea)
        
        self.oocnameinput.setText(ini.read_ini("AO2XP.ini", "General", "OOC name", "unnamed"))
        self.shownameedit.setText(ini.read_ini("AO2XP.ini", "General", "Showname"))
        
        self.pairdropdown.clear()
        self.paircheckbox.setChecked(False)
        if "cccc_ic_support" in features:
            self.shownameedit.show()
            self.nointerruptbtn.show()
            self.paircheckbox.setDisabled(False)
            self.paircheckbox.setText("Enable pairing")
            for char in charlist:
                self.pairdropdown.addItem(char[0])
        else:
            self.shownameedit.hide()
            self.nointerruptbtn.hide()
            self.paircheckbox.setDisabled(True)
            self.paircheckbox.setText("This server does not support pairing.")

        self.deskbtn.setDisabled("deskmod" not in features)
        self.flipbutton.setDisabled("flipping" not in features)
        self.customobject.setVisible("customobjections" in features)
        self.ypairoffset.setVisible("y_offset" in features)
        self.ypairoffset_l.setVisible("y_offset" in features)
        
        self.colordropdown.clear()
        self.colordropdown.addItems(['White', 'Green', 'Red', 'Orange', 'Blue'])
        if "yellowtext" in features:
            self.colordropdown.addItems(['Yellow', 'Pink', 'Cyan', 'Gray', 'Rainbow', 'Black'])
        self.colordropdown.setCurrentIndex(self.mychatcolor)
        
        for hp in hplist:
            self.healthbars.emit(hp[0], hp[1])

        for char in self.charlist:
            if not exists(AOpath + 'characters/' + char[0].lower() + '/char.ini'):
                continue
            char[2] = get_char_ini(char[0], "Options", "gender").lower()
            if char[2] == "":
                char[2] = get_char_ini(char[0], "Options", "blips").lower()

        self.realizationbtn.setPressed(False)
        self.customobject.setPressed(False)
        self.mutedlist.clear()
        self.unmutedlist.clear()
        for char in self.charlist:
            self.unmutedlist.addItem(char[0])

        self.musicitems.clear()
        self.areaitems.clear()
        self.evidencedropdown.clear()
        for evi in evidence:
            self.evidencedropdown.addItem(evi[0].strip())

        logstart = '<b>--- Log started on ' + time.ctime() + ' ---</b>'
        if self.ooclog.toPlainText():
            self.ooclog.append("\n"+logstart)
        else:
            self.ooclog.append(logstart)
        if self.iclog.toPlainText():
            self.iclog.append("\n"+logstart)
        else:
            self.iclog.append(logstart)
        
        self.setBackground(background.lower())
        self.set_scene(True)
        self.chatbox.hide()
        
        for msg in oocjoin:
            self.ooclog.append(msg)
        
        if self.areas_len:
            for i in range(self.areas_len):
                areaitem = QtGui.QListWidgetItem()
                self.areaitems.addItem(areaitem)
            self.allMusic()
            self.update_area_list()
        else:
            self.no_arup = True
        
        for pid in playerlist:
            self.updatePlayerList(pid, 0, 0)
            for type in range(len(playerlist[pid])):
                self.updatePlayerList(pid, 1, type, playerlist[pid][type])

        self.musicslider.setValue(ini.read_ini_int("AO2XP.ini", "Audio", "Music volume", 100))
        self.soundslider.setValue(ini.read_ini_int("AO2XP.ini", "Audio", "Sound volume", 100))
        self.blipslider.setValue(ini.read_ini_int("AO2XP.ini", "Audio", "Blip volume", 100))
        
        self.onImportEvidence(True)

        self.tcpthread = TCPThread(self)
        self.tcpthread.MS_Chat.connect(self.netmsg_ms)
        self.tcpthread.newChar.connect(self.onPVPacket)
        self.tcpthread.newBackground.connect(self.setBackground)
        self.tcpthread.OOC_Log.connect(self.ooclog.append)
        self.tcpthread.IC_Log.connect(self.iclog.append)
        self.tcpthread.charSlots.connect(partial(self.charselect.setCharList, self.charlist))
        self.tcpthread.showCharSelect.connect(self.charselect.showCharSelect)
        self.tcpthread.allEvidence.connect(self.allEvidence)
        self.tcpthread.updatePlayerList.connect(self.updatePlayerList)
        self.tcpthread.rainbowColor.connect(self.text.setStyleSheet)
        self.tcpthread.timerUpdate.connect(self.start_pause_timers)
        self.tcpthread.start()
        
        self.demo_playing = False
        self.enable_widgets()
        
        self.start_demo_recorder(background)
        self.icchatinput.setFocus()
    
    def start_demo(self, fname):
        self.playerlist = []
        self.charlist = []
        self.evidence = []
        self.areas = []
        self.areas_len = 0
        self.features = ['noencryption', 'yellowtext', 'prezoom', 'flipping', 'customobjections', 'fastloading', 'deskmod', 'evidence', 'cccc_ic_support', 'arup', 'casing_alerts', 'modcall_reason', 'looping_sfx', 'additive', 'effects', 'y_offset', 'expanded_desk_mods', 'auth_packet', 'custom_blips']
        self.musiclist = OrderedDict([])
        self.charselect.hide()
        self.onPVPacket()
        
        self.setBackground("default")
        self.set_scene(True)
        self.chatbox.hide()
        
        self.musicslider.setValue(ini.read_ini_int("AO2XP.ini", "Audio", "Music volume", 100))
        self.soundslider.setValue(ini.read_ini_int("AO2XP.ini", "Audio", "Sound volume", 100))
        self.blipslider.setValue(ini.read_ini_int("AO2XP.ini", "Audio", "Blip volume", 100))
        
        self.onImportEvidence(True)
    
        self.demo_player = demo.DemoPlayer(self)
        self.demo_player.MS_Chat.connect(self.netmsg_ms)
        self.demo_player.newChar.connect(self.onPVPacket)
        self.demo_player.newBackground.connect(self.setBackground)
        self.demo_player.OOC_Log.connect(self.ooclog.append)
        self.demo_player.IC_Log.connect(self.iclog.append)
        self.demo_player.charSlots.connect(partial(self.charselect.setCharList, self.charlist))
        self.demo_player.allEvidence.connect(self.allEvidence)
        self.demo_player.updatePlayerList.connect(self.updatePlayerList)
        self.demo_player.rainbowColor.connect(self.text.setStyleSheet)
        self.demo_player.timerUpdate.connect(self.start_pause_timers)
    
        self.player_list.clear()
        self.stopMusic()
        self.iclog.clear()
        self.ooclog.clear()
        
        self.changechar.setText('Disconnect')
        self.enable_widgets(True)
        self.demo_playing = True
        self.demo_player.start(fname)
    
    def start_demo_recorder(self, bg=None):
        if ini.read_ini_bool("AO2XP.ini", "General", "record demos", False):
            self.demo_recorder = demo.DemoRecorder()
            self.demo_recorder.start()
            self.demo_recorder.record([["SC"] + [char[0] for char in self.charlist]], encode=True)
            if bg:
                self.demo_recorder.record([["BN", bg, ""]], encode=True)
  
    def enable_widgets(self, demo = False):
        for widget in [
            self.oocinput, self.callmodbtn,
            self.oocnameinput, self.ooclogin, self.gametab_evidence,
            self.gametab_msgqueue, self.gametab_iniswap, self.gametab_mute,
            self.gametab_pair, self.gametab_misc, self.gametab_players,
            self.gametab_music, self.emotedropdown, self.posdropdown,
            self.flipbutton, self.sfxbutton, self.nointerruptbtn,
            self.effectdropdown, self.slidebutton, self.deskbtn,
            self.additivebtn, self.areaitems, self.shownameedit,
            self.colordropdown, self.defensebar.minusbtn, self.prosecutionbar.minusbtn,
            self.defensebar.plusbtn, self.prosecutionbar.plusbtn, self.wtcebtn_1,
            self.wtcebtn_2, self.notguiltybtn, self.guiltybtn,
            self.realizationbtn, self.shakebtn,
            ]:
            widget.setEnabled(not demo)
        self.demoslider.setVisible(demo)
        self.icchatinput.setVisible(not demo)
        
        if demo:
            self.changechar.setText('Disconnect')
        else:
            self.changechar.setText('Switch &character')
            
    def update_area_list(self):
        for i in range(self.areas_len):
            area_players = self.areas[0][i]
            area_status = self.areas[1][i].title()
            area_cm = self.areas[2][i].decode('utf-8')
            area_locked = self.areas[3][i].title()
            area_name = self.areas[4][i].decode('utf-8')
            if area_status == "Casing":
                self.areaitems.item(i).setText("%s\n%s | %s\n%s users | %s" % (area_name, area_status, area_cm, area_players, area_locked))
            else:
                self.areaitems.item(i).setText("%s\n%s\n%s users | %s" % (area_name, area_status, area_players, area_locked))
                
            if area_locked == "Locked":
                self.areaitems.item(i).setIcon(QtGui.QIcon(AO2XPpath + "icons/" + "lock.png"))
            else:
                self.areaitems.item(i).setIcon(QtGui.QIcon(AO2XPpath + "icons/" + "house.png"))