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 index d9df63c..1c7b345 100644 --- a/scripts/ai/ai-player.gd +++ b/scripts/ai/ai-player.gd @@ -1,22 +1,26 @@ -extends "res://scripts/player.gd" +extends Node -var time = 0 +var recording +var time func _ready(): - ._ready() - read_recording() - print(recording.spawn) - set_translation(recording.spawn) + 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): - time += delta - play_keys() + if is_network_master(): + time += delta + play_keys() func read_recording(): # Gather all existing recordings var possible = [] - var begin = "%d-%d" % [player_info.level, player_info.hero] + 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) @@ -30,24 +34,39 @@ func read_recording(): 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()) - print(recording.events) 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] - print(event_obj) var event = obj_to_event(event_obj) Input.parse_input_event(event) #._input(event) diff --git a/scripts/lobby.gd b/scripts/lobby.gd index 5cc285a..38c98b0 100644 --- a/scripts/lobby.gd +++ b/scripts/lobby.gd @@ -20,6 +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', true, 'Run this client as AI') + opts.add('-record', true, 'Record this play for AI later') opts.add('-h', false, "Print help") return opts @@ -56,6 +58,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 o.get_value("-record"): + my_info.record = true if o.get_value('-h'): o.print_help() quit() @@ -203,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 c0cdaa2..21272b5 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,9 +53,27 @@ func spawn(): # So we don't all spawn on top of each other placement.x += rand_range(0, x_varies) placement.y += rand_range(0, y_varies) + 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(): @@ -63,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): @@ -77,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): @@ -160,6 +189,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! @@ -190,10 +222,21 @@ sync func switch_hero(hero): func _exit_scene(): Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) + 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()