Skip to content

Instantly share code, notes, and snippets.

@trysdyn
Last active March 3, 2017 00:08
Show Gist options
  • Save trysdyn/3d9dc8c9951e7ce87227c9d302bcd488 to your computer and use it in GitHub Desktop.
Save trysdyn/3d9dc8c9951e7ce87227c9d302bcd488 to your computer and use it in GitHub Desktop.
This is a python (2.7) script that utilizes datafiles saved by Copy Kitty version 2.x to feed split data into LiveSplit. Requires Python 2.7, Livesplit, Livesplit Server, and Copy Kitty 2.x. 1.9 may work but is not supported.
# Copy Kitty 2.x IGT auto-splitter
# Utilizes LiveSplit and its Server component to auto-split a Copy Kitty run
# Built for version 2.1, should work on anything 2.x
### BEGIN USER-ACCESSIBLE CONFIGS ###
# Edit this to be the directory in which your saves are stored
infile = "D:\\steam\\steamapps\\common\\copy kitty\\saves + levels"
# If this is empty, the script will split every stage
# If you put something here, it will only split AFTER that stage
split_at = []
# This will split on every stage in world 1
#split_at = [1, 2, 3, 4, 5, 6, 7]
### END USER-ACCESSIBLE CONFIGS, THERE BE DRAGONS BEYOND ###
import time
import struct
import socket
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class BokiHandler(FileSystemEventHandler):
# Data locations in the bytestream in the savefile
handle = {"started": 0, "stage": 2, "frames": 9, "seconds": 10,
"minutes": 11, "hours": 12, "days": 13, "weeks": 14}
last_stage = -1
def on_modified(self, event):
# If the splitfile got edited, parse it
if event.src_path.endswith("split"):
data = self.parse_savefile(event.src_path)
# If we started the first stage, send starttimer and return
# This makes Real Time accurate too, though we don't care about RTA
if data["started"]:
s.send("reset\r\n")
s.send("starttimer\r\n")
print "Starting Real Timer"
return
# If the file updated but stage didn't change, something went odd
# abort in this case without doing anything
if data["stage"] == self.last_stage:
return
# Record what stage we just finished
self.last_stage = data["stage"]
# Figure out if we want to split on this stage
# By default we split every stage. If split_at is populated, we only
# split if the listed stages are completed instead
if len(split_at) and data["stage"] not in split_at:
return
# Figure out how many seconds in gametime we're in
sec = ((data["weeks"] * 86400 * 7) +
(data["days"] * 86400) +
(data["hours"] * 3600) +
(data["minutes"] * 60) +
data["seconds"] +
(data["frames"] * (1 / 60.0)))
# Send the split command to livesplit
print "Sending split at %02i:%02i:%02i" % (data["hours"],
data["minutes"],
data["seconds"])
s.send("setgametime %f\r\n" % sec)
s.send("split\r\n")
def parse_savefile(self, filename):
# Datafile format:
# bool: Are we on the starting stage?
# int: starting stage
# int: last finished stage
# 6 ints: stage time in frames, sec, min, hr, dy, wk
# 6 ints: total time in frames, sec, min, hr, dy, wk
with open(filename, 'rb') as f:
data = struct.unpack("<b" + "i" * 14, f.read())
# Use the information in handle dict to construct a data dict
packed_data = {}
for k, v in self.handle.items():
packed_data[k] = data[v]
return packed_data
if __name__ == "__main__":
s = socket.socket()
# Fire up our livesplit socket
try:
s.connect(("localhost", 16834))
print "Connected to LiveSplit"
except:
print "Could not connect to LiveSplit. Is it running?"
# Here we set a hook to fire BokiHandler.on_modified when files
# in CopyKitty's save dir change.
event_handler = BokiHandler()
observer = Observer()
observer.schedule(event_handler, path=infile, recursive=False)
observer.start()
# Now we check once a second for a ctrl-C to terminate
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print "Ctrl-C caught. Exiting"
finally:
observer.stop()
s.close()
observer.join()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment