diff --git a/scenes/ai/ai-player.tscn b/scenes/ai/ai-player.tscn new file mode 100644 index 0000000..99742fd --- /dev/null +++ b/scenes/ai/ai-player.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://scenes/player.tscn" type="PackedScene" id=1] +[ext_resource path="res://scripts/ai/ai-player.gd" type="Script" id=2] + +[node name="RigidBody" index="0" instance=ExtResource( 1 )] + +script = ExtResource( 2 ) + + diff --git a/scenes/ai/heroes/0.tscn b/scenes/ai/heroes/0.tscn new file mode 100644 index 0000000..6a4e274 --- /dev/null +++ b/scenes/ai/heroes/0.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://scenes/heroes/0.tscn" type="PackedScene" id=1] +[ext_resource path="res://scripts/ai/ai-player.gd" type="Script" id=2] + +[node name="RigidBody" index="0" instance=ExtResource( 1 )] + +[node name="AIController" type="Node" parent="." index="7"] + +pause_mode = 2 +script = ExtResource( 2 ) +_sections_unfolded = [ "Pause" ] + + diff --git a/scenes/heroes/0.tscn b/scenes/heroes/0.tscn index 311cf4a..2043cec 100644 --- a/scenes/heroes/0.tscn +++ b/scenes/heroes/0.tscn @@ -3,16 +3,12 @@ [ext_resource path="res://scenes/player.tscn" type="PackedScene" id=1] [ext_resource path="res://scripts/heroes/0.gd" type="Script" id=2] -[node name="RigidBody" instance=ExtResource( 1 )] +[node name="RigidBody" index="0" instance=ExtResource( 1 )] contacts_reported = 1 contact_monitor = true script = ExtResource( 2 ) -[node name="Ray" parent="Yaw/Pitch" index="1"] - -_sections_unfolded = [ "Pause", "Transform", "Visibility" ] - [node name="Crosshair" parent="MasterOnly" index="0"] text = "" diff --git a/scenes/player.tscn b/scenes/player.tscn index 976ce4c..e34dc12 100644 --- a/scenes/player.tscn +++ b/scenes/player.tscn @@ -60,8 +60,6 @@ angular_velocity = Vector3( 0, 0, 0 ) angular_damp = -1.0 script = ExtResource( 1 ) _sections_unfolded = [ "Angular", "Axis Lock", "Collision", "Linear", "Transform", "Visibility", "collision" ] -tp_camera = NodePath("TPCamera") -master_only = NodePath("MasterOnly") [node name="Body" type="CollisionShape" parent="." index="0"] diff --git a/scripts/ai/ai-player.gd b/scripts/ai/ai-player.gd new file mode 100644 index 0000000..1c7b345 --- /dev/null +++ b/scripts/ai/ai-player.gd @@ -0,0 +1,73 @@ +extends Node + +var recording +var time + +func _ready(): + if is_network_master(): + print("readdyyyyy") + read_recording() + time = 0 + get_node("..").set_translation(str2var(recording.spawn)) + set_physics_process(true) + +func _physics_process(delta): + if is_network_master(): + time += delta + play_keys() + +func read_recording(): + + # Gather all existing recordings + var possible = [] + var begin = "%d-%d" % [get_node("..").player_info.level, get_node("..").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 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 play_keys(): + # events[0] is first event + # events[0][0] is first event's TIME + while float(recording.events[0][0]) <= time: + # events[0][1] is first event's EVENT2 + var event_obj = recording.events.pop_front()[1] + var event = obj_to_event(event_obj) + Input.parse_input_event(event) + #._input(event) + #get_node("TPCamera")._input(event) diff --git a/scripts/lobby.gd b/scripts/lobby.gd index 975bbd5..89471ad 100644 --- a/scripts/lobby.gd +++ b/scripts/lobby.gd @@ -21,6 +21,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', true, 'Run this client as AI') + opts.add('-no-record', true, "Don't record this play for AI later") opts.add('-h', false, "Print help") return opts @@ -57,6 +59,10 @@ func _ready(): call_deferred("_client_init") if o.get_value("-singleplayer"): call_deferred("_singleplayer_init") + if o.get_value("-ai"): + my_info.is_ai = true + if not o.get_value("-no-record"): + my_info.record = true if o.get_value('-h'): o.print_help() quit() diff --git a/scripts/player.gd b/scripts/player.gd index 10d63a1..293cb1d 100644 --- a/scripts/player.gd +++ b/scripts/player.gd @@ -22,13 +22,14 @@ 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, "states": [], "events": [], "spawn": Vector3() } slave var slave_transform = Basis() slave var slave_lin_v = Vector3() slave var slave_ang_v = Vector3() -export(NodePath) var tp_camera -export(NodePath) var master_only +var tp_camera = "TPCamera" +var master_only = "MasterOnly" func _ready(): @@ -52,10 +53,27 @@ func spawn(): # So we don't all spawn on top of each other placement.x += rand_range(0, x_varies) placement.z += rand_range(0, z_varies) - recording.spawn = placement + recording.spawn = var2str(placement) set_transform(Basis()) 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 _input(event): if is_network_master(): @@ -64,6 +82,8 @@ 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): @@ -78,10 +98,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): @@ -161,6 +189,9 @@ func _process(delta): if get_translation().y < fall_height: spawn() switch_hero_interface() + + if "record" in player_info: + recording.time += delta if "record" in player_info: recording.time += delta @@ -196,9 +227,22 @@ sync func switch_hero(hero): func _exit_scene(): Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) +func _exit_tree(): + if "record" in player_info: + write_recording() + # Functions # ========= +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.close() + # Quits the game: func quit(): + if "record" in player_info: + write_recording() get_tree().quit()