# -*- coding: utf-8 -*-
import thread, time, random, traceback, requests, json
from os.path import exists
from PyQt4 import QtGui, QtCore
import hardware
import AOsocket
import ini
from constants import *
class PicButton(QtGui.QAbstractButton):
def __init__(self, pixmap, parent=None):
super(PicButton, self).__init__(parent)
self.pixmap = pixmap
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.drawPixmap(event.rect(), self.pixmap)
def sizeHint(self):
return self.pixmap.size()
def setPixmap(self, pixmap):
self.pixmap = pixmap
class Lobby(QtGui.QWidget):
mainWindow = None
tab = 0
def __init__(self, parent=None, demo=None):
super(Lobby, self).__init__(parent)
self.canConnect = False
self.svclicked = None
self.mainWindow = parent
self.pixLobby = QtGui.QPixmap(AO2XPpath+'themes/default/lobbybackground.png')
self.pixBtnPublic = QtGui.QPixmap(AO2XPpath+'themes/default/publicservers.png')
self.pixBtnFavs = QtGui.QPixmap(AO2XPpath+'themes/default/favorites.png')
self.pixBtnRefresh = QtGui.QPixmap(AO2XPpath+'themes/default/refresh.png')
self.pixBtnAddfav = QtGui.QPixmap(AO2XPpath+'themes/default/addtofav.png')
self.pixBtnConnect = QtGui.QPixmap(AO2XPpath+'themes/default/connect.png')
self.pixConnecting = QtGui.QPixmap(AO2XPpath+'themes/default/loadingbackground.png')
self.autoconnect = None
if exists('serverlist.txt'):
with open('serverlist.txt') as file:
self.favoriteslist = [line.rstrip().split(':') for line in file]
autoconnectId = ini.read_ini_int("AO2XP.ini", "General", "auto connect", -1)
if autoconnectId >= 0:
try:
self.autoconnect = self.favoriteslist[autoconnectId]
except:
print "[debug] Can't autoconnect to server."
else:
self.favoriteslist = []
#self.favoriteslist = ['127.0.0.1:27017:your server (port 27017)'.split(':'), '127.0.0.1:27016:your server (port 27016)'.split(':'),]
self.lobbyimg = QtGui.QLabel(self)
self.lobbyimg.setPixmap(self.pixLobby)
self.lobbyimg.show()
self.connectingimg = QtGui.QLabel(self)
self.connectingimg.setPixmap(self.pixConnecting)
self.connectingimg.hide()
self.btnSettings = QtGui.QPushButton(self)
self.btnSettings.setText("Settings")
self.btnSettings.resize(self.btnSettings.sizeHint())
self.btnSettings.move(self.pixLobby.size().width() - self.btnSettings.size().width(), 0)
self.btnSettings.clicked.connect(self.onSettingsClicked)
self.btnDemo = QtGui.QPushButton(self)
self.btnDemo.setText("Play a demo")
self.btnDemo.resize(self.btnDemo.sizeHint())
self.btnDemo.move(self.btnSettings.x() - self.btnDemo.size().width(), 0)
self.btnDemo.clicked.connect(self.onDemoClicked)
self.btnPublic = PicButton(self.pixBtnPublic, self)
self.btnPublic.resize(self.btnPublic.sizeHint())
self.btnPublic.move(46, 88)
self.btnPublic.clicked.connect(self.onClickedPublic)
self.btnFavs = PicButton(self.pixBtnFavs, self)
self.btnFavs.resize(self.btnFavs.sizeHint())
self.btnFavs.move(164, 88)
self.btnFavs.clicked.connect(self.onClickedFavs)
self.btnRefresh = PicButton(self.pixBtnRefresh, self)
self.btnRefresh.resize(self.btnRefresh.sizeHint())
self.btnRefresh.move(56, 381)
self.btnRefresh.clicked.connect(self.onClickedRefresh)
self.btnAddfav = PicButton(self.pixBtnAddfav, self)
self.btnAddfav.resize(self.btnAddfav.sizeHint())
self.btnAddfav.move(194, 381)
self.btnAddfav.clicked.connect(self.onClickedAddfav)
self.btnConnect = PicButton(self.pixBtnConnect, self)
self.btnConnect.resize(self.btnConnect.sizeHint())
self.btnConnect.move(332, 381)
self.btnConnect.clicked.connect(self.onClickedConnect)
self.serverlist = QtGui.QListWidget(self)
self.serverlist.resize(286, 240)
self.serverlist.move(20, 125)
p = self.serverlist.viewport().palette()
p.setColor(self.serverlist.viewport().backgroundRole(), QtGui.QColor(114,114,114))
self.serverlist.viewport().setPalette(p)
self.serverlist.itemClicked.connect(self.onClickedServerlist)
self.onlineplayers = QtGui.QLabel(self)
self.onlineplayers.setStyleSheet('color: white')
self.onlineplayers.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignTop)
self.onlineplayers.setText(random.choice(['Hi', 'Oh, welcome back', 'Hello', 'Click on a server to begin', 'Yo, how are you doing?']))
self.onlineplayers.move(336, 91)
self.onlineplayers.resize(173, 16)
self.serverinfo = QtGui.QTextEdit(self)
self.serverinfo.setReadOnly(True)
p = self.serverinfo.viewport().palette()
p.setColor(self.serverinfo.viewport().backgroundRole(), QtGui.QColor(0,0,0))
self.serverinfo.viewport().setPalette(p)
self.serverinfo.setTextColor(QtGui.QColor("white"))
self.serverinfo.move(337, 109)
self.serverinfo.resize(173, 245)
self.connectcancel = QtGui.QPushButton(self)
self.connectcancel.setText('Cancel')
self.connectcancel.resize(80, 20)
self.connectcancel.move(220, 220)
self.connectcancel.clicked.connect(self.onClickedCancelconnect)
self.connectcancel.hide()
self.actualServerlist = []
self.lobbychatlog = QtGui.QTextEdit(self)
self.lobbychatlog.setReadOnly(True)
self.lobbychatlog.setGeometry(2, 445, 513, 198)
p = self.lobbychatlog.viewport().palette()
p.setColor(self.lobbychatlog.viewport().backgroundRole(), QtGui.QColor(139,139,139))
self.lobbychatlog.viewport().setPalette(p)
self.lobbychatlog.textChanged.connect(self.lobbychatlogUpdate)
self.whitecolor = QtGui.QColor(255, 255, 255)
self.font = QtGui.QFont()
self.font.setFamily(QtCore.QString.fromUtf8('Arial'))
self.font.setBold(True)
self.font.setPointSize(14)
self.connectprogress = QtGui.QLabel(self)
self.connectprogress.hide()
self.connectprogress.setStyleSheet('color: rgb(255, 128, 0);')
self.connectprogress.setFont(self.font)
self.connectprogress.setText('Connecting...')
self.connectprogress.resize(300, 95)
self.connectprogress.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignTop)
self.connectprogress.move(135-20, 92)
self.aoserverinfo = AOServerInfo()
self.aoserverinfo.moveToGameSignal.connect(self.mainWindow.showGame)
self.aoserverinfo.msgboxSignal.connect(self.showMessageBox)
self.aoserverinfo.setOnlinePlayers.connect(self.onlineplayers.setText)
self.aoserverinfo.returnToLobby.connect(self.onClickedCancelconnect)
self.aoserverinfo.setConnectProgress.connect(self.connectprogress.setText)
self.aoserverinfo.readySoon.connect(self.connectcancel.hide)
self.aoserverinfo.setWindowTitle.connect(self.setTitle)
self.aoserverinfo.canConnect.connect(self.setCanConnect)
self.masterserver = MasterServer()
self.masterserver.gotServers.connect(self.onGetServers)
self.masterserver.gotOOCMsg.connect(self.newOOCMessage)
self.masterserver.msgboxSignal.connect(self.showMessageBox)
self.masterserver.start()
if not demo and self.autoconnect and not self.mainWindow.tabsEnabled:
self.aoserverinfo.setIP(self.autoconnect[-1], self.autoconnect[0], self.autoconnect[1], self.autoconnect[2], self.autoconnect[3])
print '[debug]', 'Connecting automatically to ip: ' + self.autoconnect[0] + ', port: ' + str(self.autoconnect[1]) + ", websocket port: " + str(self.autoconnect[2]) + ", secure websocket port: " + str(self.autoconnect[3])
self.aoserverinfo.stop()
self.aoserverinfo.start()
def setCanConnect(self):
self.canConnect = True
if self.autoconnect:
self.autoconnect = None # We only want to autoconnect on first login after all
self.onClickedConnect()
def onGetServers(self, servers):
if self.tab == 0: self.serverlist.clear()
self.actualServerlist = []
del servers[0]
del servers[-1]
for server in servers:
name = server["name"]
desc = server["description"]
ip = server["ip"]
port = server["port"]
wsPort = server["ws_port"] if "ws_port" in server else 0
wssPort = server["wss_port"] if "wss_port" in server else 0
serveritem = QtGui.QListWidgetItem(name)
if self.tab == 0: self.serverlist.addItem(serveritem)
self.actualServerlist.append((ip, port, name, desc, wsPort, wssPort))
def onSettingsClicked(self):
self.mainWindow.showSettings()
def onDemoClicked(self):
self.mainWindow.showDemoPicker()
def onClickedPublic(self):
self.tab = 0
self.serverlist.clear()
for sv in self.actualServerlist:
self.serverlist.addItem(QtGui.QListWidgetItem(sv[2]))
def onClickedFavs(self):
self.tab = 1
self.serverlist.clear()
self.serverinfo.setText("")
for sv in self.favoriteslist:
self.serverlist.addItem(QtGui.QListWidgetItem(sv[-1]))
def onClickedRefresh(self):
self.serverlist.clear()
if self.tab == 0:
try:
self.masterserver.start()
except Exception as e:
print "[debug] Failed to refresh server list:", e
self.lobbychatlog.append('Failed to refresh server list.')
elif self.tab == 1:
if exists('serverlist.txt'):
with open('serverlist.txt') as file:
self.favoriteslist = [ line.rstrip().split(':') for line in file ]
else:
self.favoriteslist = ['127.0.0.1:27017:your server (port 27017)'.split(':'), '0.0.0.0:27017:serverlist.txt not found on base folder'.split(':')]
def onClickedAddfav(self):
if self.tab == 1 or not self.svclicked:
if self.tab == 1:
QtGui.QMessageBox.information(self, "???", "Wait. That's illegal.")
return
for i in range(self.serverlist.count()):
if self.serverlist.item(i) == self.svclicked:
ip = self.actualServerlist[i][0]
port = str(self.actualServerlist[i][1])
name = self.actualServerlist[i][2]
ws = self.actualServerlist[i][4]
wss = self.actualServerlist[i][5]
for sv in self.favoriteslist:
if sv[0] == ip and sv[1] == port:
return QtGui.QMessageBox.information(self, "AO2XP", "This server already exists in your favorites list, named '%s'" % sv[4])
self.favoriteslist.append([ip, port, ws, wss, name])
with open('serverlist.txt', "a") as file:
file.write("%s:%s:%s:%s:%s\n" % (ip, port, ws, wss, name))
file.close()
QtGui.QMessageBox.information(self, "AO2XP", "The server '%s' was added to your favorites." % name)
def onClickedConnect(self):
if not self.canConnect:
return
self.btnPublic.hide()
self.btnFavs.hide()
self.btnRefresh.hide()
self.btnAddfav.hide()
self.btnConnect.hide()
self.serverlist.hide()
self.onlineplayers.hide()
self.serverinfo.hide()
self.btnSettings.hide()
self.btnDemo.hide()
self.connectprogress.setText('Connecting...')
self.connectingimg.show()
self.connectcancel.show()
self.connectprogress.show()
self.aoserverinfo.tcp.send('askchaa#%')
def onClickedCancelconnect(self):
self.connectingimg.hide()
self.connectcancel.hide()
self.connectprogress.hide()
self.btnPublic.show()
self.btnFavs.show()
self.btnRefresh.show()
self.btnAddfav.show()
self.btnConnect.show()
self.serverlist.show()
self.onlineplayers.show()
self.serverinfo.show()
self.btnSettings.show()
self.btnDemo.show()
self.aoserverinfo.stop()
self.canConnect = False
def onClickedServerlist(self, item):
self.svclicked = item
self.canConnect = False
self.onlineplayers.setText('Retrieving...')
text = item.text()
print '[debug]', 'Selected %s' % text
for i in range(self.serverlist.count()):
if self.serverlist.item(i) == item:
if self.tab == 0:
self.serverinfo.setText(self.actualServerlist[i][3])
self.aoserverinfo.setIP(text, self.actualServerlist[i][0], self.actualServerlist[i][1], self.actualServerlist[i][4], self.actualServerlist[i][5])
print '[debug]', 'ind: ' + str(i) + ', ip: ' + self.actualServerlist[i][0] + ', port: ' + str(self.actualServerlist[i][1]) + ", websocket port: " + str(self.actualServerlist[i][4]) + ", secure websocket port: " + str(self.actualServerlist[i][5])
elif self.tab == 1:
self.aoserverinfo.setIP(text, self.favoriteslist[i][0], self.favoriteslist[i][1], self.favoriteslist[i][2], self.favoriteslist[i][3])
print '[debug]', 'ind: ' + str(i) + ', ip: ' + self.favoriteslist[i][0] + ', port: ' + str(self.favoriteslist[i][1]) + ", websocket port: " + str(self.favoriteslist[i][2]) + ", secure websocket port: " + str(self.favoriteslist[i][3])
self.aoserverinfo.stop()
self.aoserverinfo.start()
def lobbychatlogUpdate(self):
if self.lobbychatlog.verticalScrollBar().value() == self.lobbychatlog.verticalScrollBar().maximum(): self.lobbychatlog.verticalScrollBar().setValue(self.lobbychatlog.verticalScrollBar().maximum())
def newOOCMessage(self, text):
self.lobbychatlog.append(text)
def showMessageBox(self, type, title, message):
if type == 0: #critical
return QtGui.QMessageBox.critical(self, title, message)
elif type == 1: #information
return QtGui.QMessageBox.information(self, title, message)
elif type == 2: #question
return QtGui.QMessageBox.question(self, title, message)
elif type == 3: #warning
return QtGui.QMessageBox.warning(self, title, message)
def setTitle(self, title):
if self.mainWindow.tabsEnabled:
self.mainWindow.gameTabs.setTabText(self.mainWindow.currentTab, title)
else:
self.mainWindow.setWindowTitle("AO2XP: " + title)
class MasterServer(QtCore.QThread):
gotServers = QtCore.pyqtSignal(list)
gotOOCMsg = QtCore.pyqtSignal(str)
msgboxSignal = QtCore.pyqtSignal(int, str, str)
def __init__(self):
super(MasterServer, self).__init__()
def run(self):
try:
self.msHttp = requests.get("http://servers.aceattorneyonline.com/servers")
self.msMotd = requests.get("http://servers.aceattorneyonline.com/motd")
if self.msHttp.ok: self.gotServers.emit(json.loads(self.msHttp.content))
if self.msMotd.ok: self.gotOOCMsg.emit(self.msMotd.content)
except Exception as e:
print "[debug] Failed to load server list:", e
QtGui.QMessageBox.critical(None, "Error", "Failed to load the master server list. Please check your internet connection and try again.")
class AOServerInfo(QtCore.QThread):
moveToGameSignal = QtCore.pyqtSignal(list)
msgboxSignal = QtCore.pyqtSignal(int, str, str)
setOnlinePlayers = QtCore.pyqtSignal(str)
setConnectProgress = QtCore.pyqtSignal(str)
returnToLobby = QtCore.pyqtSignal()
readySoon = QtCore.pyqtSignal()
setWindowTitle = QtCore.pyqtSignal(str)
canConnect = QtCore.pyqtSignal()
def __init__(self):
super(AOServerInfo, self).__init__()
self.ip = ""
self.port = 0
self.wsPort = 0
self.wssPort = 0
self.name = "jm"
self.webAOBucket = ""
self.useWS = False
self.connected = False
self.disconnect = False
self.musicHack = False
def setIP(self, name, ip, port, wsPort=0, wssPort=0):
self.ip = ip
self.port = int(port)
self.wsPort = int(wsPort)
self.wssPort = int(wssPort)
self.name = name
def stop(self):
self.disconnect = True
if self.isRunning():
if self.tcp:
self.tcp.close()
self.tcp = None
self.quit()
def run(self):
self.disconnect = False
try:
if self.wsPort == 0 and self.wssPort == 0: raise Exception # make it jump to except: and use TCP
self.tcp = AOsocket.AOwebSocket()
self.connected = self.tcp.connect(self.ip, self.wsPort, self.wssPort)
except:
self.tcp = AOsocket.AOtcpSocket()
try:
print "[debug]", "Trying TCP..."
self.connected = self.tcp.connect(self.ip, self.port)
except:
self.setOnlinePlayers.emit("Couldn't retrieve players")
return
print "[debug]", "Connected! websocket: %s" % self.tcp.isWS + " (secure)" if (self.tcp.isWS and self.tcp.isSecure) else ""
self.tcp.sock.settimeout(0.1)
gotStuff = False
gotChars = False
hpList = []
joinOOC = []
areas = [[], [], [], [], []]
features = []
evidence = []
playerList = {}
musicList = []
pingtimer = 150
readytick = -1
gotARUP = False
while True:
if self.disconnect:
self.tcp.close()
return
pingtimer -= 1
if pingtimer == 0:
pingtimer = 150
self.tcp.send('CH#%')
if readytick > -1:
readytick -= 1
if readytick == 0:
readytick = -1
try:
self.moveToGameSignal.emit([self.tcp, playerList, charList, musicList, background, evidence, areas, features, joinOOC, hpList, self.webAOBucket])
except Exception as err:
self.msgboxSignal.emit(0, "Error caught while loading", traceback.format_exc(err))
self.returnToLobby.emit()
return
self.setWindowTitle.emit(self.name)
return
error, totals = self.tcp.recv()
if error == -2: # timeout
continue
elif error == -1: # disconnected
self.setOnlinePlayers.emit("Something wrong happened" if not gotStuff else "Connection lost")
return
for network in totals:
if not network:
print "[debug] Received empty packet"
continue
header = network[0]
if header == 'PN':
players = int(network[1])
maxplayers = int(network[2])
self.canConnect.emit()
self.setOnlinePlayers.emit('%d/%d players online' % (players, maxplayers))
gotStuff = True
elif header == "decryptor":
self.tcp.send("HI#%s#%%" % hardware.get_hdid())
elif header == "ASS": # ha ha ha...
self.webAOBucket = network[1]
print "[debug] URL:", self.webAOBucket
elif header == "ID":
self.tcp.send("ID#AO2XP#%s#%%" % "2.11.0") # need to send this to tsuserver3 servers in order to get feature list (FL)
elif header == "FL":
features = network[1:]
# print features
elif header == 'BD':
reason = network[1].decode("utf-8") if len(network) > 1 else "Failed to receive ban reason (old server version?)" # new in AO2 2.6
self.setOnlinePlayers.emit('Banned')
self.msgboxSignal.emit(0, "Banned", "Reason:\n"+reason)
self.tcp.close()
return
elif header == 'SI':
if self.disconnect:
continue
maxchars = int(network[1])
maxevidence = int(network[2])
maxmusic = int(network[3])
self.setConnectProgress.emit('Requesting character list (%d)...' % maxchars)
self.tcp.send('RC#%')
print '[client]', '%d characters, %d pieces of evidence and %d songs' % (maxchars, maxevidence, maxmusic)
elif header == 'SC':
if self.disconnect:
continue
del network[0]
gotChars = True
charList = [ [char.split('&')[0].decode('utf-8'), 0, "male", True ] for char in network ]
# Disable characters not found in filesystem
for char in charList:
if not exists(BASE_PATH + "characters/" + char[0].lower()):
char[3] = False
self.setConnectProgress.emit('Requesting music list (%d)...' % maxmusic)
self.tcp.send('RM#%')
print '[client]', 'Received characters (%d)' % len(charList)
elif header == 'SM':
if self.disconnect:
continue
del network[0]
musicList = [music for music in network]
self.setConnectProgress.emit('Finishing...')
self.tcp.send('RD#%')
print '[client]', 'Received songs (%d)' % len(musicList)
elif header == 'CharsCheck':
if self.disconnect or not gotChars:
continue
del network[0]
for i in range(len(network)):
charList[i][1] = int(network[i])
elif header == 'BN':
if self.disconnect:
continue
background = network[1]
print '[client]', 'Courtroom background: %s' % background
elif header == 'LE':
if self.disconnect:
continue
del network[0]
if len(network) > 0:
evidence = [evi.split("&") for evi in network]
else:
evidence = []
for evi in evidence:
while len(evi) < 3: # new AO 2.9 bug where they never correctly escaped evidence name/desc/image on FantaProtocol
evi += [""]
evi[0] = decodeAOString(evi[0].decode("utf-8"))
evi[1] = decodeAOString(evi[1].decode("utf-8"))
evi[2] = decodeAOString(evi[2].decode("utf-8"))
print '[client]', 'Received evidence'
elif header == 'HP':
if self.disconnect:
continue
kind = int(network[1])
health = int(network[2])
hpList.append([kind, health])
#AO2 2.6 new feature: area update. Not all servers send this during the handshake!
elif header == "ARUP":
del network[0]
kind = int(network[0])
del network[0]
areas[kind] = [network[i] for i in range(len(network))]
areasLen = len(areas[kind])
for i in range(areasLen):
for j in range(4):
if j != kind and not len(areas[j]):
areas[j] = ["" for i in range(len(network))]
if not gotARUP:
print '[client]', 'The server has %d areas' % areasLen
gotARUP = True
if musicList and not self.musicHack and not len(areas[4]):
for i in range(areasLen):
areas[4].append(musicList[0])
del musicList[0]
elif header == 'DONE':
if self.disconnect:
continue
self.setConnectProgress.emit('Done, loading...')
self.readySoon.emit()
print '[client]', 'Finished requesting data. Loading game...'
readytick = 4
elif header == 'CT':
if self.disconnect:
continue
name = decodeAOString(network[1].decode('utf-8'))
chatmsg = decodeAOString(network[2].decode('utf-8').replace("\n", "
"))
joinOOC.append("%s: %s" % (name, chatmsg.replace("<", "<").replace("<br />","
") if len(network) > 3 and network[3] == "0" else chatmsg))
elif header == 'PU':
del network[0]
pid = network[0]
if not pid in playerList:
playerList[pid] = []
playerList[pid].append(network[2].decode('utf-8'))
# Evil hack to support some servers that don't send music list as SC packet
else:
if self.disconnect:
continue
if "mp3" not in network[0] and "opus" not in network[0]:
continue
self.musicHack = True
musicList = [music for music in network]
self.setConnectProgress.emit('Finishing...')
self.tcp.send('RD#%')
print '[client]', 'Received songs (%d)' % len(musicList)