AO2XP/demo.py
2025-03-11 19:55:29 -03:00

241 lines
7.9 KiB
Python

import ini, packets
import os, time
from PyQt4 import QtCore, QtGui
from bisect import bisect_left
class DemoPlayer(QtCore.QObject):
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()
allEvidence = QtCore.pyqtSignal(list)
rainbowColor = QtCore.pyqtSignal(str)
updatePlayerList = QtCore.pyqtSignal(str, int, int, str)
timerUpdate = QtCore.pyqtSignal(int, int, int)
def __init__(self, parent):
super(DemoPlayer, self).__init__(parent)
self.parent = parent
self.paused = False
self.demo = []
self.demo_length = len(self.demo)
self.time = 0
self.demo_length_ms = 0
self.wait_timer = QtCore.QTimer(self)
self.wait_timer.setSingleShot(True)
self.wait_timer.timeout.connect(self.timer_done)
self.mc = [] # Music changes
self.bn = [] # Background changes
self.last_music = ""
self.last_bg = ""
def start(self, file):
self.paused = True
self.wait_timer.stop()
self.time = 0
self.demo_length_ms = 0
self.demo = []
self.mc = []
self.bn = []
self.last_music = ""
self.last_bg = ""
self.load_demo(file)
print "[client] Started demo playback (%s commands)" % self.demo_length
self.parent.demoslider.setMaximum(self.demo_length - 1)
self.OOC_Log.emit("<b>Demo playback started.</b>")
secs = self.demo_length_ms / 1000
mins = secs / 60
hours = mins / 60
self.OOC_Log.emit("Approximate duration: %02d:%02d:%02d." % (hours, mins % 60, secs % 60))
self.OOC_Log.emit("")
self.step()
def playpause(self):
self.paused = not self.paused
if not self.paused and self.time < self.demo_length:
self.step()
def step(self, skip_wait=False):
if self.time >= self.demo_length:
self.time = 0
return
packet = self.demo[self.time]
self.parent.demoslider.blockSignals(True)
self.parent.demoslider.setValue(self.time)
self.parent.demoslider.blockSignals(False)
self.time += 1
self.paused = False
if packet[0] == "wait":
if skip_wait:
self.time += 1
else:
self.wait_timer.start(int(packet[1]))
return
packets.handle_packets(self, [packet], False)
if self.time < self.demo_length:
self.wait_timer.start(1)
else:
self.OOC_Log.emit("<b>Demo playback finished.</b>")
def seek(self, time):
self.parent.inbox_timer.stop()
self.parent.inboxqueue = []
self.wait_timer.stop()
self.time = time
mc_times = [t[0] for t in self.mc]
t = bisect_left(mc_times, self.time) - 1
if t >= 0:
music = self.mc[t][1][1]
if music != self.last_music:
packets.handle_packets(self, [self.mc[t][1]], False)
self.last_music = music
bn_times = [t[0] for t in self.bn]
t = bisect_left(bn_times, self.time) - 1
if t >= 0:
bg = self.bn[t][1][1]
if bg != self.last_bg:
packets.handle_packets(self, [self.bn[t][1]], False)
self.last_bg = bg
self.step()
def load_demo(self, file):
last_line = ""
time = 0
with open(file) as f:
for line in f:
last_line = last_line + line
if last_line.strip()[-1] == "%":
packet = last_line.split("#")[:-1]
self.demo.append(packet)
if packet[0] == "MC":
self.mc.append((time, packet))
elif packet[0] == "BN":
self.bn.append((time, packet))
elif packet[0] == "wait":
self.demo_length_ms += int(packet[1])
last_line = ""
time += 1
self.demo_length = len(self.demo)
def timer_done(self):
if self.paused:
return
self.step()
def stop(self):
self.paused = True
self.wait_timer.stop()
class DemoRecorder():
def __init__(self):
self.demofile = None
self.lasttime = 0
def start(self):
if not os.path.exists("logs"):
os.mkdir("logs")
currtime = time.localtime()
self.lasttime = time.time() * 1000
self.demofile = "logs/%d-%02d-%02d %02d.%02d.%02d.demo" % (currtime[0], currtime[1], currtime[2], currtime[3], currtime[4], currtime[5])
def record(self, packet, encode=False):
if packet[0][0] in ["FM", "ARUP", "CharsCheck"]:
return
#print packet[0][0]
with open(self.demofile, "a") as demofile:
currtime = time.time() * 1000
diff = currtime - self.lasttime
self.lasttime = currtime
demofile.write(("wait#%d#%%" % diff) + "\n")
line = "#".join(packet[0]) + "#%\n"
if encode:
line = line.encode('utf-8')
demofile.write(line)
class DemoPicker(QtGui.QDialog):
def __init__(self, parent):
super(DemoPicker, self).__init__()
self.setModal(True)
self.gamewindow = parent
self.parent = parent
self.setWindowTitle("Select a demo file...")
self.setFixedSize(480, 512)
self.setWindowIcon(QtGui.QIcon("AO2XP.ico"))
self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint)
self.hide()
self.treeview = get_demo_treeview()
self.treeview.doubleClicked.connect(self.on_demo_click)
self.cancelbtn = QtGui.QPushButton()
self.cancelbtn.setText("Cancel")
self.cancelbtn.clicked.connect(self.hide)
main_layout = QtGui.QVBoxLayout(self)
btns_layout = QtGui.QHBoxLayout()
btns_layout.addWidget(self.cancelbtn, 0, QtCore.Qt.AlignRight)
main_layout.addWidget(self.treeview)
main_layout.addLayout(btns_layout)
self.cancelbtn.setFocus()
def on_demo_click(self, item):
fname = get_demo_fname(self.treeview, item)
if not fname:
return
self.gamewindow.gamewidget.start_demo(fname)
self.gamewindow.stackwidget.setCurrentWidget(self.gamewindow.gamewidget)
self.hide()
def get_demo_treeview():
demo_model = QtGui.QFileSystemModel();
demo_model.setRootPath("logs");
demo_model.setFilter(QtCore.QDir.Files | QtCore.QDir.AllDirs | QtCore.QDir.NoDotAndDotDot)
demo_model.setNameFilters(QtCore.QStringList("*.demo"))
demo_model.setNameFilterDisables(False)
demo_treeview = QtGui.QTreeView()
demo_treeview.setHeaderHidden(True)
demo_treeview.setModel(demo_model);
demo_treeview.setRootIndex(demo_model.index("logs"));
for i in range(1, demo_model.columnCount()):
demo_treeview.hideColumn(i)
return demo_treeview
def get_demo_fname(treeview, item):
model = treeview.model()
if model.isDir(item):
return None
path = QtCore.QStringList()
while item.isValid():
fname = model.fileName(item)
if fname == "logs":
path.insert(0, "logs")
break
path.insert(0, fname)
item = item.parent()
return path.join("/")