diff --git a/scenes/ai/ai-player.tscn b/scenes/ai/ai-player.tscn
index 8e2c194..99742fd 100644
--- a/scenes/ai/ai-player.tscn
+++ b/scenes/ai/ai-player.tscn
@@ -6,7 +6,5 @@
 [node name="RigidBody" index="0" instance=ExtResource( 1 )]
 
 script = ExtResource( 2 )
-tp_camera = null
-master_only = null
 
 
diff --git a/scenes/ai/heroes/0.tscn b/scenes/ai/heroes/0.tscn
index d84ac67..db5f424 100644
--- a/scenes/ai/heroes/0.tscn
+++ b/scenes/ai/heroes/0.tscn
@@ -4,7 +4,4 @@
 
 [node name="RigidBody" instance=ExtResource( 1 )]
 
-tp_camera = NodePath("TPCamera")
-master_only = NodePath("MasterOnly")
-
 
diff --git a/scripts/ai/ai-player.gd b/scripts/ai/ai-player.gd
new file mode 100644
index 0000000..a02c818
--- /dev/null
+++ b/scripts/ai/ai-player.gd
@@ -0,0 +1,52 @@
+extends "res://scripts/player.gd"
+
+var time = 0
+
+func _ready():
+	._ready()
+	read_recording()
+
+func _physics_process(delta):
+	time += delta
+
+func _integrate_forces(state):
+	play_keys(state)
+
+func read_recording():
+
+	# Gather all existing recordings
+	var possible = []
+	var begin = "%d-%d" % [player_info.level, player_info.hero]
+	var path = "res://recordings/"
+	var dir = Directory.new()
+	dir.open(path)
+	dir.list_dir_begin()
+	while true:
+		var fname = dir.get_next()
+		print(fname)
+		if fname == "":
+			# Indicates end of directory
+			break
+		if fname.begins_with(begin):
+			possible.append(fname)
+	dir.list_dir_end()
+
+	# Now pick a random one
+	var fname = possible[randi() % possible.size()]
+
+	# Read the file into recording.events for later use
+	var frec = File.new()
+	frec.open(path + fname, File.READ)
+	recording = parse_json(frec.get_as_text())
+	frec.close()
+
+func play_keys(phys_state):
+	# states[0] is first state
+	# states[0][0] is first state's time
+	while float(recording[0][0]) <= time:
+		# states[0][1] is first state's STATE
+		var state = recording.pop_front()[1]
+		for i in range(state.size()):
+			state[i] = str2var(state[i])
+		set_status(state)
+	phys_state.integrate_forces()
diff --git a/scripts/lobby.gd b/scripts/lobby.gd
index 73b18a3..38c98b0 100644
--- a/scripts/lobby.gd
+++ b/scripts/lobby.gd
@@ -20,8 +20,8 @@ func setup_options():
 	opts.add('-hero', 'r', 'Your choice of hero (index)')
 	opts.add('-level', 'r', 'Your choice of level (index) - server only!')
 	opts.add('-start-game', false, 'Join as a client and immediately start the game')
-	opts.add('-ai', false, 'Run this client as AI')
-	opts.add('-record', false, 'Record this play for AI later')
+	opts.add('-ai', true, 'Run this client as AI')
+	opts.add('-record', true, 'Record this play for AI later')
 	opts.add('-h', false, "Print help")
 	return opts
 
@@ -209,8 +209,11 @@ sync func pre_configure_game(level):
 	for p in player_info:
 		var hero = player_info[p].hero
 		var player = load("res://scenes/heroes/" + str(hero) + ".tscn").instance()
+		if "is_ai" in player_info[p]:
+			player = load("res://scenes/ai/heroes/" + str(hero) + ".tscn").instance()
 		player.set_name(str(p))
 		player.set_network_master(p)
+		player_info[p].level = level
 		player.player_info = player_info[p]
 		get_node("/root/Level/Players").call_deferred("add_child", player)
 
diff --git a/scripts/player.gd b/scripts/player.gd
index 2899f3d..4b21a44 100644
--- a/scripts/player.gd
+++ b/scripts/player.gd
@@ -22,7 +22,7 @@ var movement_charge = 0.15 # In percent per meter (except when heroes change tha
 const fall_height = -50
 
 var debug_node
-var recording = { "time": 0, "events": [], "spawn": Vector3() }
+var recording = { "time": 0, "states": [], "spawn": Vector3() }
 
 slave var slave_transform = Basis()
 slave var slave_lin_v = Vector3()
@@ -58,40 +58,6 @@ func spawn():
 	set_translation(placement)
 	set_linear_velocity(Vector3())
 
-func event_to_obj(event):
-	var d = {}
-	if event is InputEventMouseMotion:
-		d.relative = {}
-		d.relative.x = event.relative.x
-		d.relative.y = event.relative.y
-		d.type = "motion"
-	if event is InputEventKey:
-		d.scancode = event.scancode
-		d.pressed = event.pressed
-		d.type = "key"
-	if event is InputEventMouseButton:
-		d.button_index = event.button_index
-		d.pressed = event.pressed
-		d.type = "mb"
-	return d
-
-func apply_dict(from, to):
-	if typeof(from) != TYPE_DICTIONARY:
-		return from
-	else:
-		for key in from:
-			to[key] = apply_dict(from[key], to[key])
-		return to
-
-func obj_to_event(d):
-	var e
-	if d.type == "motion": e = InputEventMouseMotion.new()
-	if d.type == "key": e = InputEventKey.new()
-	if d.type == "mb": e = InputEventMouseButton.new()
-	d.erase("type") # Not in the event
-	apply_dict(d, e)
-	return e
-
 func _input(event):
 	if is_network_master():
 		if Input.is_action_just_pressed("switch_hero"):
@@ -99,8 +65,6 @@ func _input(event):
 		# Quit the game:
 		if Input.is_action_pressed("quit"):
 			quit()
-		if "record" in player_info:
-			recording.events.append([recording.time, event_to_obj(event)])
 
 func toggle_mouse_capture():
 	if (Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED):
@@ -115,10 +79,18 @@ func set_rotation():
 	get_node("Yaw").set_rotation(Vector3(0, deg2rad(get_node(tp_camera).cam_yaw), 0))
 	get_node("Yaw/Pitch").set_rotation(Vector3(deg2rad(-get_node(tp_camera).cam_pitch), 0, 0))
 
+func record_status(status):
+	if "record" in player_info:
+		for i in range(status.size()):
+			status[i] = var2str(status[i])
+		recording.states.append([recording.time, status])
+
 func _integrate_forces(state):
 	if is_network_master():
 		control_player(state)
-		rpc_unreliable("set_status", get_status())
+		var status = get_status()
+		rpc_unreliable("set_status", status)
+		record_status(status)
 	set_rotation()
 
 slave func set_status(s):
@@ -198,6 +170,9 @@ func _process(delta):
 		if get_translation().y < fall_height:
 			spawn()
 			switch_hero_interface()
+		
+		if "record" in player_info:
+			recording.time += delta
 
 func switch_hero_interface():
 	# Interface needs the mouse!
@@ -228,6 +203,8 @@ sync func switch_hero(hero):
 
 func _exit_scene():
 	Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
+	if "record" in player_info:
+		write_recording()
 
 # Functions
 # =========
@@ -236,7 +213,7 @@ func write_recording():
 	var save = File.new()
 	var fname = "res://recordings/%d-%d-%d.rec" % [player_info.level, player_info.hero, randi() % 10000]
 	save.open(fname, File.WRITE)
-	save.store_line(to_json(recording))
+	save.store_line(to_json(recording.states))
 	save.close()
 
 # Quits the game: