From a12cf9c26bf89e00bdfd2abd699a6be612c2d91c Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Tue, 22 Nov 2022 13:07:37 +0100 Subject: [PATCH 01/17] WIP --- source/Missions.py | 2 +- source/main.py | 1 - source/master.py | 14 +- source/vcr.py | 367 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 381 insertions(+), 3 deletions(-) create mode 100644 source/vcr.py diff --git a/source/Missions.py b/source/Missions.py index bfdff72..0b495bf 100644 --- a/source/Missions.py +++ b/source/Missions.py @@ -582,7 +582,7 @@ def init(cls): cls.validation_state = tools.create_stereo_sound_state(hg.SR_Once) cls.validation_state.volume = 0.5 - cls.missions.append(Mission("Network mode", ["Eurofighter"], ["Rafale"], 1, 1, Missions.network_mode_setup, Missions.network_mode_end_test, Missions.network_mode_end_phase_update)) + cls.missions.append(Mission("Network mode", ["Eurofighter"]*4, ["Rafale"]*4, 1, 1, Missions.network_mode_setup, Missions.network_mode_end_test, Missions.network_mode_end_phase_update)) cls.missions.append(Mission("Training with Rafale", [], ["Rafale"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) cls.missions.append(Mission("Training with Eurofighter", [], ["Eurofighter"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) diff --git a/source/main.py b/source/main.py index 71379b7..b4153d4 100644 --- a/source/main.py +++ b/source/main.py @@ -153,7 +153,6 @@ def get_monitor_mode(width, height): Main.update_window() - # ----------------- Exit: diff --git a/source/master.py b/source/master.py index 9b3b03b..8ff9e8d 100644 --- a/source/master.py +++ b/source/master.py @@ -30,6 +30,7 @@ from WaterReflection import * from overlays import * from math import atan +import vcr class Main: @@ -42,6 +43,7 @@ class Main: flag_OpenGL = True antialiasing = 4 flag_display_HUD = True + flag_display_recorder = False # Control devices @@ -1467,6 +1469,9 @@ def update(cls): if cls.keyboard.Pressed(hg.K_F10): cls.flag_display_HUD = not cls.flag_display_HUD + + if cls.keyboard.Pressed(hg.K_F9): + cls.flag_display_recorder = not cls.flag_display_recorder if cls.flag_gui: hg.ImGuiBeginFrame(int(cls.resolution.x), int(cls.resolution.y), real_dt, hg.ReadMouse(), hg.ReadKeyboard()) @@ -1483,9 +1488,16 @@ def update(cls): # =========== State update: if cls.flag_renderless: used_dt = forced_dt + simulation_dt = used_dt else: + simulation_dt = used_dt used_dt = min(forced_dt * 2, real_dt) - cls.current_state = cls.current_state(hg.time_to_sec_f(used_dt)) # Minimum frame rate security + + # Simulation_dt is timestep for dogfight kinetics: + + cls.current_state = cls.current_state(hg.time_to_sec_f(simulation_dt)) # Minimum frame rate security + + # Used_dt is timestep used for Harfang 3D: hg.SceneUpdateSystems(cls.scene, cls.clocks, used_dt, cls.scene_physics, used_dt, 1000) # ,10,1000) # =========== Render scene visuals: diff --git a/source/vcr.py b/source/vcr.py new file mode 100644 index 0000000..98b2eae --- /dev/null +++ b/source/vcr.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2017-2020 Thomas Simonnet, Movida Production. +import json +import os + +import harfang as hg +from datetime import datetime +import data_converter as dc +import sqlite3 + +conn = None +current_id_user = 1 +current_id_rec = 1 +current_id_play = 1 +user_name = "" +user_firstname = "" +user_info = "" +user_birthdate = "" + +adding_user = False +recording = False +playing = False +pausing= False +render_head = False +timer = 0 +previous_timer = 0 +recorded_max_time = 0 +recorded_fps = 60 + +items = {} +items_list = [] +selected_record = 0 +records = None +last_value_recorded = {} + +items_words_list = ["world", "int", "float"] + +fps_record = 60 + +#item: hg.Node +#params: "world", "pos", "mat4", "enable", "float", "int", "bool", "str" +#container: if not None, contain the value to record +def AddItem(item, params=[], name=None, container=None): + global items, items_list + if isinstance(item, hg.Node) and name is None: + name = f"{item.GetName()} {item.GetTransform().GetPos().x:.2}{item.GetTransform().GetPos().y:.2}{item.GetTransform().GetPos().z:.2}" + elif isinstance(item, str) and name is None: + name = item + + name = dc.conform_string(name) + #print("VCR - add item " + name) + items[name] = {"i": item, "params": params, "container": container, "recording": True} + + items_list.append(items[name]) + return items[name] + + +def post_load_env(): + global conn, items_list, selected_record + + items_list = [] + + # check everything is set + if conn is None: + conn = sqlite3.connect('database.db') + conn.row_factory = sqlite3.Row + print("VCR - database connected") + + c = conn.cursor() + + c.execute(f'''SELECT name FROM sqlite_master WHERE type='table' AND name='users';''') + table_users_exists = c.fetchone() + + c.execute('''CREATE TABLE IF NOT EXISTS users(id_user INTEGER PRIMARY KEY, name TEXT, info TEXT)''') + c.execute('''CREATE TABLE IF NOT EXISTS records(id_rec INTEGER PRIMARY KEY, name TEXT, max_clock FLOAT, fps INT, id_user INTEGER REFERENCES users, CONSTRAINT fk_users FOREIGN KEY (id_user) REFERENCES users(id_user) ON DELETE CASCADE)''') + + # add default user + if table_users_exists is None: + c.execute("INSERT INTO users(name, info) VALUES (\"Default\", ?)", ["HARFANG NWSC User"]) + conn.commit() + + selected_record = 0 + + +def start_record(name_record): + global recording, records, timer, current_id_rec, last_value_recorded + + # check if we are already start + if recording: + return + + recording = True + records = None + last_value_recorded = {} + timer = 0 + + # add record + c = conn.cursor() + #c.execute(f'''SELECT id_rec FROM records WHERE id_user={current_id_user} AND name=\"{name_record}\"''') + #r = c.fetchone() + #if r is None: + # insert the new record + c.execute(f'''INSERT INTO records(id_user, name, fps) VALUES ({current_id_user}, \"{name_record}\", {fps_record})''') + c.execute(f'''SELECT id_rec FROM records WHERE id_user={current_id_user} AND name=\"{name_record}\"''') + r = c.fetchone() + + if r is not None: + current_id_rec = r["id_rec"] + conn.commit() + print(f"create record: {name_record}, {current_id_rec}") + + +def stop_record(): + global recording + + # check if we are already stopped + if not recording: + return + + recording = False + c = conn.cursor() + + # save the current record + # create db for items + for t, record in records.items(): + for name, value in record.items(): + if isinstance(value, str): + c.execute(f"CREATE TABLE IF NOT EXISTS {name}(id_rec INTEGER, c FLOAT, v TEXT, PRIMARY KEY (id_rec, c), CONSTRAINT fk_record FOREIGN KEY (id_rec) REFERENCES records(id_rec) ON DELETE CASCADE) WITHOUT ROWID;") + elif isinstance(value, bool): + c.execute(f"CREATE TABLE IF NOT EXISTS {name}(id_rec INTEGER, c FLOAT, v BOOLEAN, PRIMARY KEY (id_rec, c), CONSTRAINT fk_record FOREIGN KEY (id_rec) REFERENCES records(id_rec) ON DELETE CASCADE) WITHOUT ROWID;") + elif isinstance(value, int): + c.execute(f"CREATE TABLE IF NOT EXISTS {name}(id_rec INTEGER, c FLOAT, v INTEGER, PRIMARY KEY (id_rec, c), CONSTRAINT fk_record FOREIGN KEY (id_rec) REFERENCES records(id_rec) ON DELETE CASCADE) WITHOUT ROWID;") + elif isinstance(value, float): + c.execute(f"CREATE TABLE IF NOT EXISTS {name}(id_rec INTEGER, c FLOAT, v FLOAT, PRIMARY KEY (id_rec, c), CONSTRAINT fk_record FOREIGN KEY (id_rec) REFERENCES records(id_rec) ON DELETE CASCADE) WITHOUT ROWID;") + #c.execute(f"DELETE FROM {name} WHERE id_rec={current_id_rec};") + + # add value to items + for t, record in records.items(): + for name, value in record.items(): + c.execute(f"INSERT INTO {name}(id_rec, c, v) VALUES ({current_id_rec}, {t}, \"{value}\");") + + print(str(current_id_rec)) + c.execute(f"UPDATE records SET max_clock={timer} WHERE id_rec={current_id_rec};") + print(str(current_id_rec)) + #c.execute(f"UPDATE records SET fps={fps_record} WHERE id_rec={current_id_rec};") + conn.commit() + + +def update_recording(dt): + global records, timer, previous_timer, last_value_recorded + if records is None: + records = {} + timer = 0 + previous_timer = 0 + + if timer - previous_timer > 1.0 / fps_record: + print(str(fps_record) + " " + str(timer)) + record = {} + for name, params in items.items(): + if not params["recording"]: + continue + item = params["i"] + for p in params["params"]: + v = "" + n = f"{name}_{p}" + if p == "world": + v = dc.serialize_mat4(item.GetTransform().GetWorld()) + elif p == "mat4": + if params["container"] is not None: + v = dc.serialize_mat4(params["container"][item]) + else: + v = dc.serialize_mat4(item) + elif p == "enable": + v = item.IsEnabled() + elif p == "pos": + v = dc.serialize_vec3(hg.GetT(item.GetTransform().GetWorld())) + elif p in ["int", "float", "bool"]: + if params["container"] is not None: + v = params["container"][item] + else: + v = item + elif p in "str": + v = item + else: + v = eval(p["save"]) + n = f"{name}_{p['i']}" + if n not in last_value_recorded or (n in last_value_recorded and v != last_value_recorded[n]): + last_value_recorded[n] = record[n] = v + + records[timer] = record + previous_timer = timer + timer += hg.time_to_sec_f(dt) + + +def start_play(scene): + global playing, timer + + timer = 0 + playing = True + + +def stop_play(scene): + global playing, pausing + playing = False + pausing = False + +def pause_play(): + global pausing + pausing = not pausing + + +def update_play(scene, dt): + global timer, playing + + ''' + def interpolate_mat(name_record): # TODO not used yet but can be one day + first_mat = deserialize_matrix(records[first_key][name_record]) + second_mat = deserialize_matrix(records[second_key][name_record]) + t = (timer - float(first_key)) / (float(second_key) - float(first_key)) + + pos = first_mat.GetTranslation() + (second_mat.GetTranslation() - first_mat.GetTranslation()) * t + rot = hg.Quaternion.Slerp(t, hg.Quaternion.FromMatrix3(hg.GetRMatrix(first_mat)), + hg.Quaternion.FromMatrix3(hg.GetRMatrix(second_mat))).ToMatrix3() + + return hg.TransformationMat4(pos, rot) + ''' + + + c = conn.cursor() + + for name, params in items.items(): + item = params["i"] + for p in params["params"]: + n = f"{name}_{p}" + if p not in items_words_list: + n = f"{name}_{p['n']}" + + c.execute(f"SELECT v FROM {n} where id_rec={current_id_play} and c <= {timer} ORDER BY c DESC LIMIT 1;") + r = c.fetchone() + if r is not None: + v = r["v"] + if p == "world": + item.GetTransform().SetWorld(dc.deserialize_mat4(v)) + elif p == "mat4": + if params["container"] is not None: + params["container"][item] = dc.deserialize_mat4(v) + else: + item = dc.list_to_mat4(v) + elif p == "enable": + item.Enable() if v else item.Disable() + elif p == "pos": + item.GetTransform().SetWorld(hg.TranslationMat4(dc.deserialize_vec3(v))) + elif p in ["int", "float", "bool"]: + if params["container"] is not None: + params["container"][item] = v + else: + item = v + elif p in "str": + item = v + else: + eval(p["load"]) + + if not pausing: + timer += hg.time_to_sec_f(dt) + + if timer > recorded_max_time: + stop_play(scene) + + +def update_gui(scene, keyboard): + global recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps + + if adding_user: + user_name = hg.ImGuiInputText("Name", user_name, 128)[1] + user_info = hg.ImGuiInputText("Infos", user_info, 128)[1] + if hg.ImGuiButton("Add"): + c = conn.cursor() + c.execute(f'''INSERT INTO users(name, info) VALUES (\"{user_name}\", \"{user_info}\")''') + conn.commit() + adding_user = False + if hg.ImGuiButton("Cancel"): + adding_user = False + + else: + + if not playing and not recording: + if hg.ImGuiButton("Add user"): + adding_user = True + user_name = user_firstname = user_info = user_birthdate = "" + + c = conn.cursor() + c.execute(f'''SELECT name, id_user FROM users''') + r = c.fetchall() + users = [(str(user[1]) +" - " + user[0]) for user in r] + + current_id_user -= 1 + f, current_id_user = hg.ImGuiCombo("Users", current_id_user, users) + if f: + selected_record = 0 + current_id_user += 1 + + if hg.ImGuiButton("Start recording"): + name_record = datetime.now().strftime("%m/%d/%Y_%H:%M:%S") + start_record(dc.conform_string(name_record)) + + fps_record = int(hg.ImGuiSliderFloat("Recording FPS", fps_record, 1, 128)[1]) + + c.execute(f'''SELECT name FROM records WHERE id_user={current_id_user}''') + r = c.fetchall() + r = [x for xs in r for x in xs] + selected_record = hg.ImGuiCombo("Records", selected_record, ["None"]+r)[1] + + if selected_record != 0: + # get current id record + c.execute(f'''SELECT id_rec FROM records WHERE id_user={current_id_user} AND name=\"{r[selected_record-1]}\"''') + r = c.fetchone() + if r is not None: + current_id_play = r["id_rec"] + + c.execute(f'''SELECT max_clock, fps FROM records WHERE id_rec={current_id_play}''') + recorded_max_time = 0 + recorded_fps = 60 + r = c.fetchone() + if r is not None: + recorded_max_time = r["max_clock"] + recorded_fps = r["fps"] + hg.ImGuiText("Record infos: Duration: %.2f - FPS: %d" % (recorded_max_time, recorded_fps)) + + if hg.ImGuiButton("Start play"): + start_play(scene) + + elif recording: + if hg.ImGuiButton("Stop recording"): + stop_record() + + elif playing: + if recorded_max_time: + timer = hg.ImGuiSliderFloat("Timeline", timer, 0, recorded_max_time)[1] + if pausing: + lbl = "Resume" + else: + lbl = "Pause" + if hg.ImGuiButton(lbl): + pause_play() + if pausing: + if hg.ImGuiButton("< Prev frame") or keyboard.Pressed(hg.K_Sub): + timer -= 1/recorded_fps + hg.ImGuiSameLine() + if hg.ImGuiButton("Next frame >") or keyboard.Pressed(hg.K_Add): + timer += 1/recorded_fps + if hg.ImGuiButton("Stop playing"): + stop_play(scene) + +def update(scene, dt): + if recording: + update_recording(dt) + elif playing: + update_play(scene, dt) + + +def before_quit_app(): + global conn + if conn is not None: + conn.commit() + conn.close() + conn = None \ No newline at end of file From 2d196e81378d90e9daa414668d2e335119027692 Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Thu, 24 Nov 2022 13:11:06 +0100 Subject: [PATCH 02/17] recorder WIP --- .gitignore | 1 + source/MachineDevice.py | 1 + source/data_converter.py | 28 ++++ source/main.py | 2 +- source/master.py | 76 +++++---- source/states.py | 198 +++++++++++++++++++---- source/vcr.py | 328 +++++++++++++++++++++++++++++++++------ 7 files changed, 523 insertions(+), 111 deletions(-) diff --git a/.gitignore b/.gitignore index 34bc483..da1c473 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /source/assets_compiled /.vscode /bin_ +/source/database.db diff --git a/source/MachineDevice.py b/source/MachineDevice.py index c78b871..c67748e 100644 --- a/source/MachineDevice.py +++ b/source/MachineDevice.py @@ -564,6 +564,7 @@ class ControlDevice(MachineDevice): CM_MOUSE = "Mouse" CM_LOGITECH_EXTREME_3DPRO = "Logitech extreme 3DPro" CM_LOGITECH_ATTACK_3 = "Logitech Attack 3" + CM_NONE = "None" keyboard = None mouse = None diff --git a/source/data_converter.py b/source/data_converter.py index 03283ff..1fe326a 100644 --- a/source/data_converter.py +++ b/source/data_converter.py @@ -65,6 +65,34 @@ def vec3_to_list_degrees(v: hg.Vec3): l[2] = degrees(l[2]) return l +def serialize_vec3(v): + return "{0:.6f};{1:.6f};{2:.6f}".format( + v.x, v.y, v.z) + + +def deserialize_vec3(s): + f = s.split(";") + return hg.Vec3(float(f[0]), float(f[1]), float(f[2])) + + +def serialize_mat4(m): + r0 = hg.GetRow(m, 0) + r1 = hg.GetRow(m, 1) + r2 = hg.GetRow(m, 2) + return "{0:.6f};{1:.6f};{2:.6f};{3:.6f};{4:.6f};{5:.6f};{6:.6f};{7:.6f};{8:.6f};{9:.6f};{10:.6f};{11:.6f}".format( + r0.x, r0.y, r0.z, r0.w, + r1.x, r1.y, r1.z, r1.w, + r2.x, r2.y, r2.z, r2.w) + + +def deserialize_mat4(s): + f = s.split(";") + m = hg.Mat4() + hg.SetRow(m, 0, hg.Vec4(float(f[0]), float(f[1]), float(f[2]), float(f[3]))) + hg.SetRow(m, 1, hg.Vec4(float(f[4]), float(f[5]), float(f[6]), float(f[7]))) + hg.SetRow(m, 2, hg.Vec4(float(f[8]), float(f[9]), float(f[10]), float(f[11]))) + return m + def load_json_matrix(file_name): file = hg.OpenText(file_name) diff --git a/source/main.py b/source/main.py index b4153d4..fc7ff0d 100644 --- a/source/main.py +++ b/source/main.py @@ -138,7 +138,7 @@ def get_monitor_mode(width, height): hg.ResetClock() # ------------------- Setup state: -Main.current_state = states.init_menu_phase() +Main.current_state = states.init_menu_state() # ------------------- Main loop: diff --git a/source/master.py b/source/master.py index 8ff9e8d..02d35fb 100644 --- a/source/master.py +++ b/source/master.py @@ -74,6 +74,7 @@ class Main: timestamp = 0 # Frame count. timestep = 1 / 60 # Frame dt + simulation_dt = 0 # dt in ns used by simulation (kinetics & renderer) flag_network_mode = False flag_client_update_mode = False @@ -128,8 +129,9 @@ class Main: current_state = None t = 0 fading_to_next_state = False + next_state = "main" #Used to switch to replay state end_state_timer = 0 - end_phase_following_aircraft = None + end_state_following_aircraft = None current_view = None camera = None @@ -302,7 +304,7 @@ def init_game(cls): cls.camera_fps = cls.scene.GetNode("Camera_fps") cls.satellite_camera = cls.scene.GetNode("Camera_satellite") cls.smart_camera = SmartCamera(SmartCamera.TYPE_FOLLOW, cls.keyboard, cls.mouse) - # Camera used in start phase : + # Camera used in start state : cls.camera_intro = cls.scene.GetNode("Camera_intro") # Shadows setup @@ -500,19 +502,19 @@ def create_missiles(cls, machine:Destroyable_Machine, smoke_color): for j in range(md.num_slots): missile_type = md.missiles_config[j] if missile_type == Sidewinder.model_name: - missile = Sidewinder(missile_type + machine.name + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = Sidewinder(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) if missile_type == Meteor.model_name: - missile = Meteor(missile_type + machine.name + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = Meteor(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) if missile_type == Mica.model_name: - missile = Mica(missile_type + machine.name + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = Mica(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) if missile_type == AIM_SL.model_name: - missile = AIM_SL(missile_type + machine.name + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = AIM_SL(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) if missile_type == Karaoke.model_name: - missile = Karaoke(missile_type + machine.name + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = Karaoke(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) if missile_type == CFT.model_name: - missile = CFT(missile_type + machine.name + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = CFT(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) if missile_type == S400.model_name: - missile = S400(missile_type + machine.name + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = S400(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) md.fit_missile(missile, j) missile.set_smoke_color(smoke_color) @@ -1422,10 +1424,11 @@ def update_renderless(cls, dt): @classmethod def update_inputs(cls): - if cls.flag_running: - cls.keyboard.Update() - cls.mouse.Update() + + cls.keyboard.Update() + cls.mouse.Update() + if cls.flag_running: if cls.gamepad is not None: cls.gamepad.Update() if cls.gamepad.IsConnected(): @@ -1473,52 +1476,67 @@ def update(cls): if cls.keyboard.Pressed(hg.K_F9): cls.flag_display_recorder = not cls.flag_display_recorder - if cls.flag_gui: - hg.ImGuiBeginFrame(int(cls.resolution.x), int(cls.resolution.y), real_dt, hg.ReadMouse(), hg.ReadKeyboard()) - cls.smart_camera.update_hovering_ImGui() - cls.gui() - cls.sea_render.gui(cls.scene.GetCurrentCamera().GetTransform().GetPos()) - ParticlesEngine.gui() + if not cls.flag_renderless: + if cls.flag_gui or cls.flag_display_recorder: + hg.ImGuiBeginFrame(int(cls.resolution.x), int(cls.resolution.y), real_dt, hg.ReadMouse(), hg.ReadKeyboard()) + cls.smart_camera.update_hovering_ImGui() + if cls.flag_gui: + cls.gui() + cls.sea_render.gui(cls.scene.GetCurrentCamera().GetTransform().GetPos()) + ParticlesEngine.gui() + else: + if cls.flag_display_recorder: + hg.ImGuiBeginFrame(int(cls.resolution.x), int(cls.resolution.y), real_dt, hg.ReadMouse(), hg.ReadKeyboard()) if cls.flag_display_fps: cls.update_num_fps(hg.time_to_sec_f(real_dt)) #cls.texts_display_list.append({"text": "FPS %d" % (cls.num_fps), "font": cls.hud_font, "pos": hg.Vec2(0.001, 0.999), "size": 0.018, "color": hg.Color.Yellow}) Overlays.add_text2D("FPS %d" % (cls.num_fps), hg.Vec2(0.001, 0.999), 0.018, hg.Color.Yellow, cls.hud_font) + if cls.flag_display_recorder: # and cls.current_state.__name__ == "main_state": + if not vcr.is_init(): + vcr.init() + else: + vcr.update_gui(cls.scene,cls.keyboard) + # =========== State update: if cls.flag_renderless: used_dt = forced_dt - simulation_dt = used_dt + Main.simulation_dt = used_dt else: - simulation_dt = used_dt used_dt = min(forced_dt * 2, real_dt) + Main.simulation_dt = used_dt # Simulation_dt is timestep for dogfight kinetics: - - cls.current_state = cls.current_state(hg.time_to_sec_f(simulation_dt)) # Minimum frame rate security + + cls.current_state = cls.current_state(hg.time_to_sec_f(Main.simulation_dt)) # Minimum frame rate security # Used_dt is timestep used for Harfang 3D: hg.SceneUpdateSystems(cls.scene, cls.clocks, used_dt, cls.scene_physics, used_dt, 1000) # ,10,1000) # =========== Render scene visuals: - if not cls.flag_renderless: + + # Renderless + if cls.flag_renderless: + if cls.flag_display_recorder: + hg.ImGuiEndFrame(255) + cls.update_renderless(forced_dt) + + # Render + else: if cls.flag_vr: cls.render_frame_vr() else: cls.render_frame() - if cls.flag_gui: + if cls.flag_gui or cls.flag_display_recorder: hg.ImGuiEndFrame(255) hg.Frame() if cls.flag_vr: hg.OpenVRSubmitFrame(cls.vr_left_fb, cls.vr_right_fb) #hg.UpdateWindow(cls.win) - - # =========== Renderless mode: - else: - cls.update_renderless(forced_dt) - + cls.clear_display_lists() cls.flag_client_ask_update_scene = False diff --git a/source/states.py b/source/states.py index d64618e..889b4a7 100644 --- a/source/states.py +++ b/source/states.py @@ -2,14 +2,18 @@ import harfang as hg from master import Main +import vcr from Missions import * from SmartCamera import * from HUD import * from overlays import * +import Machines - -def init_menu_phase(): +def init_menu_state(): Main.flag_running = False + + vcr.request_new_state("disable") + Main.set_renderless_mode(False) Main.flag_network_mode = False Main.fading_to_next_state = False @@ -104,10 +108,14 @@ def init_menu_phase(): Main.post_process.setup_fading(3, 1) Main.flag_running = True - return update_menu_phase + vcr.validate_requested_state() + return menu_state + +def menu_state(dts): + + -def update_menu_phase(dts): Main.t += dts Main.post_process.update_fading(dts) if Main.flag_sfx: @@ -233,28 +241,41 @@ def update_menu_phase(dts): if Main.keyboard.Pressed(hg.K_Space): Main.control_mode = AircraftUserControlDevice.CM_KEYBOARD f_start = True + Main.next_state = "main" + elif vcr.request_state == "replay": + Main.control_mode = AircraftUserControlDevice.CM_NONE + f_start = True + Main.next_state = "replay" elif Main.flag_paddle: if Main.gamepad.Pressed(hg.GB_Start): Main.control_mode = AircraftUserControlDevice.CM_GAMEPAD f_start = True + Main.next_state = "main" elif Main.flag_generic_controller: if Main.generic_controller.Down(1): Main.control_mode = AircraftUserControlDevice.CM_LOGITECH_ATTACK_3 f_start = True + Main.next_state = "main" + if f_start: Main.post_process.setup_fading(1, -1) Main.fading_to_next_state = True else: if not Main.post_process.fade_running: Main.destroy_players() - init_main_phase() - return update_main_phase - return update_menu_phase + if Main.next_state == "replay": + init_replay_state() + return replay_state + else: + init_main_state() + return main_state + return menu_state # =================================== IN GAME ============================================= -def init_main_phase(): +def init_main_state(): + vcr.request_new_state("record") Main.flag_running = False Main.fading_to_next_state = False Main.post_process.setup_fading(1, 1) @@ -276,16 +297,22 @@ def init_main_phase(): HUD_Radar.setup_plots(Main.resolution, n_aircrafts, n_missiles, mission.allies_carriers + mission.ennemies_carriers, n_missile_launchers) - #Main.setup_weaponery() + # Setup recorder + vcr.clear_items + for dm in Main.destroyables_list: + if dm.type == Machines.Destroyable_Machine.TYPE_AIRCRAFT \ + or dm.type == Machines.Destroyable_Machine.TYPE_MISSILE: + vcr.AddItem(dm, ["machine_state"]) Main.num_start_frames = 10 Main.timestamp = 0 Main.flag_running = True - return update_main_phase + vcr.validate_requested_state() + return main_state -def update_main_phase(dts): - +def main_state(dts): + Main.timestamp += 1 if not Main.flag_renderless: Main.post_process.update_fading(dts) @@ -317,12 +344,12 @@ def update_main_phase(dts): if machine.is_activated: if machine.bounding_boxe is not None: matrix = machine.get_parent_node().GetTransform().GetWorld() - Overlays.add_line(machine.bound_front * matrix, (machine.bound_front + hg.Vec3(0, 0, 1)) * matrix, hg.Color.Blue, hg.Color.Blue) - Overlays.add_line(machine.bound_back * matrix, (machine.bound_back + hg.Vec3(0, 0, -1)) * matrix, hg.Color.Blue, hg.Color.Blue) - Overlays.add_line(machine.bound_up * matrix, (machine.bound_up + hg.Vec3(0, 1, 0)) * matrix, hg.Color.Green, hg.Color.Green) - Overlays.add_line(machine.bound_down * matrix, (machine.bound_down + hg.Vec3(0, -1, 0)) * matrix, hg.Color.Green, hg.Color.Green) - Overlays.add_line(machine.bound_right * matrix, (machine.bound_right + hg.Vec3(1, 0, 0)) * matrix, hg.Color.Red, hg.Color.Red) - Overlays.add_line(machine.bound_left * matrix, (machine.bound_left + hg.Vec3(-1, 0, 0)) * matrix, hg.Color.Red, hg.Color.Red) + Overlays.add_line(matrix * machine.bound_front, matrix * (machine.bound_front + hg.Vec3(0, 0, 1)), hg.Color.Blue, hg.Color.Blue) + Overlays.add_line(matrix * machine.bound_back, matrix * (machine.bound_back + hg.Vec3(0, 0, -1)), hg.Color.Blue, hg.Color.Blue) + Overlays.add_line(matrix * machine.bound_up, matrix * (machine.bound_up + hg.Vec3(0, 1, 0)), hg.Color.Green, hg.Color.Green) + Overlays.add_line(matrix * machine.bound_down, matrix * (machine.bound_down + hg.Vec3(0, -1, 0)), hg.Color.Green, hg.Color.Green) + Overlays.add_line(matrix * machine.bound_right, matrix * (machine.bound_right + hg.Vec3(1, 0, 0)), hg.Color.Red, hg.Color.Red) + Overlays.add_line(matrix * machine.bound_left, matrix * (machine.bound_left + hg.Vec3(-1, 0, 0)), hg.Color.Red, hg.Color.Red) Overlays.display_boxe(machine.get_world_bounding_boxe(), hg.Color.Yellow) else: if Main.user_aircraft is not None and Main.flag_display_radar_in_renderless: @@ -363,6 +390,9 @@ def update_main_phase(dts): Main.smart_camera.update(cam, dts, camera_noise_level) + if vcr.is_init(): + vcr.update(Main.scene, Main.simulation_dt) + mission = Missions.get_current_mission() if Main.keyboard.Pressed(hg.K_L): @@ -374,18 +404,117 @@ def update_main_phase(dts): if Main.keyboard.Pressed(hg.K_Tab): Main.set_renderless_mode(False) mission.aborted = True - init_end_phase() - return update_end_phase + init_end_state() + return end_state elif mission.end_test(Main): - init_end_phase() - return update_end_phase + init_end_state() + return end_state + elif vcr.request_state == "replay": + Main.destroy_players() + init_replay_state() + return replay_state + + return main_state + + +# =================================== REPLAY MODE ============================================= + +def init_replay_state(): + Main.set_renderless_mode(False) + Main.flag_running = False + Main.fading_to_next_state = False + Main.post_process.setup_fading(1, 1) + Main.destroy_sfx() + ParticlesEngine.reset_engines() + Destroyable_Machine.reset_machines() + + Main.flag_network_mode = False + + fps_start_matrix = hg.TranslationMat4(hg.Vec3(0, 20, 0)) + Main.camera_fps.GetTransform().SetWorld(fps_start_matrix) + + Main.user_aircraft = None + Main.setup_views_carousel(True) + Main.set_view_carousel("fps") + + Main.timestamp = 0 + Main.flag_running = True + vcr.validate_requested_state() - return update_main_phase +def replay_state(dts): + Main.timestamp += 1 + Main.post_process.update_fading(dts) + + if Main.flag_control_views: + Main.control_views(Main.keyboard) + if Main.flag_display_HUD: + if Main.user_aircraft is not None: + if Main.user_aircraft.type == Destroyable_Machine.TYPE_AIRCRAFT: + HUD_Aircraft.update(Main, Main.user_aircraft, Main.destroyables_list) + elif Main.user_aircraft.type == Destroyable_Machine.TYPE_MISSILE_LAUNCHER: + HUD_MissileLauncher.update(Main, Main.user_aircraft, Main.destroyables_list) + + if Main.flag_display_selected_aircraft and Main.selected_aircraft is not None: + HUD_MissileTarget.display_selected_target(Main, Main.selected_aircraft) + if Main.flag_display_machines_bounding_boxes: + for machine in Destroyable_Machine.machines_list: + if machine.is_activated: + if machine.bounding_boxe is not None: + matrix = machine.get_parent_node().GetTransform().GetWorld() + Overlays.add_line(matrix * machine.bound_front, matrix * (machine.bound_front + hg.Vec3(0, 0, 1)), hg.Color.Blue, hg.Color.Blue) + Overlays.add_line(matrix * machine.bound_back, matrix * (machine.bound_back + hg.Vec3(0, 0, -1)), hg.Color.Blue, hg.Color.Blue) + Overlays.add_line(matrix * machine.bound_up, matrix * (machine.bound_up + hg.Vec3(0, 1, 0)), hg.Color.Green, hg.Color.Green) + Overlays.add_line(matrix * machine.bound_down, matrix * (machine.bound_down + hg.Vec3(0, -1, 0)), hg.Color.Green, hg.Color.Green) + Overlays.add_line(matrix * machine.bound_right, matrix * (machine.bound_right + hg.Vec3(1, 0, 0)), hg.Color.Red, hg.Color.Red) + Overlays.add_line(matrix * machine.bound_left, matrix * (machine.bound_left + hg.Vec3(-1, 0, 0)), hg.Color.Red, hg.Color.Red) + Overlays.display_boxe(machine.get_world_bounding_boxe(), hg.Color.Yellow) + + camera_noise_level = 0 + if Main.user_aircraft is not None: + + #if Main.user_aircraft.type == Destroyable_Machine.TYPE_AIRCRAFT: + # acc = Main.user_aircraft.get_linear_acceleration() + # camera_noise_level = max(0, Main.user_aircraft.get_linear_speed() * 3.6 / 2500 * 0.1 + pow(min(1, abs(acc / 7)), 2) * 1) + # if Main.user_aircraft.post_combustion: + # camera_noise_level += 0.1 + + if Main.player_view_mode == SmartCamera.TYPE_FIX: + cam = Main.camera_cokpit + else: + cam = Main.camera + + else: + cam = Main.camera_fps + + if Main.satellite_view: + cam = Main.satellite_camera + + Main.smart_camera.update(cam, dts, camera_noise_level) + + vcr.update(Main.scene, Main.simulation_dt) + + if not Main.fading_to_next_state: + + if Main.keyboard.Pressed(hg.K_Tab) or vcr.request_state == "disable": + vcr.request_new_state("disable") + Main.post_process.setup_fading(1, -1) + Main.fading_to_next_state = True + else: + Main.post_process.update_fading(dts) + if Main.flag_sfx: + Main.master_sfx_volume = Main.post_process.fade_f + if not Main.post_process.fade_running: + Main.destroy_players() + init_menu_state() + return menu_state + + return replay_state # =================================== END GAME ============================================= -def init_end_phase(): +def init_end_state(): + vcr.request_new_state("disable") Main.set_renderless_mode(False) Main.flag_running = False Main.deactivate_cockpit_view() @@ -422,15 +551,16 @@ def init_end_phase(): if aircraft is None: aircraft = Main.players_allies[0] - Main.end_phase_following_aircraft = aircraft + Main.end_state_following_aircraft = aircraft Main.smart_camera.setup(SmartCamera.TYPE_FOLLOW, Main.camera, aircraft.get_parent_node()) Main.scene.SetCurrentCamera(Main.camera) Main.flag_running = True - return update_end_phase - + vcr.validate_requested_state() + return end_state -def update_end_phase(dts): +def end_state(dts): + Main.smart_camera.update(Main.camera, dts) Main.update_kinetics(dts) @@ -443,12 +573,12 @@ def update_end_phase(dts): HUD_MissileTarget.display_selected_target(Main, Main.selected_aircraft) mission = Missions.get_current_mission() - mission.update_end_phase(Main, dts) + mission.end_state(Main, dts) if not Main.fading_to_next_state: - if Main.end_phase_following_aircraft.flag_destroyed or Main.end_phase_following_aircraft.wreck: + if Main.end_state_following_aircraft.flag_destroyed or Main.end_state_following_aircraft.wreck: Main.end_state_timer -= dts - if Main.keyboard.Pressed(hg.K_Tab) or Main.end_state_timer < 0 or Main.end_phase_following_aircraft.flag_landed: + if Main.keyboard.Pressed(hg.K_Tab) or Main.end_state_timer < 0 or Main.end_state_following_aircraft.flag_landed: Main.post_process.setup_fading(1, -1) Main.fading_to_next_state = True else: @@ -458,7 +588,7 @@ def update_end_phase(dts): if not Main.post_process.fade_running: mission.reset() Main.destroy_players() - init_menu_phase() - return update_menu_phase + init_menu_state() + return menu_state - return update_end_phase + return end_state diff --git a/source/vcr.py b/source/vcr.py index 98b2eae..ca57067 100644 --- a/source/vcr.py +++ b/source/vcr.py @@ -8,7 +8,9 @@ from datetime import datetime import data_converter as dc import sqlite3 +import Machines +flag_init = False conn = None current_id_user = 1 current_id_rec = 1 @@ -30,6 +32,8 @@ items = {} items_list = [] +items_names = [] +selected_item_idx = 0 selected_record = 0 records = None last_value_recorded = {} @@ -38,28 +42,42 @@ fps_record = 60 +state = "disable" +request_state = "disable" + #item: hg.Node #params: "world", "pos", "mat4", "enable", "float", "int", "bool", "str" #container: if not None, contain the value to record def AddItem(item, params=[], name=None, container=None): - global items, items_list + global items, items_list, items_names if isinstance(item, hg.Node) and name is None: name = f"{item.GetName()} {item.GetTransform().GetPos().x:.2}{item.GetTransform().GetPos().y:.2}{item.GetTransform().GetPos().z:.2}" elif isinstance(item, str) and name is None: name = item + elif isinstance(item, Machines.Destroyable_Machine) and name is None: + name = item.name name = dc.conform_string(name) #print("VCR - add item " + name) items[name] = {"i": item, "params": params, "container": container, "recording": True} items_list.append(items[name]) + items_names.append(name) return items[name] +def clear_items(): + global items, items_list, selected_item_idx, items_names + selected_item_idx = 0 + items = {} + items_list = [] + items_names = [] + -def post_load_env(): - global conn, items_list, selected_record +def is_init(): + return flag_init - items_list = [] +def init(): + global conn, selected_record, flag_init # check everything is set if conn is None: @@ -79,6 +97,7 @@ def post_load_env(): if table_users_exists is None: c.execute("INSERT INTO users(name, info) VALUES (\"Default\", ?)", ["HARFANG NWSC User"]) conn.commit() + flag_init = True selected_record = 0 @@ -164,7 +183,9 @@ def update_recording(dt): for p in params["params"]: v = "" n = f"{name}_{p}" - if p == "world": + if p == "machine_state": + v = serialize_machine_state(item) + elif p == "world": v = dc.serialize_mat4(item.GetTransform().GetWorld()) elif p == "mat4": if params["container"] is not None: @@ -199,7 +220,6 @@ def start_play(scene): timer = 0 playing = True - def stop_play(scene): global playing, pausing playing = False @@ -240,7 +260,9 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day r = c.fetchone() if r is not None: v = r["v"] - if p == "world": + if p == "machine_state": + deserialize_machine_state(item, v) + elif p == "world": item.GetTransform().SetWorld(dc.deserialize_mat4(v)) elif p == "mat4": if params["container"] is not None: @@ -268,44 +290,94 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day stop_play(scene) -def update_gui(scene, keyboard): - global recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps +def update_gui_record(scene, keyboard): - if adding_user: - user_name = hg.ImGuiInputText("Name", user_name, 128)[1] - user_info = hg.ImGuiInputText("Infos", user_info, 128)[1] - if hg.ImGuiButton("Add"): - c = conn.cursor() - c.execute(f'''INSERT INTO users(name, info) VALUES (\"{user_name}\", \"{user_info}\")''') - conn.commit() - adding_user = False - if hg.ImGuiButton("Cancel"): - adding_user = False - - else: - - if not playing and not recording: - if hg.ImGuiButton("Add user"): - adding_user = True - user_name = user_firstname = user_info = user_birthdate = "" + global selected_item_idx, recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps, request_state + + if hg.ImGuiBegin("Dogfight - Recorder"): + if adding_user: + user_name = hg.ImGuiInputText("Name", user_name, 128)[1] + user_info = hg.ImGuiInputText("Infos", user_info, 128)[1] + if hg.ImGuiButton("Add"): + c = conn.cursor() + c.execute(f'''INSERT INTO users(name, info) VALUES (\"{user_name}\", \"{user_info}\")''') + conn.commit() + adding_user = False + if hg.ImGuiButton("Cancel"): + adding_user = False + + else: + + if not recording: + + # Users list: + if hg.ImGuiButton("Add user"): + adding_user = True + user_name = user_firstname = user_info = user_birthdate = "" + + c = conn.cursor() + c.execute(f'''SELECT name, id_user FROM users''') + r = c.fetchall() + users = [(str(user[1]) +" - " + user[0]) for user in r] + + current_id_user -= 1 + f, current_id_user = hg.ImGuiCombo("Users", current_id_user, users) + if f: + selected_record = 0 + current_id_user += 1 + + # Items list: + if len(items_list) > 0: + f, d = hg.ImGuiListBox("Items", selected_item_idx, items_names,20) + if f: + selected_item_idx = d + + # Record: + if hg.ImGuiButton("Start recording"): + name_record = datetime.now().strftime("%m/%d/%Y_%H:%M:%S") + start_record(dc.conform_string(name_record)) + + fps_record = int(hg.ImGuiSliderFloat("Recording FPS", fps_record, 1, 128)[1]) + + # Records list: + c.execute(f'''SELECT name FROM records WHERE id_user={current_id_user}''') + r = c.fetchall() + r = [x for xs in r for x in xs] + selected_record = hg.ImGuiCombo("Records", selected_record, ["None"]+r)[1] + + if selected_record != 0: + # get current id record + c.execute(f'''SELECT id_rec FROM records WHERE id_user={current_id_user} AND name=\"{r[selected_record-1]}\"''') + r = c.fetchone() + if r is not None: + current_id_play = r["id_rec"] + + c.execute(f'''SELECT max_clock, fps FROM records WHERE id_rec={current_id_play}''') + recorded_max_time = 0 + recorded_fps = 60 + r = c.fetchone() + if r is not None: + recorded_max_time = r["max_clock"] + recorded_fps = r["fps"] + hg.ImGuiText("Record infos: Duration: %.2f - FPS: %d" % (recorded_max_time, recorded_fps)) + + if hg.ImGuiButton("Enter replay mode"): + request_state = "replay" + + elif recording: + if hg.ImGuiButton("Stop recording"): + stop_record() + hg.ImGuiEnd() + +def update_gui_replay(scene, keyboard): + + global recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps, request_state + + if hg.ImGuiBegin("Dogfight - Replayer"): + if not playing: c = conn.cursor() - c.execute(f'''SELECT name, id_user FROM users''') - r = c.fetchall() - users = [(str(user[1]) +" - " + user[0]) for user in r] - - current_id_user -= 1 - f, current_id_user = hg.ImGuiCombo("Users", current_id_user, users) - if f: - selected_record = 0 - current_id_user += 1 - - if hg.ImGuiButton("Start recording"): - name_record = datetime.now().strftime("%m/%d/%Y_%H:%M:%S") - start_record(dc.conform_string(name_record)) - fps_record = int(hg.ImGuiSliderFloat("Recording FPS", fps_record, 1, 128)[1]) - c.execute(f'''SELECT name FROM records WHERE id_user={current_id_user}''') r = c.fetchall() r = [x for xs in r for x in xs] @@ -329,12 +401,11 @@ def update_gui(scene, keyboard): if hg.ImGuiButton("Start play"): start_play(scene) - - elif recording: - if hg.ImGuiButton("Stop recording"): - stop_record() + + if hg.ImGuiButton("Exit replay mode"): + request_state = "disable" - elif playing: + else: if recorded_max_time: timer = hg.ImGuiSliderFloat("Timeline", timer, 0, recorded_max_time)[1] if pausing: @@ -351,6 +422,48 @@ def update_gui(scene, keyboard): timer += 1/recorded_fps if hg.ImGuiButton("Stop playing"): stop_play(scene) + hg.ImGuiEnd() + +def update_gui_wait_request(): + if hg.ImGuiBegin("Dogfight - Recorder"): + hg.ImGuiText("... Entering " + request_state + " mode ...") + hg.ImGuiEnd() + +def update_gui_disable(): + global request_state + if hg.ImGuiBegin("Dogfight - Recorder"): + + hg.ImGuiText("Select a mission to record or enter replay mode") + + if hg.ImGuiButton("Enter replay mode"): + request_state = "replay" + + hg.ImGuiEnd() + + +# Call this to lock recorder: + +def request_new_state(req_state): + global request_state + request_state = req_state + +# Call this when the scene to record/replay is ready: + +def validate_requested_state(): + global state + state = request_state + +def update_gui(scene,keyboard): + + if state != request_state: + update_gui_wait_request() + elif state == "record": + update_gui_record(scene, keyboard) + elif state == "replay": + update_gui_replay(scene, keyboard) + elif state == "disable": + update_gui_disable() + def update(scene, dt): if recording: @@ -364,4 +477,125 @@ def before_quit_app(): if conn is not None: conn.commit() conn.close() - conn = None \ No newline at end of file + conn = None + + +def serialize_machine_state(machine:Machines.Destroyable_Machine): + if machine.type == Machines.Destroyable_Machine.TYPE_AIRCRAFT: + return serialize_aircraft_state(machine) + if machine.type == Machines.Destroyable_Machine.TYPE_MISSILE: + return serialize_missile_state(machine) + +def deserialize_machine_state(machine:Machines.Destroyable_Machine, s:str): + if machine.type == Machines.Destroyable_Machine.TYPE_AIRCRAFT: + deserialize_aircraft_state(machine, s) + if machine.type == Machines.Destroyable_Machine.TYPE_MISSILE: + deserialize_missile_state(machine, s) + +def serialize_missile_state(machine:Machines.Missile): + + matrix = dc.serialize_mat4(machine.get_parent_node().GetTransform().GetWorld()) + v_move = dc.serialize_vec3(machine.get_move_vector()) + return matrix +":"+ v_move + +def serialize_aircraft_state(machine:Machines.Aircraft): + + matrix = dc.serialize_mat4(machine.get_parent_node().GetTransform().GetWorld()) + v_move = dc.serialize_vec3(machine.get_move_vector()) + health_lvl = str(machine.get_health_level()) + return matrix +":"+ v_move +":"+ health_lvl + +def deserialize_aircraft_state(machine:Machines.Aircraft, s:str): + f = s.split(":") + matrix = dc.deserialize_mat4(f[0]) + v_move = dc.deserialize_vec3(f[1]) + health_lvl = f[2] + machine.get_parent_node().GetTransform().SetWorld(matrix) + machine.v_move = v_move + machine.set_health_level(health_lvl) + +def deserialize_missile_state(machine:Machines.Missile, s:str): + f = s.split(":") + matrix = dc.deserialize_mat4(f[0]) + v_move = dc.deserialize_vec3(f[1]) + machine.get_parent_node().GetTransform().SetWorld(matrix) + machine.v_move = v_move + + """ + h_spd, v_spd = machine.get_world_speed() + + gear = machine.get_device("Gear") + apctrl = machine.get_device("AutopilotControlDevice") + iactrl = machine.get_device("IAControlDevice") + td = machine.get_device("TargettingDevice") + + if gear is not None: + gear_activated = gear.activated + else: + gear_activated = False + + if apctrl is not None: + autopilot_activated = apctrl.is_activated() + autopilot_heading = apctrl.autopilot_heading + autopilot_speed = apctrl.autopilot_speed + autopilot_altitude = apctrl.autopilot_altitude + else: + autopilot_activated = False + autopilot_heading = 0 + autopilot_speed = 0 + autopilot_altitude = 0 + + if iactrl is not None: + ia_activated = iactrl.is_activated() + else: + ia_activated = False + + if td is not None: + target_id = td.get_target_name() + target_locked = td.target_locked + else: + target_id = "- ! No TargettingDevice ! -" + target_locked = False + + position = machine.get_position() + rotation = machine.get_Euler() + v_move = machine.get_move_vector() + state = { + "timestamp": main.timestamp, + "timestep": main.timestep, + "position": [position.x, position.y, position.z], + "Euler_angles": [rotation.x, rotation.y, rotation.z], + "easy_steering": machine.flag_easy_steering, + "health_level": machine.health_level, + "destroyed": machine.flag_destroyed, + "wreck": machine.wreck, + "crashed": machine.flag_crashed, + "active": machine.activated, + "type": Destroyable_Machine.types_labels[machine.type], + "nationality": machine.nationality, + "thrust_level": machine.get_thrust_level(), + "brake_level": machine.get_brake_level(), + "flaps_level": machine.get_flaps_level(), + "horizontal_speed": h_spd, + "vertical_speed": v_spd, + "linear_speed": machine.get_linear_speed(), + "move_vector": [v_move.x, v_move.y, v_move.z], + "linear_acceleration": machine.get_linear_acceleration(), + "altitude": machine.get_altitude(), + "heading": machine.get_heading(), + "pitch_attitude": machine.get_pitch_attitude(), + "roll_attitude": machine.get_roll_attitude(), + "post_combustion": machine.post_combustion, + "user_pitch_level": machine.get_pilot_pitch_level(), + "user_roll_level": machine.get_pilot_roll_level(), + "user_yaw_level": machine.get_pilot_yaw_level(), + "gear": gear_activated, + "ia": ia_activated, + "autopilot": autopilot_activated, + "autopilot_heading": autopilot_heading, + "autopilot_speed": autopilot_speed, + "autopilot_altitude": autopilot_altitude, + "target_id": target_id, + "target_locked": target_locked + } + """ \ No newline at end of file From df7ef72e6f23cc9d85e01052757f273c74c9f3a6 Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Fri, 25 Nov 2022 13:00:34 +0100 Subject: [PATCH 03/17] added record scene description to database --- source/master.py | 14 +++++++------- source/states.py | 7 +++---- source/vcr.py | 21 ++++++++++++++------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/source/master.py b/source/master.py index 02d35fb..6f60ea2 100644 --- a/source/master.py +++ b/source/master.py @@ -502,19 +502,19 @@ def create_missiles(cls, machine:Destroyable_Machine, smoke_color): for j in range(md.num_slots): missile_type = md.missiles_config[j] if missile_type == Sidewinder.model_name: - missile = Sidewinder(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = Sidewinder(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) if missile_type == Meteor.model_name: - missile = Meteor(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = Meteor(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) if missile_type == Mica.model_name: - missile = Mica(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = Mica(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) if missile_type == AIM_SL.model_name: - missile = AIM_SL(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = AIM_SL(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) if missile_type == Karaoke.model_name: - missile = Karaoke(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = Karaoke(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) if missile_type == CFT.model_name: - missile = CFT(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = CFT(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) if missile_type == S400.model_name: - missile = S400(machine.name + "." + missile_type + "." + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) + missile = S400(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) md.fit_missile(missile, j) missile.set_smoke_color(smoke_color) diff --git a/source/states.py b/source/states.py index 889b4a7..d59aff0 100644 --- a/source/states.py +++ b/source/states.py @@ -298,10 +298,9 @@ def init_main_state(): HUD_Radar.setup_plots(Main.resolution, n_aircrafts, n_missiles, mission.allies_carriers + mission.ennemies_carriers, n_missile_launchers) # Setup recorder - vcr.clear_items + vcr.clear_items() for dm in Main.destroyables_list: - if dm.type == Machines.Destroyable_Machine.TYPE_AIRCRAFT \ - or dm.type == Machines.Destroyable_Machine.TYPE_MISSILE: + if dm.type == Machines.Destroyable_Machine.TYPE_AIRCRAFT or dm.type == Machines.Destroyable_Machine.TYPE_MISSILE: vcr.AddItem(dm, ["machine_state"]) Main.num_start_frames = 10 @@ -573,7 +572,7 @@ def end_state(dts): HUD_MissileTarget.display_selected_target(Main, Main.selected_aircraft) mission = Missions.get_current_mission() - mission.end_state(Main, dts) + mission.update_end_phase(Main, dts) if not Main.fading_to_next_state: if Main.end_state_following_aircraft.flag_destroyed or Main.end_state_following_aircraft.wreck: diff --git a/source/vcr.py b/source/vcr.py index ca57067..1a18d08 100644 --- a/source/vcr.py +++ b/source/vcr.py @@ -60,7 +60,7 @@ def AddItem(item, params=[], name=None, container=None): name = dc.conform_string(name) #print("VCR - add item " + name) items[name] = {"i": item, "params": params, "container": container, "recording": True} - + items_list.append(items[name]) items_names.append(name) return items[name] @@ -71,8 +71,7 @@ def clear_items(): items = {} items_list = [] items_names = [] - - + def is_init(): return flag_init @@ -91,7 +90,7 @@ def init(): table_users_exists = c.fetchone() c.execute('''CREATE TABLE IF NOT EXISTS users(id_user INTEGER PRIMARY KEY, name TEXT, info TEXT)''') - c.execute('''CREATE TABLE IF NOT EXISTS records(id_rec INTEGER PRIMARY KEY, name TEXT, max_clock FLOAT, fps INT, id_user INTEGER REFERENCES users, CONSTRAINT fk_users FOREIGN KEY (id_user) REFERENCES users(id_user) ON DELETE CASCADE)''') + c.execute('''CREATE TABLE IF NOT EXISTS records(id_rec INTEGER PRIMARY KEY, name TEXT, max_clock FLOAT, fps INT,scene_items TEXT, id_user INTEGER REFERENCES users, CONSTRAINT fk_users FOREIGN KEY (id_user) REFERENCES users(id_user) ON DELETE CASCADE)''') # add default user if table_users_exists is None: @@ -101,6 +100,13 @@ def init(): selected_record = 0 +def create_scene_items_list(): + scene_items = [] + for name, params in items.items(): + item = params["i"] + if isinstance( item,Machines.Destroyable_Machine): + scene_items.append(str(item.type) + ";" + item.model_name + ";" + name) + return scene_items def start_record(name_record): global recording, records, timer, current_id_rec, last_value_recorded @@ -114,13 +120,15 @@ def start_record(name_record): last_value_recorded = {} timer = 0 + scene_items = ":".join(create_scene_items_list()) + # add record c = conn.cursor() #c.execute(f'''SELECT id_rec FROM records WHERE id_user={current_id_user} AND name=\"{name_record}\"''') #r = c.fetchone() #if r is None: # insert the new record - c.execute(f'''INSERT INTO records(id_user, name, fps) VALUES ({current_id_user}, \"{name_record}\", {fps_record})''') + c.execute(f'''INSERT INTO records(id_user, name, fps, scene_items) VALUES ({current_id_user}, \"{name_record}\", {fps_record},\"{scene_items}\")''') c.execute(f'''SELECT id_rec FROM records WHERE id_user={current_id_user} AND name=\"{name_record}\"''') r = c.fetchone() @@ -201,7 +209,7 @@ def update_recording(dt): v = params["container"][item] else: v = item - elif p in "str": + elif p == "str": v = item else: v = eval(p["save"]) @@ -246,7 +254,6 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day return hg.TransformationMat4(pos, rot) ''' - c = conn.cursor() for name, params in items.items(): From 691d53b15b781aac41e361fa3e6e001051aaf3bc Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Tue, 29 Nov 2022 08:48:04 +0100 Subject: [PATCH 04/17] basic replay works (only machine, no sfx, no particles) --- source/Missions.py | 16 ++-- source/master.py | 233 ++++++++++++++++++++++----------------------- source/states.py | 9 +- source/vcr.py | 191 +++++++++++++++++-------------------- 4 files changed, 216 insertions(+), 233 deletions(-) diff --git a/source/Missions.py b/source/Missions.py index 0b495bf..7324082 100644 --- a/source/Missions.py +++ b/source/Missions.py @@ -226,7 +226,7 @@ def mission_setup_training(cls, main): # --------- Views setup main.setup_views_carousel(True) - main.set_view_carousel("Aircraft_ally_" + str(main.num_players_allies)) + main.set_view_carousel("Aircraft_ally_" + str(len(main.players_allies))) main.set_track_view("back") main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) @@ -269,7 +269,7 @@ def mission_training_end_test(cls, main): for ally in main.players_allies: if ally.wreck: allies_wreck += 1 - if main.num_players_allies == allies_wreck: + if len(main.players_allies) == allies_wreck: mission.failed = True print("MISSION FAILED !") return True @@ -329,7 +329,7 @@ def mission_setup_players(cls, main): ia.activate() main.setup_views_carousel(False) - main.set_view_carousel("Aircraft_ally_" + str(main.num_players_allies)) + main.set_view_carousel("Aircraft_ally_" + str(len(main.players_allies))) main.set_track_view("back") main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) @@ -342,7 +342,7 @@ def mission_setup_players(cls, main): uctrl = main.user_aircraft.get_device("UserControlDevice") if uctrl is not None: uctrl.activate() - if main.num_players_allies < 3: + if len(main.players_allies) < 3: main.user_aircraft.reset_thrust_level(1) main.user_aircraft.activate_post_combustion() @@ -356,10 +356,10 @@ def mission_one_against_x_end_test(cls, main): for ally in main.players_allies: if ally.wreck: allies_wreck += 1 - if main.num_players_ennemies == ennemies_wreck: + if len(main.players_ennemies) == ennemies_wreck: mission.failed = False return True - if main.num_players_allies == allies_wreck: + if len(main.players_allies) == allies_wreck: mission.failed = True return True @@ -412,7 +412,7 @@ def mission_total_war_setup_players(cls, main): ia.activate() main.setup_views_carousel() - main.set_view_carousel("Aircraft_ally_" + str(main.num_players_allies)) + main.set_view_carousel("Aircraft_ally_" + str(len(main.players_allies))) main.set_track_view("back") main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) @@ -425,7 +425,7 @@ def mission_total_war_setup_players(cls, main): uctrl = main.user_aircraft.get_device("UserControlDevice") if uctrl is not None: uctrl.activate() - if main.num_players_allies < 4: + if len(main.players_allies) < 4: main.user_aircraft.reset_thrust_level(1) main.user_aircraft.activate_post_combustion() diff --git a/source/master.py b/source/master.py index 6f60ea2..35692b4 100644 --- a/source/master.py +++ b/source/master.py @@ -164,10 +164,6 @@ class Main: background_color = 0x1070a0ff # 0xb9efffff ennemyaircraft_nodes = None - num_players_ennemies = 0 - num_players_allies = 0 - num_missile_launchers_allies = 0 - num_missile_launchers_ennemies = 0 players_allies = [] players_ennemies = [] players_sfx = [] @@ -476,6 +472,17 @@ def destroy_players(cls): cls.scene_cockpit_aircrafts = [] + @classmethod + def create_aircraft_carrier(cls, name, nationality): + carrier = Carrier(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality) + if carrier is not None: + if nationality == 1: + cls.aircraft_carrier_allies.append(carrier) + elif nationality == 2: + cls.aircraft_carrier_ennemies.append(carrier) + cls.destroyables_list.append(carrier) + carrier.add_to_update_list() + @classmethod def create_aircraft_carriers(cls, num_allies, num_ennemies): @@ -483,78 +490,109 @@ def create_aircraft_carriers(cls, num_allies, num_ennemies): cls.aircraft_carrier_allies = [] cls.aircraft_carrier_ennemies = [] for i in range(num_allies): - carrier = Carrier("Ally_Carrier_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 1) - cls.aircraft_carrier_allies.append(carrier) - cls.destroyables_list.append(carrier) - carrier.add_to_update_list() - + carrier = cls.create_aircraft_carrier("Ally_Carrier_" + str(i + 1), 1) + for i in range(num_ennemies): - carrier = Carrier("Ennemy_Carrier_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 2) - cls.aircraft_carrier_ennemies.append(carrier) - cls.destroyables_list.append(carrier) - carrier.add_to_update_list() + carrier = cls.create_aircraft_carrier("Ennemy_Carrier_" + str(i + 1), 2) + @classmethod - def create_missiles(cls, machine:Destroyable_Machine, smoke_color): + def create_missile(cls, model_name, name, nationality): + if model_name == Sidewinder.model_name: + missile = Sidewinder(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality) + elif model_name == Meteor.model_name: + missile = Meteor(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality) + elif model_name == Mica.model_name: + missile = Mica(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality) + elif model_name == AIM_SL.model_name: + missile = AIM_SL(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality) + elif model_name == Karaoke.model_name: + missile = Karaoke(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality) + elif model_name == CFT.model_name: + missile = CFT(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality) + elif model_name == S400.model_name: + missile = S400(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality) + else: + missile = None + if missile is not None: + if nationality == 1: + cls.missiles_allies.append(missile) + elif nationality == 2: + cls.missiles_ennemies.append(missile) + cls.destroyables_list.append(missile) + return missile + + + @classmethod + def create_missiles_from_machine_config(cls, machine:Destroyable_Machine, smoke_color): md = machine.get_device("MissilesDevice") md.set_missiles_config(machine.missiles_config) if md is not None: for j in range(md.num_slots): missile_type = md.missiles_config[j] - if missile_type == Sidewinder.model_name: - missile = Sidewinder(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) - if missile_type == Meteor.model_name: - missile = Meteor(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) - if missile_type == Mica.model_name: - missile = Mica(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) - if missile_type == AIM_SL.model_name: - missile = AIM_SL(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) - if missile_type == Karaoke.model_name: - missile = Karaoke(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) - if missile_type == CFT.model_name: - missile = CFT(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) - if missile_type == S400.model_name: - missile = S400(machine.name + "-" + missile_type + "-" + str(j), cls.scene, cls.scene_physics, cls.pl_resources, machine.nationality) - + missile = cls.create_missile(missile_type, machine.name + "-" + missile_type + "-" + str(j), machine.nationality) md.fit_missile(missile, j) missile.set_smoke_color(smoke_color) return md.missiles return None + @classmethod + def create_missile_launcher(cls, name, nationality): + launcher = MissileLauncherS400(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) + if launcher is not None: + if nationality == 1: + cls.missile_launchers_allies.append(launcher) + elif nationality == 2: + cls.missile_launchers_ennemies.append(launcher) + cls.destroyables_list.append(launcher) + launcher.add_to_update_list() + return launcher + @classmethod def create_missile_launchers(cls, num_allies, num_ennemies): cls.missile_launchers_allies = [] cls.missile_launchers_ennemies = [] - cls.num_missile_launchers_allies = num_allies - cls.num_missile_launchers_ennemies = num_ennemies for i in range(num_allies): - launcher = MissileLauncherS400("Ally_Missile_launcher_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 1, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - cls.missile_launchers_allies.append(launcher) - cls.destroyables_list.append(launcher) - launcher.add_to_update_list() - - missiles = cls.create_missiles(launcher, cls.allies_missiles_smoke_color) - if missiles is not None: - cls.missiles_allies.append([] + missiles) - cls.destroyables_list += missiles - + launcher = cls.create_missile_launcher("Ally_Missile_launcher_" + str(i + 1), 1) + missiles = cls.create_missiles_from_machine_config(launcher, cls.allies_missiles_smoke_color) for i in range(num_ennemies): - launcher = MissileLauncherS400("Ennemy_Missile_launcher_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 2, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - cls.missile_launchers_ennemies.append(launcher) - cls.destroyables_list.append(launcher) - launcher.add_to_update_list() + launcher = cls.create_missile_launcher("Ennemy_Missile_launcher_" + str(i + 1), 2) + missiles = cls.create_missiles_from_machine_config(launcher, cls.ennemies_missiles_smoke_color) + - missiles = cls.create_missiles(launcher, cls.ennemies_missiles_smoke_color) - if missiles is not None: - cls.missiles_ennemies.append([] + missiles) - cls.destroyables_list += missiles + @classmethod + def create_aircraft(cls,model_name, name, nationality): + if model_name == F14_Parameters.model_name: + aircraft = F14(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) + elif model_name == F14_2_Parameters.model_name: + aircraft = F14_2(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) + elif model_name == Rafale_Parameters.model_name: + aircraft = Rafale(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) + elif model_name == Eurofighter_Parameters.model_name: + aircraft = Eurofighter(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) + elif model_name == F16_Parameters.model_name: + aircraft = F16(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) + elif model_name == TFX_Parameters.model_name: + aircraft = TFX(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) + elif model_name == Miuss_Parameters.model_name: + aircraft = Miuss(name, cls.scene, cls.scene_physics, cls.pl_resources, nationality, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) + else: + aircraft = None + + if aircraft is not None: + cls.destroyables_list.append(aircraft) + aircraft.add_to_update_list() + if nationality == 1: + cls.players_allies.append(aircraft) + elif nationality == 2: + cls.players_ennemies.append(aircraft) + + return aircraft @classmethod def create_players(cls, allies_types, ennemies_types): - cls.num_players_allies = len(allies_types) - cls.num_players_ennemies = len(ennemies_types) cls.players_allies = [] cls.players_ennemies = [] cls.missiles_allies = [] @@ -562,61 +600,13 @@ def create_players(cls, allies_types, ennemies_types): cls.players_sfx = [] cls.missiles_sfx = [] + for i, model_name in enumerate(allies_types): + aircraft = cls.create_aircraft(model_name, "ally_" + str(i + 1), 1) + missiles = cls.create_missiles_from_machine_config(aircraft, cls.allies_missiles_smoke_color) - for i, a_type in enumerate(allies_types): - - if a_type == F14_Parameters.model_name: - aircraft = F14("ally_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 1, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - elif a_type == F14_2_Parameters.model_name: - aircraft = F14_2("ally_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 1, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - elif a_type == Rafale_Parameters.model_name: - aircraft = Rafale("ally_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 1, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - elif a_type == Eurofighter_Parameters.model_name: - aircraft = Eurofighter("ally_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 1, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - elif a_type == F16_Parameters.model_name: - aircraft = F16("ally_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 1, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - elif a_type == TFX_Parameters.model_name: - aircraft = TFX("ally_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 1, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - elif a_type == Miuss_Parameters.model_name: - aircraft = Miuss("ally_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 1, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - - cls.destroyables_list.append(aircraft) - aircraft.add_to_update_list() - - cls.players_allies.append(aircraft) - - missiles = cls.create_missiles(aircraft, cls.allies_missiles_smoke_color) - if missiles is not None: - cls.missiles_allies.append([] + missiles) - cls.destroyables_list += missiles - - - for i, a_type in enumerate(ennemies_types): - - if a_type == F14_Parameters.model_name: - aircraft = F14("ennemy_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 2, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - elif a_type == F14_2_Parameters.model_name: - aircraft = F14_2("ennemy_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 2, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - elif a_type == Rafale_Parameters.model_name: - aircraft = Rafale("ennemy_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 2, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - elif a_type == Eurofighter_Parameters.model_name: - aircraft = Eurofighter("ennemy_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 2, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - elif a_type == F16_Parameters.model_name: - aircraft = F16("ennemy_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 2, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - elif a_type == TFX_Parameters.model_name: - aircraft = TFX("ennemy_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 2, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - elif a_type == Miuss_Parameters.model_name: - aircraft = Miuss("ennemy_" + str(i + 1), cls.scene, cls.scene_physics, cls.pl_resources, 2, hg.Vec3(0, 500, 0), hg.Vec3(0, 0, 0)) - - aircraft.add_to_update_list() - cls.destroyables_list.append(aircraft) - - cls.players_ennemies.append(aircraft) - - missiles = cls.create_missiles(aircraft, cls.ennemies_missiles_smoke_color) - if missiles is not None: - cls.missiles_ennemies.append([] + missiles) - cls.destroyables_list += missiles + for i, model_name in enumerate(ennemies_types): + aircraft = cls.create_aircraft(model_name, "ennemy_" + str(i + 1), 2) + missiles = cls.create_missiles_from_machine_config(aircraft, cls.ennemies_missiles_smoke_color) if cls.flag_sfx: cls.setup_sfx() @@ -658,30 +648,30 @@ def init_playground(cls): td.set_destroyable_targets(cls.players_ennemies) pl.set_landing_targets(lt_allies) td.targets = cls.players_ennemies - if cls.num_players_ennemies > 0: - td.set_target_id(int(uniform(0, 1000) % cls.num_players_ennemies)) + if len(cls.players_ennemies) > 0: + td.set_target_id(int(uniform(0, 1000) % len(cls.players_ennemies))) for i, pl in enumerate(cls.missile_launchers_allies): td = pl.get_device("TargettingDevice") td.set_destroyable_targets(cls.players_ennemies) td.targets = cls.players_ennemies - if cls.num_players_ennemies > 0: - td.set_target_id(int(uniform(0, 1000) % cls.num_players_ennemies)) + if len(cls.players_ennemies) > 0: + td.set_target_id(int(uniform(0, 1000) % len(cls.players_ennemies))) for i, pl in enumerate(cls.players_ennemies): td = pl.get_device("TargettingDevice") td.set_destroyable_targets(cls.players_allies) pl.set_landing_targets(lt_ennemies) td.targets = cls.players_allies - if cls.num_players_allies > 0: - td.set_target_id(int(uniform(0, 1000) % cls.num_players_allies)) + if len(cls.players_allies) > 0: + td.set_target_id(int(uniform(0, 1000) % len(cls.players_allies))) for i, pl in enumerate(cls.missile_launchers_ennemies): td = pl.get_device("TargettingDevice") td.set_destroyable_targets(cls.players_allies) td.targets = cls.players_allies - if cls.num_players_allies > 0: - td.set_target_id(int(uniform(0, 1000) % cls.num_players_allies)) + if len(cls.players_allies) > 0: + td.set_target_id(int(uniform(0, 1000) % len(cls.players_allies))) cls.destroyables_items = {} @@ -691,6 +681,13 @@ def init_playground(cls): Destroyable_Machine.machines_list = cls.destroyables_list # !!! Move to Destroyable_Machine.__init__() Destroyable_Machine.machines_items = cls.destroyables_items # !!! Move to Destroyable_Machine.__init__() + # Setup HUD systems: + n_aircrafts = len(cls.players_allies) + len(cls.players_ennemies) + n_missile_launchers = len(cls.missile_launchers_allies) + len(cls.missile_launchers_ennemies) + n_missiles = len(cls.missiles_allies) + len(cls.missiles_ennemies) + + HUD_Radar.setup_plots(cls.resolution, n_aircrafts, n_missiles, len(cls.aircraft_carrier_allies) + len(cls.aircraft_carrier_ennemies), n_missile_launchers) + # ----------------- Views ------------------------------------------------------------------- @classmethod def update_initial_head_matrix(cls, vr_state: hg.OpenVRState): @@ -707,14 +704,14 @@ def setup_views_carousel(cls, flag_include_enemies=False): cls.camera_fps.GetTransform().SetWorld(fps_start_matrix) cls.views_carousel = ["fps"] - for i in range(cls.num_players_allies): + for i in range(len(cls.players_allies)): cls.views_carousel.append("Aircraft_ally_" + str(i + 1)) - for i in range(cls.num_missile_launchers_allies): + for i in range(len(cls.missile_launchers_allies)): cls.views_carousel.append("MissileLauncher_ally_" + str(i + 1)) if flag_include_enemies: - for i in range(cls.num_players_ennemies): + for i in range(len(cls.players_ennemies)): cls.views_carousel.append("Aircraft_enemy_" + str(i + 1)) - for i in range(cls.num_missile_launchers_ennemies): + for i in range(len(cls.missile_launchers_ennemies)): cls.views_carousel.append("MissileLauncher_enemy_" + str(i + 1)) cls.views_carousel_ptr = 1 @@ -1497,7 +1494,7 @@ def update(cls): if not vcr.is_init(): vcr.init() else: - vcr.update_gui(cls.scene,cls.keyboard) + vcr.update_gui(cls,cls.keyboard) # =========== State update: if cls.flag_renderless: diff --git a/source/states.py b/source/states.py index d59aff0..53b1fc3 100644 --- a/source/states.py +++ b/source/states.py @@ -287,8 +287,8 @@ def init_main_state(): mission.setup_players(Main) - n_aircrafts = Main.num_players_allies + Main.num_players_ennemies - n_missile_launchers = Main.num_missile_launchers_allies + Main.num_missile_launchers_ennemies + n_aircrafts = len(Main.players_allies) + len(Main.players_ennemies) + n_missile_launchers = len(Main.missile_launchers_allies) + len(Main.missile_launchers_ennemies) n_missiles = 0 for aircraft in Main.players_allies: n_missiles += aircraft.get_num_missiles_slots() @@ -298,10 +298,7 @@ def init_main_state(): HUD_Radar.setup_plots(Main.resolution, n_aircrafts, n_missiles, mission.allies_carriers + mission.ennemies_carriers, n_missile_launchers) # Setup recorder - vcr.clear_items() - for dm in Main.destroyables_list: - if dm.type == Machines.Destroyable_Machine.TYPE_AIRCRAFT or dm.type == Machines.Destroyable_Machine.TYPE_MISSILE: - vcr.AddItem(dm, ["machine_state"]) + vcr.setup_items(Main) Main.num_start_frames = 10 Main.timestamp = 0 diff --git a/source/vcr.py b/source/vcr.py index 1a18d08..c001be6 100644 --- a/source/vcr.py +++ b/source/vcr.py @@ -9,6 +9,7 @@ import data_converter as dc import sqlite3 import Machines +import MissileLauncherS400 flag_init = False conn = None @@ -38,13 +39,21 @@ records = None last_value_recorded = {} -items_words_list = ["world", "int", "float"] +items_words_list = ["world", "int", "float", "machine_state"] fps_record = 60 state = "disable" request_state = "disable" +# Create recordable items from dogfight scene +def setup_items(main): + clear_items() + machines_types = [Machines.Destroyable_Machine.TYPE_AIRCRAFT, Machines.Destroyable_Machine.TYPE_MISSILE,Machines.Destroyable_Machine.TYPE_MISSILE_LAUNCHER, Machines.Destroyable_Machine.TYPE_SHIP] + for dm in main.destroyables_list: + if dm.type in machines_types: + AddItem(dm, ["machine_state"], dm.name) + #item: hg.Node #params: "world", "pos", "mat4", "enable", "float", "int", "bool", "str" #container: if not None, contain the value to record @@ -105,7 +114,7 @@ def create_scene_items_list(): for name, params in items.items(): item = params["i"] if isinstance( item,Machines.Destroyable_Machine): - scene_items.append(str(item.type) + ";" + item.model_name + ";" + name) + scene_items.append(str(item.type) + ";" + item.model_name + ";" + str(item.nationality) + ";" + name) return scene_items def start_record(name_record): @@ -182,7 +191,7 @@ def update_recording(dt): previous_timer = 0 if timer - previous_timer > 1.0 / fps_record: - print(str(fps_record) + " " + str(timer)) + #print(str(fps_record) + " " + str(timer)) record = {} for name, params in items.items(): if not params["recording"]: @@ -221,14 +230,39 @@ def update_recording(dt): previous_timer = timer timer += hg.time_to_sec_f(dt) - -def start_play(scene): +def create_scene(main, scene_items): + for scene_item in scene_items: + item_def = scene_item.split(";") + item_def[0] = int(item_def[0]) + item_def[2] = int(item_def[2]) + if item_def[0] == Machines.Destroyable_Machine.TYPE_AIRCRAFT: + main.create_aircraft(item_def[1], item_def[3], item_def[2]) + elif item_def[0] == Machines.Destroyable_Machine.TYPE_MISSILE: + main.create_missile(item_def[1], item_def[3], item_def[2]) + elif item_def[0] == Machines.Destroyable_Machine.TYPE_MISSILE_LAUNCHER: + main.create_missile_launcher(item_def[3], item_def[2]) + elif item_def[0] == Machines.Destroyable_Machine.TYPE_SHIP: + main.create_aircraft_carrier(item_def[3], item_def[2]) + main.init_playground() + setup_items(main) + + +def start_play(main): global playing, timer - timer = 0 - playing = True + c = conn.cursor() + c.execute(f"SELECT scene_items FROM records where id_rec={current_id_play} and id_user = {current_id_user};") + r = c.fetchone() + if r is not None: + scene_items = r["scene_items"].split(":") + if len(scene_items) > 0: + main.destroy_players() + create_scene(main, scene_items) + timer = 0 + playing = True + -def stop_play(scene): +def stop_play(): global playing, pausing playing = False pausing = False @@ -260,8 +294,8 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day item = params["i"] for p in params["params"]: n = f"{name}_{p}" - if p not in items_words_list: - n = f"{name}_{p['n']}" + #if p not in items_words_list: + # n = f"{name}_{p['n']}" c.execute(f"SELECT v FROM {n} where id_rec={current_id_play} and c <= {timer} ORDER BY c DESC LIMIT 1;") r = c.fetchone() @@ -269,6 +303,7 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day v = r["v"] if p == "machine_state": deserialize_machine_state(item, v) + elif p == "world": item.GetTransform().SetWorld(dc.deserialize_mat4(v)) elif p == "mat4": @@ -293,11 +328,11 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day if not pausing: timer += hg.time_to_sec_f(dt) - if timer > recorded_max_time: + if timer >= recorded_max_time: stop_play(scene) -def update_gui_record(scene, keyboard): +def update_gui_record(): global selected_item_idx, recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps, request_state @@ -376,7 +411,7 @@ def update_gui_record(scene, keyboard): stop_record() hg.ImGuiEnd() -def update_gui_replay(scene, keyboard): +def update_gui_replay(main, keyboard): global recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps, request_state @@ -385,6 +420,16 @@ def update_gui_replay(scene, keyboard): c = conn.cursor() + c.execute(f'''SELECT name, id_user FROM users''') + r = c.fetchall() + users = [(str(user[1]) +" - " + user[0]) for user in r] + + current_id_user -= 1 + f, current_id_user = hg.ImGuiCombo("Users", current_id_user, users) + if f: + selected_record = 0 + current_id_user += 1 + c.execute(f'''SELECT name FROM records WHERE id_user={current_id_user}''') r = c.fetchall() r = [x for xs in r for x in xs] @@ -407,7 +452,7 @@ def update_gui_replay(scene, keyboard): hg.ImGuiText("Record infos: Duration: %.2f - FPS: %d" % (recorded_max_time, recorded_fps)) if hg.ImGuiButton("Start play"): - start_play(scene) + start_play(main) if hg.ImGuiButton("Exit replay mode"): request_state = "disable" @@ -428,7 +473,7 @@ def update_gui_replay(scene, keyboard): if hg.ImGuiButton("Next frame >") or keyboard.Pressed(hg.K_Add): timer += 1/recorded_fps if hg.ImGuiButton("Stop playing"): - stop_play(scene) + stop_play() hg.ImGuiEnd() def update_gui_wait_request(): @@ -460,14 +505,14 @@ def validate_requested_state(): global state state = request_state -def update_gui(scene,keyboard): +def update_gui(main,keyboard): if state != request_state: update_gui_wait_request() elif state == "record": - update_gui_record(scene, keyboard) + update_gui_record() elif state == "replay": - update_gui_replay(scene, keyboard) + update_gui_replay(main, keyboard) elif state == "disable": update_gui_disable() @@ -490,33 +535,46 @@ def before_quit_app(): def serialize_machine_state(machine:Machines.Destroyable_Machine): if machine.type == Machines.Destroyable_Machine.TYPE_AIRCRAFT: return serialize_aircraft_state(machine) - if machine.type == Machines.Destroyable_Machine.TYPE_MISSILE: + elif machine.type == Machines.Destroyable_Machine.TYPE_MISSILE: return serialize_missile_state(machine) + elif machine.type == Machines.Destroyable_Machine.TYPE_MISSILE_LAUNCHER: + return serialize_missile_launcher_state(machine) + elif machine.type == Machines.Destroyable_Machine.TYPE_SHIP: + return serialize_ship_state(machine) def deserialize_machine_state(machine:Machines.Destroyable_Machine, s:str): if machine.type == Machines.Destroyable_Machine.TYPE_AIRCRAFT: deserialize_aircraft_state(machine, s) - if machine.type == Machines.Destroyable_Machine.TYPE_MISSILE: + elif machine.type == Machines.Destroyable_Machine.TYPE_MISSILE: deserialize_missile_state(machine, s) + elif machine.type == Machines.Destroyable_Machine.TYPE_SHIP: + deserialize_ship_state(machine, s) + def serialize_missile_state(machine:Machines.Missile): - matrix = dc.serialize_mat4(machine.get_parent_node().GetTransform().GetWorld()) v_move = dc.serialize_vec3(machine.get_move_vector()) return matrix +":"+ v_move def serialize_aircraft_state(machine:Machines.Aircraft): - matrix = dc.serialize_mat4(machine.get_parent_node().GetTransform().GetWorld()) v_move = dc.serialize_vec3(machine.get_move_vector()) health_lvl = str(machine.get_health_level()) return matrix +":"+ v_move +":"+ health_lvl +def serialize_missile_launcher_state(machine:MissileLauncherS400): + matrix = dc.serialize_mat4(machine.get_parent_node().GetTransform().GetWorld()) + return matrix + +def serialize_ship_state(machine:Machines.Carrier): + matrix = dc.serialize_mat4(machine.get_parent_node().GetTransform().GetWorld()) + return matrix + def deserialize_aircraft_state(machine:Machines.Aircraft, s:str): f = s.split(":") matrix = dc.deserialize_mat4(f[0]) v_move = dc.deserialize_vec3(f[1]) - health_lvl = f[2] + health_lvl = float(f[2]) machine.get_parent_node().GetTransform().SetWorld(matrix) machine.v_move = v_move machine.set_health_level(health_lvl) @@ -528,81 +586,12 @@ def deserialize_missile_state(machine:Machines.Missile, s:str): machine.get_parent_node().GetTransform().SetWorld(matrix) machine.v_move = v_move - """ - h_spd, v_spd = machine.get_world_speed() - - gear = machine.get_device("Gear") - apctrl = machine.get_device("AutopilotControlDevice") - iactrl = machine.get_device("IAControlDevice") - td = machine.get_device("TargettingDevice") - - if gear is not None: - gear_activated = gear.activated - else: - gear_activated = False - - if apctrl is not None: - autopilot_activated = apctrl.is_activated() - autopilot_heading = apctrl.autopilot_heading - autopilot_speed = apctrl.autopilot_speed - autopilot_altitude = apctrl.autopilot_altitude - else: - autopilot_activated = False - autopilot_heading = 0 - autopilot_speed = 0 - autopilot_altitude = 0 - - if iactrl is not None: - ia_activated = iactrl.is_activated() - else: - ia_activated = False - - if td is not None: - target_id = td.get_target_name() - target_locked = td.target_locked - else: - target_id = "- ! No TargettingDevice ! -" - target_locked = False - - position = machine.get_position() - rotation = machine.get_Euler() - v_move = machine.get_move_vector() - state = { - "timestamp": main.timestamp, - "timestep": main.timestep, - "position": [position.x, position.y, position.z], - "Euler_angles": [rotation.x, rotation.y, rotation.z], - "easy_steering": machine.flag_easy_steering, - "health_level": machine.health_level, - "destroyed": machine.flag_destroyed, - "wreck": machine.wreck, - "crashed": machine.flag_crashed, - "active": machine.activated, - "type": Destroyable_Machine.types_labels[machine.type], - "nationality": machine.nationality, - "thrust_level": machine.get_thrust_level(), - "brake_level": machine.get_brake_level(), - "flaps_level": machine.get_flaps_level(), - "horizontal_speed": h_spd, - "vertical_speed": v_spd, - "linear_speed": machine.get_linear_speed(), - "move_vector": [v_move.x, v_move.y, v_move.z], - "linear_acceleration": machine.get_linear_acceleration(), - "altitude": machine.get_altitude(), - "heading": machine.get_heading(), - "pitch_attitude": machine.get_pitch_attitude(), - "roll_attitude": machine.get_roll_attitude(), - "post_combustion": machine.post_combustion, - "user_pitch_level": machine.get_pilot_pitch_level(), - "user_roll_level": machine.get_pilot_roll_level(), - "user_yaw_level": machine.get_pilot_yaw_level(), - "gear": gear_activated, - "ia": ia_activated, - "autopilot": autopilot_activated, - "autopilot_heading": autopilot_heading, - "autopilot_speed": autopilot_speed, - "autopilot_altitude": autopilot_altitude, - "target_id": target_id, - "target_locked": target_locked - } - """ \ No newline at end of file +def deserialize_ship_state(machine:Machines.Carrier, s:str): + #f = s.split(":") + matrix = dc.deserialize_mat4(s) + machine.get_parent_node().GetTransform().SetWorld(matrix) + +def deserialize_missile_launcher_state(machine:MissileLauncherS400, s:str): + #f = s.split(":") + matrix = dc.deserialize_mat4(s) + machine.get_parent_node().GetTransform().SetWorld(matrix) \ No newline at end of file From 53d6f9cb76fe5f3ae4dba27aa2f507340d1214f6 Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Tue, 29 Nov 2022 13:06:10 +0100 Subject: [PATCH 05/17] adding async recording in db --- source/SmartCamera.py | 24 +++++++++----- source/master.py | 2 +- source/states.py | 6 +++- source/vcr.py | 76 ++++++++++++++++++++++++++++++++++--------- 4 files changed, 82 insertions(+), 26 deletions(-) diff --git a/source/SmartCamera.py b/source/SmartCamera.py index 5cb32a0..d1bcb94 100644 --- a/source/SmartCamera.py +++ b/source/SmartCamera.py @@ -23,6 +23,8 @@ def __init__(self, cam_type=TYPE_FOLLOW, keyboard=None, mouse=None): self.flag_fix_mouse_controls_rotation = True # True = FIX camera rotation controlled with mouse self.flag_reseting_rotation = False + self.flag_inertia = True + self.type = cam_type self.keyboard = keyboard @@ -175,15 +177,19 @@ def update(self, camera: hg.Camera, dts, noise_level=0): self.update_tactical_camera(camera, dts) def update_target_point(self, dts): - v = self.target_node.GetTransform().GetPos() - self.target_point - self.target_point += v * self.pos_inertia * dts * 60 - mat_n = hg.TransformationMat4(self.target_node.GetTransform().GetPos(), self.target_node.GetTransform().GetRot()) - rz = hg.Cross(hg.GetZ(self.target_matrix), hg.GetZ(mat_n)) - ry = hg.Cross(hg.GetY(self.target_matrix), hg.GetY(mat_n)) - mr = rz + ry - le = hg.Len(mr) - if le > 0.001: - self.target_matrix = ms.MathsSupp.rotate_matrix(self.target_matrix, hg.Normalize(mr), le * self.rot_inertia * dts * 60) + if self.flag_inertia: + v = self.target_node.GetTransform().GetPos() - self.target_point + self.target_point += v * self.pos_inertia * dts * 60 + mat_n = hg.TransformationMat4(self.target_node.GetTransform().GetPos(), self.target_node.GetTransform().GetRot()) + rz = hg.Cross(hg.GetZ(self.target_matrix), hg.GetZ(mat_n)) + ry = hg.Cross(hg.GetY(self.target_matrix), hg.GetY(mat_n)) + mr = rz + ry + le = hg.Len(mr) + if le > 0.001: + self.target_matrix = ms.MathsSupp.rotate_matrix(self.target_matrix, hg.Normalize(mr), le * self.rot_inertia * dts * 60) + else: + self.target_point = self.target_node.GetTransform().GetPos() + self.target_matrix = hg.RotationMat3(hg.GetR(self.target_node.GetTransform().GetWorld())) # ============== Camera fix ===================== def enable_mouse_controls_fix_rotation(self): diff --git a/source/master.py b/source/master.py index 35692b4..9e9147e 100644 --- a/source/master.py +++ b/source/master.py @@ -1505,7 +1505,7 @@ def update(cls): Main.simulation_dt = used_dt # Simulation_dt is timestep for dogfight kinetics: - + cls.current_state = cls.current_state(hg.time_to_sec_f(Main.simulation_dt)) # Minimum frame rate security # Used_dt is timestep used for Harfang 3D: diff --git a/source/states.py b/source/states.py index 53b1fc3..e87c501 100644 --- a/source/states.py +++ b/source/states.py @@ -13,6 +13,7 @@ def init_menu_state(): Main.flag_running = False vcr.request_new_state("disable") + Main.smart_camera.flag_inertia = True Main.set_renderless_mode(False) Main.flag_network_mode = False @@ -275,6 +276,7 @@ def menu_state(dts): # =================================== IN GAME ============================================= def init_main_state(): + Main.smart_camera.flag_inertia = True vcr.request_new_state("record") Main.flag_running = False Main.fading_to_next_state = False @@ -416,6 +418,7 @@ def main_state(dts): # =================================== REPLAY MODE ============================================= def init_replay_state(): + Main.smart_camera.flag_inertia = False Main.set_renderless_mode(False) Main.flag_running = False Main.fading_to_next_state = False @@ -486,9 +489,9 @@ def replay_state(dts): if Main.satellite_view: cam = Main.satellite_camera - Main.smart_camera.update(cam, dts, camera_noise_level) vcr.update(Main.scene, Main.simulation_dt) + Main.smart_camera.update(cam, dts, camera_noise_level) if not Main.fading_to_next_state: @@ -510,6 +513,7 @@ def replay_state(dts): # =================================== END GAME ============================================= def init_end_state(): + Main.smart_camera.flag_inertia = True vcr.request_new_state("disable") Main.set_renderless_mode(False) Main.flag_running = False diff --git a/source/vcr.py b/source/vcr.py index c001be6..77768ed 100644 --- a/source/vcr.py +++ b/source/vcr.py @@ -25,6 +25,8 @@ recording = False playing = False pausing= False +updating_database = False +progress_cptr = 0 render_head = False timer = 0 previous_timer = 0 @@ -39,6 +41,8 @@ records = None last_value_recorded = {} +task_record_in_database = None + items_words_list = ["world", "int", "float", "machine_state"] fps_record = 60 @@ -145,20 +149,16 @@ def start_record(name_record): current_id_rec = r["id_rec"] conn.commit() print(f"create record: {name_record}, {current_id_rec}") - -def stop_record(): - global recording - - # check if we are already stopped - if not recording: - return - - recording = False +# Coroutine +def record_in_database(): + global updating_database, progress_cptr c = conn.cursor() # save the current record # create db for items + print("Record items table if necessary") + i = 0 for t, record in records.items(): for name, value in record.items(): if isinstance(value, str): @@ -170,17 +170,36 @@ def stop_record(): elif isinstance(value, float): c.execute(f"CREATE TABLE IF NOT EXISTS {name}(id_rec INTEGER, c FLOAT, v FLOAT, PRIMARY KEY (id_rec, c), CONSTRAINT fk_record FOREIGN KEY (id_rec) REFERENCES records(id_rec) ON DELETE CASCADE) WITHOUT ROWID;") #c.execute(f"DELETE FROM {name} WHERE id_rec={current_id_rec};") - + progress_cptr = i / (2*len(records)) + i+=1 + if (i % fps_record) == 0: + yield # add value to items for t, record in records.items(): for name, value in record.items(): c.execute(f"INSERT INTO {name}(id_rec, c, v) VALUES ({current_id_rec}, {t}, \"{value}\");") + progress_cptr = i / (2*len(records)) + i+=1 + if (i % fps_record) == 0: + yield - print(str(current_id_rec)) c.execute(f"UPDATE records SET max_clock={timer} WHERE id_rec={current_id_rec};") - print(str(current_id_rec)) #c.execute(f"UPDATE records SET fps={fps_record} WHERE id_rec={current_id_rec};") conn.commit() + yield + updating_database = False + +def stop_record(): + global recording, updating_database, task_record_in_database + + # check if we are already stopped + if not recording: + return + recording = False + task_record_in_database = record_in_database() + progress_cptr = 0 + + updating_database = True def update_recording(dt): @@ -244,6 +263,9 @@ def create_scene(main, scene_items): elif item_def[0] == Machines.Destroyable_Machine.TYPE_SHIP: main.create_aircraft_carrier(item_def[3], item_def[2]) main.init_playground() + main.user_aircraft = None + main.setup_views_carousel(True) + main.set_view_carousel("fps") setup_items(main) @@ -329,7 +351,7 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day timer += hg.time_to_sec_f(dt) if timer >= recorded_max_time: - stop_play(scene) + stop_play() def update_gui_record(): @@ -489,15 +511,30 @@ def update_gui_disable(): if hg.ImGuiButton("Enter replay mode"): request_state = "replay" - hg.ImGuiEnd() +def update_gui_updating_database(): + if hg.ImGuiBegin("Dogfight - Recorder"): + hg.ImGuiText("... Wait while writing in database ...") + hg.ImGuiProgressBar(progress_cptr) + hg.ImGuiEnd() # Call this to lock recorder: def request_new_state(req_state): global request_state request_state = req_state + if req_state == "disable": + if recording: + stop_record() + elif playing: + stop_play() + elif req_state == "record": + if playing: + stop_play() + elif req_state =="replay": + if recording: + stop_record() # Call this when the scene to record/replay is ready: @@ -510,7 +547,10 @@ def update_gui(main,keyboard): if state != request_state: update_gui_wait_request() elif state == "record": - update_gui_record() + if updating_database: + update_gui_updating_database() + else: + update_gui_record() elif state == "replay": update_gui_replay(main, keyboard) elif state == "disable": @@ -518,10 +558,16 @@ def update_gui(main,keyboard): def update(scene, dt): + global updating_database if recording: update_recording(dt) elif playing: update_play(scene, dt) + elif updating_database: + try: + next(task_record_in_database) + except StopIteration as stop: + updating_database = False def before_quit_app(): From 3f38aeeca5c58d70b63ad55b213f00bcbd96315d Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Wed, 30 Nov 2022 10:23:12 +0100 Subject: [PATCH 06/17] debuguing recorder --- source/master.py | 40 ++++++++++++++------- source/states.py | 91 ++++++++++++++++++++++++++++++------------------ source/vcr.py | 63 +++++++++++++++++++++------------ 3 files changed, 125 insertions(+), 69 deletions(-) diff --git a/source/master.py b/source/master.py index 9e9147e..0fe987d 100644 --- a/source/master.py +++ b/source/master.py @@ -217,7 +217,7 @@ class Main: #======= Aircrafts view: selected_aircraft_id = 0 - selected_aircraft = None + selected_machine = None @classmethod def init(cls): @@ -442,6 +442,17 @@ def update_num_fps(cls, dts): cls.num_fps += ne cls.num_fps = cls.num_fps / len(cls.nfps) + @classmethod + def clear_scene(cls): + cls.selected_machine = None + cls.user_aircraft = None + cls.set_view_carousel("fps") + cls.destroy_players() + cls.destroy_sfx() + ParticlesEngine.reset_engines() + Destroyable_Machine.reset_machines() + cls.setup_views_carousel(False) + @classmethod def destroy_players(cls): for aircraft in cls.players_ennemies: @@ -1062,22 +1073,26 @@ def gui(cls): d, f = hg.ImGuiCheckbox("Display selected aircraft", cls.flag_display_selected_aircraft) if d: cls.flag_display_selected_aircraft = f - aircrafts_list = hg.StringList() + if len(aircrafts) > 0: + aircrafts_list = hg.StringList() - for aircraft in aircrafts: - nm = aircraft.name - if aircraft == cls.user_aircraft: - nm += " - USER -" - aircrafts_list.push_back(nm) + for aircraft in aircrafts: + nm = aircraft.name + if aircraft == cls.user_aircraft: + nm += " - USER -" + aircrafts_list.push_back(nm) - f, d = hg.ImGuiListBox("Aircrafts", cls.selected_aircraft_id, aircrafts_list,20) - if f: - cls.selected_aircraft_id = d + f, d = hg.ImGuiListBox("Aircrafts", cls.selected_aircraft_id, aircrafts_list,20) + if f: + cls.selected_aircraft_id = d hg.ImGuiEnd() - cls.selected_aircraft = aircrafts[cls.selected_aircraft_id] - cls.selected_aircraft.gui() + if len(aircrafts) > 0: + cls.selected_machine = aircrafts[cls.selected_aircraft_id] + cls.selected_machine.gui() + else: + cls.selected_machine = None @classmethod @@ -1508,6 +1523,7 @@ def update(cls): cls.current_state = cls.current_state(hg.time_to_sec_f(Main.simulation_dt)) # Minimum frame rate security + # Used_dt is timestep used for Harfang 3D: hg.SceneUpdateSystems(cls.scene, cls.clocks, used_dt, cls.scene_physics, used_dt, 1000) # ,10,1000) diff --git a/source/states.py b/source/states.py index e87c501..d1f7d9c 100644 --- a/source/states.py +++ b/source/states.py @@ -10,9 +10,10 @@ import Machines def init_menu_state(): + Main.flag_display_selected_aircraft = False Main.flag_running = False - vcr.request_new_state("disable") + vcr.request_new_state(Main, "disable") Main.smart_camera.flag_inertia = True Main.set_renderless_mode(False) @@ -115,8 +116,6 @@ def init_menu_state(): def menu_state(dts): - - Main.t += dts Main.post_process.update_fading(dts) if Main.flag_sfx: @@ -124,15 +123,6 @@ def menu_state(dts): Main.master_sfx_volume = Main.post_process.fade_f tools.set_stereo_volume(Main.main_music_source, Main.master_sfx_volume) - if Main.intro_anim_id == 1: - Main.anim_camera_intro_dist.update(Main.t) - Main.anim_camera_intro_rot.update(Main.t) - - Main.smart_camera.follow_distance = Main.anim_camera_intro_dist.v - Main.smart_camera.lateral_rot = Main.anim_camera_intro_rot.v - - Main.smart_camera.update(Main.camera_intro, dts) - for carrier in Main.aircraft_carrier_allies: carrier.update_kinetics(dts) @@ -237,6 +227,18 @@ def menu_state(dts): Overlays.add_text2D("Left Thumb", hg.Vec2(x, (y - 300) / 900), s, c, Main.hud_font) Overlays.add_text2D("Right Thumb", hg.Vec2(x, (y - 320) / 900), s, c, Main.hud_font) + if vcr.is_init(): + vcr.update(Main, Main.simulation_dt) + + if Main.intro_anim_id == 1: + Main.anim_camera_intro_dist.update(Main.t) + Main.anim_camera_intro_rot.update(Main.t) + + Main.smart_camera.follow_distance = Main.anim_camera_intro_dist.v + Main.smart_camera.lateral_rot = Main.anim_camera_intro_rot.v + + Main.smart_camera.update(Main.camera_intro, dts) + if not Main.fading_to_next_state: f_start = False if Main.keyboard.Pressed(hg.K_Space): @@ -276,8 +278,9 @@ def menu_state(dts): # =================================== IN GAME ============================================= def init_main_state(): + Main.flag_display_selected_aircraft = False Main.smart_camera.flag_inertia = True - vcr.request_new_state("record") + vcr.request_new_state(Main, "record") Main.flag_running = False Main.fading_to_next_state = False Main.post_process.setup_fading(1, 1) @@ -328,8 +331,8 @@ def main_state(dts): elif Main.user_aircraft.type == Destroyable_Machine.TYPE_MISSILE_LAUNCHER: HUD_MissileLauncher.update(Main, Main.user_aircraft, Main.destroyables_list) - if Main.flag_display_selected_aircraft and Main.selected_aircraft is not None: - HUD_MissileTarget.display_selected_target(Main, Main.selected_aircraft) + if Main.flag_display_selected_aircraft and Main.selected_machine is not None: + HUD_MissileTarget.display_selected_target(Main, Main.selected_machine) if Main.flag_display_landing_trajectories: if Main.user_aircraft is not None: @@ -361,6 +364,10 @@ def main_state(dts): for sfx in Main.players_sfx: sfx.update_sfx(Main, dts) for sfx in Main.missiles_sfx: sfx.update_sfx(Main, dts) + + if vcr.is_init(): + vcr.update(Main, Main.simulation_dt) + camera_noise_level = 0 if Main.user_aircraft is not None: @@ -388,9 +395,6 @@ def main_state(dts): Main.smart_camera.update(cam, dts, camera_noise_level) - if vcr.is_init(): - vcr.update(Main.scene, Main.simulation_dt) - mission = Missions.get_current_mission() if Main.keyboard.Pressed(hg.K_L): @@ -418,6 +422,7 @@ def main_state(dts): # =================================== REPLAY MODE ============================================= def init_replay_state(): + Main.flag_display_selected_aircraft = False Main.smart_camera.flag_inertia = False Main.set_renderless_mode(False) Main.flag_running = False @@ -447,6 +452,7 @@ def replay_state(dts): if Main.flag_control_views: Main.control_views(Main.keyboard) + if Main.flag_display_HUD: if Main.user_aircraft is not None: if Main.user_aircraft.type == Destroyable_Machine.TYPE_AIRCRAFT: @@ -454,8 +460,9 @@ def replay_state(dts): elif Main.user_aircraft.type == Destroyable_Machine.TYPE_MISSILE_LAUNCHER: HUD_MissileLauncher.update(Main, Main.user_aircraft, Main.destroyables_list) - if Main.flag_display_selected_aircraft and Main.selected_aircraft is not None: - HUD_MissileTarget.display_selected_target(Main, Main.selected_aircraft) + if Main.flag_display_selected_aircraft and Main.selected_machine is not None: + HUD_MissileTarget.display_selected_target(Main, Main.selected_machine) + if Main.flag_display_machines_bounding_boxes: for machine in Destroyable_Machine.machines_list: if machine.is_activated: @@ -469,6 +476,9 @@ def replay_state(dts): Overlays.add_line(matrix * machine.bound_left, matrix * (machine.bound_left + hg.Vec3(-1, 0, 0)), hg.Color.Red, hg.Color.Red) Overlays.display_boxe(machine.get_world_bounding_boxe(), hg.Color.Yellow) + if vcr.is_init(): + vcr.update(Main, Main.simulation_dt) + camera_noise_level = 0 if Main.user_aircraft is not None: @@ -488,15 +498,14 @@ def replay_state(dts): if Main.satellite_view: cam = Main.satellite_camera - - - vcr.update(Main.scene, Main.simulation_dt) + Main.smart_camera.update(cam, dts, camera_noise_level) + if not Main.fading_to_next_state: if Main.keyboard.Pressed(hg.K_Tab) or vcr.request_state == "disable": - vcr.request_new_state("disable") + vcr.request_new_state(Main, "disable") Main.post_process.setup_fading(1, -1) Main.fading_to_next_state = True else: @@ -513,8 +522,9 @@ def replay_state(dts): # =================================== END GAME ============================================= def init_end_state(): + Main.flag_display_selected_aircraft = False Main.smart_camera.flag_inertia = True - vcr.request_new_state("disable") + vcr.request_new_state(Main, "disable") Main.set_renderless_mode(False) Main.flag_running = False Main.deactivate_cockpit_view() @@ -561,34 +571,47 @@ def init_end_state(): def end_state(dts): - Main.smart_camera.update(Main.camera, dts) - Main.update_kinetics(dts) if Main.flag_sfx: for sfx in Main.players_sfx: sfx.update_sfx(Main, dts) for sfx in Main.missiles_sfx: sfx.update_sfx(Main, dts) - if Main.flag_display_selected_aircraft and Main.selected_aircraft is not None: - HUD_MissileTarget.display_selected_target(Main, Main.selected_aircraft) + if Main.flag_display_selected_aircraft and Main.selected_machine is not None: + HUD_MissileTarget.display_selected_target(Main, Main.selected_machine) mission = Missions.get_current_mission() mission.update_end_phase(Main, dts) + if vcr.is_init(): + vcr.update(Main, Main.simulation_dt) + + Main.smart_camera.update(Main.camera, dts) + if not Main.fading_to_next_state: if Main.end_state_following_aircraft.flag_destroyed or Main.end_state_following_aircraft.wreck: Main.end_state_timer -= dts + if Main.keyboard.Pressed(hg.K_Tab) or Main.end_state_timer < 0 or Main.end_state_following_aircraft.flag_landed: Main.post_process.setup_fading(1, -1) Main.fading_to_next_state = True + Main.next_state = "menu" + elif vcr.request_state == "replay": + Main.post_process.setup_fading(1, -1) + Main.fading_to_next_state = True + Main.next_state = "replay" else: Main.post_process.update_fading(dts) if Main.flag_sfx: Main.master_sfx_volume = Main.post_process.fade_f if not Main.post_process.fade_running: - mission.reset() - Main.destroy_players() - init_menu_state() - return menu_state - + if Main.next_state == "replay": + Main.destroy_players() + init_replay_state() + return replay_state + else: + mission.reset() + Main.destroy_players() + init_menu_state() + return menu_state return end_state diff --git a/source/vcr.py b/source/vcr.py index 77768ed..dbc46c0 100644 --- a/source/vcr.py +++ b/source/vcr.py @@ -197,12 +197,10 @@ def stop_record(): return recording = False task_record_in_database = record_in_database() - progress_cptr = 0 - updating_database = True -def update_recording(dt): +def update_recording(main, dt): global records, timer, previous_timer, last_value_recorded if records is None: records = {} @@ -284,17 +282,19 @@ def start_play(main): playing = True -def stop_play(): +def stop_play(main): global playing, pausing playing = False pausing = False + clear_items() + main.clear_scene() def pause_play(): global pausing pausing = not pausing -def update_play(scene, dt): +def update_play(main, dt): global timer, playing ''' @@ -351,7 +351,9 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day timer += hg.time_to_sec_f(dt) if timer >= recorded_max_time: - stop_play() + timer = recorded_max_time + if not pausing: + pause_play() def update_gui_record(): @@ -435,7 +437,7 @@ def update_gui_record(): def update_gui_replay(main, keyboard): - global recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps, request_state + global selected_item_idx, recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps, request_state if hg.ImGuiBegin("Dogfight - Replayer"): if not playing: @@ -495,7 +497,21 @@ def update_gui_replay(main, keyboard): if hg.ImGuiButton("Next frame >") or keyboard.Pressed(hg.K_Add): timer += 1/recorded_fps if hg.ImGuiButton("Stop playing"): - stop_play() + stop_play(main) + + # Items list: + d, f = hg.ImGuiCheckbox("Display selected item", main.flag_display_selected_aircraft) + if d: + main.flag_display_selected_aircraft = f + if len(items_list) > 0: + f, d = hg.ImGuiListBox("Items", selected_item_idx, items_names,20) + if f: + selected_item_idx = d + if main.flag_display_selected_aircraft: + main.selected_machine = items[items_names[selected_item_idx]]["i"] + else: + main.selected_machine = None + hg.ImGuiEnd() def update_gui_wait_request(): @@ -521,17 +537,18 @@ def update_gui_updating_database(): # Call this to lock recorder: -def request_new_state(req_state): +def request_new_state(main, req_state): global request_state request_state = req_state + clear_items() if req_state == "disable": if recording: stop_record() elif playing: - stop_play() + stop_play(main) elif req_state == "record": if playing: - stop_play() + stop_play(main) elif req_state =="replay": if recording: stop_record() @@ -542,33 +559,33 @@ def validate_requested_state(): global state state = request_state + def update_gui(main,keyboard): - if state != request_state: + if updating_database: + update_gui_updating_database() + elif state != request_state: update_gui_wait_request() elif state == "record": - if updating_database: - update_gui_updating_database() - else: - update_gui_record() + update_gui_record() elif state == "replay": update_gui_replay(main, keyboard) elif state == "disable": update_gui_disable() -def update(scene, dt): +def update(main, dt): global updating_database - if recording: - update_recording(dt) - elif playing: - update_play(scene, dt) - elif updating_database: + if updating_database: try: next(task_record_in_database) except StopIteration as stop: updating_database = False - + elif recording: + update_recording(main, dt) + elif playing: + update_play(main, dt) + def before_quit_app(): global conn From fddcab9c05b90135aa1838a196dbb9a096c7f4ee Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Wed, 30 Nov 2022 13:10:31 +0100 Subject: [PATCH 07/17] destroy missiles in scene fix --- source/MachineDevice.py | 8 ++++---- source/master.py | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/source/MachineDevice.py b/source/MachineDevice.py index c67748e..776775a 100644 --- a/source/MachineDevice.py +++ b/source/MachineDevice.py @@ -334,10 +334,10 @@ def __init__(self, name, machine, slots_nodes): self.flag_hide_fitted_missiles = False def destroy(self): - if self.missiles is not None: - for missile in self.missiles: - if missile is not None: - missile.destroy() + #if self.missiles is not None: + # for missile in self.missiles: + # if missile is not None: + # missile.destroy() self.missiles = None self.num_slots = 0 self.slots_nodes = None diff --git a/source/master.py b/source/master.py index 0fe987d..eabde80 100644 --- a/source/master.py +++ b/source/master.py @@ -455,6 +455,7 @@ def clear_scene(cls): @classmethod def destroy_players(cls): + """ for aircraft in cls.players_ennemies: aircraft.destroy() for aircraft in cls.players_allies: @@ -467,6 +468,9 @@ def destroy_players(cls): ml.destroy() for ml in cls.missile_launchers_allies: ml.destroy() + """ + for machine in cls.destroyables_list: + machine.destroy() for cockpit in cls.scene_cockpit_aircrafts: cockpit.destroy() From e0ad255aff3054938254966a129cc8d893854d3c Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Thu, 1 Dec 2022 12:03:43 +0100 Subject: [PATCH 08/17] imrpoving recorder fix some HUD sprites --- source/HUD.py | 14 +++-- source/MachineDevice.py | 5 ++ source/assets/sprites/machine_gun_sight.png | Bin 4915 -> 5856 bytes source/assets/sprites/missile_sight.png | Bin 4170 -> 5736 bytes source/assets/sprites/target_sight.png | Bin 3518 -> 1563 bytes source/data_converter.py | 4 ++ source/states.py | 2 - source/vcr.py | 58 ++++++++++++++------ 8 files changed, 59 insertions(+), 24 deletions(-) diff --git a/source/HUD.py b/source/HUD.py index 616123f..329c6c7 100644 --- a/source/HUD.py +++ b/source/HUD.py @@ -229,16 +229,18 @@ def update(cls, main, machine): target = td.get_target() f = 1 # Main.HSL_postProcess.GetL() if target is not None: - p2D = main.get_2d_hud(target.get_parent_node().GetTransform().GetPos()) + target_pos = target.get_parent_node().GetTransform().GetPos() + target_distance = hg.Len(target_pos - machine.get_parent_node().GetTransform().GetPos()) + p2D = main.get_2d_hud(target_pos) if p2D is not None: a_pulse = 0.5 if (sin(tps * 20) > 0) else 0.75 if td.target_locked: c = hg.Color(1., 0.5, 0.5, a_pulse) - msg = "LOCKED - " + str(int(td.target_distance)) + msg = "LOCKED - " + str(int(target_distance)) x = (p2D.x / main.resolution.x - 32 / 1600) a = a_pulse else: - msg = str(int(td.target_distance)) + msg = str(int(target_distance)) x = (p2D.x / main.resolution.x - 12 / 1600) c = hg.Color(0.5, 1, 0.5, 0.75) @@ -261,9 +263,9 @@ def update(cls, main, machine): c = hg.Color(0, 1, 0, f) - Overlays.add_text2D("Target dist: %d" % (td.target_distance), hg.Vec2(0.05, 0.91), 0.016, c, main.hud_font) - Overlays.add_text2D("Target heading: %d" % (td.target_heading),hg.Vec2(0.05, 0.89), 0.016, c, main.hud_font) - Overlays.add_text2D("Target alt: %d" % (td.target_altitude), hg.Vec2(0.05, 0.87), 0.016, c, main.hud_font) + Overlays.add_text2D("Target dist: %d" % (target_distance), hg.Vec2(0.05, 0.91), 0.016, c, main.hud_font) + Overlays.add_text2D("Target heading: %d" % (target.get_heading()),hg.Vec2(0.05, 0.89), 0.016, c, main.hud_font) + Overlays.add_text2D("Target alt: %d" % (target.get_altitude()), hg.Vec2(0.05, 0.87), 0.016, c, main.hud_font) class HUD_Aircraft: diff --git a/source/MachineDevice.py b/source/MachineDevice.py index 776775a..7809877 100644 --- a/source/MachineDevice.py +++ b/source/MachineDevice.py @@ -231,6 +231,11 @@ def set_target_id(self, tid): if target.wreck or not target.activated: self.next_target() + def get_target_name(self): + if self.target_id == 0: + return None + return self.targets[self.target_id-1].name + def set_target_by_name(self, target_name): tid = 0 for i, tgt in enumerate(self.targets): diff --git a/source/assets/sprites/machine_gun_sight.png b/source/assets/sprites/machine_gun_sight.png index 5ff5b3a3fc7915f0ae1aec55f41914f30c3b4e3e..ab4dcb3ec8c8a229372f20cb6150e728b19b1905 100644 GIT binary patch literal 5856 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 a8pq0balc|}b^t_Iz{S}eRf;_S@BaWR8G;1> literal 4915 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 diff --git a/source/assets/sprites/missile_sight.png b/source/assets/sprites/missile_sight.png index 3033aa232be7613052c5f663122b3c69c1efa3f3..3e444d1c84835b96aea19d79ef4b00089e2c0c9a 100644 GIT binary patch literal 5736 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 N;j|1iOEet9{|7!@0O0@t literal 4170 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 diff --git a/source/data_converter.py b/source/data_converter.py index 1fe326a..68a9905 100644 --- a/source/data_converter.py +++ b/source/data_converter.py @@ -69,6 +69,10 @@ def serialize_vec3(v): return "{0:.6f};{1:.6f};{2:.6f}".format( v.x, v.y, v.z) +# bool("any_string") returns True +# bool("") (empty string) returns False +def serialize_boolean(v:bool): + return "1" if v else "" def deserialize_vec3(s): f = s.split(";") diff --git a/source/states.py b/source/states.py index d1f7d9c..80dc2dc 100644 --- a/source/states.py +++ b/source/states.py @@ -524,7 +524,6 @@ def replay_state(dts): def init_end_state(): Main.flag_display_selected_aircraft = False Main.smart_camera.flag_inertia = True - vcr.request_new_state(Main, "disable") Main.set_renderless_mode(False) Main.flag_running = False Main.deactivate_cockpit_view() @@ -565,7 +564,6 @@ def init_end_state(): Main.smart_camera.setup(SmartCamera.TYPE_FOLLOW, Main.camera, aircraft.get_parent_node()) Main.scene.SetCurrentCamera(Main.camera) Main.flag_running = True - vcr.validate_requested_state() return end_state diff --git a/source/vcr.py b/source/vcr.py index dbc46c0..265080f 100644 --- a/source/vcr.py +++ b/source/vcr.py @@ -69,6 +69,7 @@ def AddItem(item, params=[], name=None, container=None): name = item elif isinstance(item, Machines.Destroyable_Machine) and name is None: name = item.name + item.name = dc.conform_string(name) # !!! This could change the machine name in the scene !!! Need valid machine name to keep right links references (targets, missiles parents....) name = dc.conform_string(name) #print("VCR - add item " + name) @@ -356,7 +357,7 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day pause_play() -def update_gui_record(): +def update_gui_record(main): global selected_item_idx, recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps, request_state @@ -427,8 +428,8 @@ def update_gui_record(): recorded_fps = r["fps"] hg.ImGuiText("Record infos: Duration: %.2f - FPS: %d" % (recorded_max_time, recorded_fps)) - if hg.ImGuiButton("Enter replay mode"): - request_state = "replay" + if hg.ImGuiButton("Enter replay mode"): + request_new_state(main, "replay") elif recording: if hg.ImGuiButton("Stop recording"): @@ -567,7 +568,7 @@ def update_gui(main,keyboard): elif state != request_state: update_gui_wait_request() elif state == "record": - update_gui_record() + update_gui_record(main) elif state == "replay": update_gui_replay(main, keyboard) elif state == "disable": @@ -617,44 +618,69 @@ def deserialize_machine_state(machine:Machines.Destroyable_Machine, s:str): def serialize_missile_state(machine:Machines.Missile): matrix = dc.serialize_mat4(machine.get_parent_node().GetTransform().GetWorld()) v_move = dc.serialize_vec3(machine.get_move_vector()) - return matrix +":"+ v_move + wreck = dc.serialize_boolean(machine.wreck) + return matrix + ":" + v_move + ":" + wreck def serialize_aircraft_state(machine:Machines.Aircraft): matrix = dc.serialize_mat4(machine.get_parent_node().GetTransform().GetWorld()) v_move = dc.serialize_vec3(machine.get_move_vector()) health_lvl = str(machine.get_health_level()) - return matrix +":"+ v_move +":"+ health_lvl + wreck = dc.serialize_boolean(machine.wreck) + brake_level = str(machine.get_brake_level()) + flaps_level = str(machine.get_flaps_level()) + landed = dc.serialize_boolean(machine.flag_landed) + td = machine.get_device("TargettingDevice") + if td is not None: + target_name = td.get_target_name() + else: + target_name = None + if target_name is None: + target_name = str(target_name) + return matrix +":"+ v_move +":"+ health_lvl + ":" + wreck + ":" + brake_level + ":" + flaps_level + ":" + landed + ":" + target_name + + def serialize_missile_launcher_state(machine:MissileLauncherS400): matrix = dc.serialize_mat4(machine.get_parent_node().GetTransform().GetWorld()) - return matrix + wreck = dc.serialize_boolean(machine.wreck) + return matrix + ":" + wreck def serialize_ship_state(machine:Machines.Carrier): matrix = dc.serialize_mat4(machine.get_parent_node().GetTransform().GetWorld()) - return matrix + wreck = dc.serialize_boolean(machine.wreck) + return matrix + ":" + wreck + def deserialize_aircraft_state(machine:Machines.Aircraft, s:str): f = s.split(":") matrix = dc.deserialize_mat4(f[0]) - v_move = dc.deserialize_vec3(f[1]) + machine.v_move = dc.deserialize_vec3(f[1]) + machine.wreck = bool(f[3]) health_lvl = float(f[2]) machine.get_parent_node().GetTransform().SetWorld(matrix) - machine.v_move = v_move machine.set_health_level(health_lvl) + machine.reset_brake_level(float(f[4])) + machine.reset_flaps_level(float(f[5])) + machine.flag_landed = bool(f[6]) + td = machine.get_device("TargettingDevice") + if td is not None: + td.set_target_by_name(f[7]) def deserialize_missile_state(machine:Machines.Missile, s:str): f = s.split(":") matrix = dc.deserialize_mat4(f[0]) - v_move = dc.deserialize_vec3(f[1]) + machine.v_move = dc.deserialize_vec3(f[1]) + machine.wreck = bool(f[2]) machine.get_parent_node().GetTransform().SetWorld(matrix) - machine.v_move = v_move def deserialize_ship_state(machine:Machines.Carrier, s:str): - #f = s.split(":") - matrix = dc.deserialize_mat4(s) + f = s.split(":") + machine.wreck = bool(f[1]) + matrix = dc.deserialize_mat4(f[0]) machine.get_parent_node().GetTransform().SetWorld(matrix) def deserialize_missile_launcher_state(machine:MissileLauncherS400, s:str): - #f = s.split(":") - matrix = dc.deserialize_mat4(s) + f = s.split(":") + machine.wreck = bool(f[1]) + matrix = dc.deserialize_mat4(f[0]) machine.get_parent_node().GetTransform().SetWorld(matrix) \ No newline at end of file From 97586b98f8d4365a7d930239c1e2368ff6bb051a Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Thu, 1 Dec 2022 13:09:30 +0100 Subject: [PATCH 09/17] Event record system WIP --- source/Machines.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/source/Machines.py b/source/Machines.py index 9ecb97c..c1e22fa 100644 --- a/source/Machines.py +++ b/source/Machines.py @@ -50,6 +50,9 @@ def test_collision(self, nd: hg.Node): for ndt in self.collision_nodes: if nd == ndt: return True return False + + def hit(self, value, position): + pass # ===================================================================================================== @@ -341,6 +344,9 @@ def __init__(self, name, model_name, scene: hg.Scene, scene_physics, pipeline_re self.flag_focus = False + self.flag_record_hits = False # Used by recorder. When machine is hitted, the event is recorded with hit damages level + self.hits = [] + self.playfield_distance = 0 self.commands.update({"SET_HEALTH_LEVEL": self.set_health_level}) @@ -453,6 +459,7 @@ def has_focus(self): def reset(self, position=None, rotation=None): AnimatedModel.reset(self) + self.hits = [] if position is not None: self.start_position = position if rotation is not None: @@ -485,8 +492,11 @@ def calculate_view_matrix(self, camera): self.mat_view_prec = self.mat_view self.mat_view = cam_mat_view * self.parent_node.GetTransform().GetWorld() - def hit(self, value): - self.set_health_level(self.health_level - value) + def hit(self, value, position, timestamp = 0): + if not self.wreck: + self.set_health_level(self.health_level - value) + if self.flag_record_hits: + self.hits.append([timestamp, value, position]) # ??? position or hg.Vec3(position) ??? def destroy_nodes(self): AnimatedModel.destroy_nodes(self) @@ -970,6 +980,7 @@ def reset(self, position=None, rotation=None): # Don't call parent's function, World Matrix mustn't be reseted ! #Destroyable_Machine.reset(self, position, rotation) + self.hits = [] if position is not None: self.start_position = position @@ -1149,7 +1160,7 @@ def update_collisions(self, matrix, dts): collision_object = Collisions_Object.get_object_by_collision_node(hit.node) if collision_object is not None and hasattr(collision_object, "nationality") and collision_object.nationality != self.nationality: self.start_explosion() - collision_object.hit(self.get_hit_damages()) + collision_object.hit(self.get_hit_damages(), hit.P) #debug: if self.flag_user_control: @@ -1449,9 +1460,9 @@ def reset(self, position=None, rotation=None): self.flag_landed = self.start_landed - def hit(self, value): + def hit(self, value, position): + Destroyable_Machine.hit(self, value, position) if not self.wreck: - self.set_health_level(self.health_level - value) if self.health_level == 0 and not self.wreck: self.start_explosion() ia_ctrl = self.get_device("IAControlDevice") @@ -1535,7 +1546,7 @@ def update_thrust_level(self, dts): f = pow((alt - self.max_safe_altitude) / (self.max_altitude - self.max_safe_altitude), 2) perturb = (self.thrust_disfunction_noise.temporal_Perlin_noise(dts) * 0.5 + 0.5) * f collapse = 1 - perturb - self.hit(self.thrust_level * 0.001 * perturb) + self.hit(self.thrust_level * 0.001 * perturb, self.parent_node.GetTransform().GetPos()) dest = self.thrust_level_dest * collapse @@ -1855,7 +1866,7 @@ def update_collisions(self, matrix, dts): hit = collision["hits"][0] machine = self.get_machine_by_node(hit.node) if machine is not None and machine.type != Destroyable_Machine.TYPE_SHIP and machine.type != Destroyable_Machine.TYPE_GROUND: - self.hit(1) + self.hit(1, hit.P) else: self.ground_node_collision = hit.node alt = hit.P.y + bottom_alt @@ -1894,7 +1905,7 @@ def update_collisions(self, matrix, dts): return hg.TransformationMat4(pos, rot) def crash(self): - self.hit(1) + self.hit(1, self.parent_node.GetTransform().GetPos()) self.flag_crashed = True self.set_thrust_level(0) ia_ctrl = self.get_device("IAControlDevice") @@ -2373,7 +2384,7 @@ def destroy(self): self.destroy_nodes() self.flag_destroyed = True - def hit(self, value): + def hit(self, value, position): pass def update_kinetics(self, dts): From 3214e798b2912e11b1b5545668241f1ed6d921b7 Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Fri, 2 Dec 2022 13:08:30 +0100 Subject: [PATCH 10/17] event record brainstorming WIP --- source/master.py | 12 ++++++++++++ source/states.py | 8 ++++---- source/vcr.py | 35 ++++++++++++++++++++++------------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/source/master.py b/source/master.py index eabde80..5e078ed 100644 --- a/source/master.py +++ b/source/master.py @@ -73,6 +73,8 @@ class Main: win = None timestamp = 0 # Frame count. + timer = 0 # clock in s (incremented at each frame) + timestep = 1 / 60 # Frame dt simulation_dt = 0 # dt in ns used by simulation (kinetics & renderer) @@ -1463,6 +1465,16 @@ def update_inputs(cls): else: cls.flag_generic_controller = False + @classmethod + def reset_timestamp(cls): + cls.timestamp = 0 + cls.timer = 0 + + @classmethod + def update_timestamp(cls, dts): + cls.timestamp += 1 + cls.timer += dts + @classmethod def client_update(cls): cls.flag_client_ask_update_scene = True diff --git a/source/states.py b/source/states.py index 80dc2dc..29f4fd6 100644 --- a/source/states.py +++ b/source/states.py @@ -306,7 +306,7 @@ def init_main_state(): vcr.setup_items(Main) Main.num_start_frames = 10 - Main.timestamp = 0 + Main.reset_timestamp() Main.flag_running = True vcr.validate_requested_state() return main_state @@ -314,7 +314,7 @@ def init_main_state(): def main_state(dts): - Main.timestamp += 1 + Main.update_timestamp(dts) if not Main.flag_renderless: Main.post_process.update_fading(dts) if Main.flag_sfx: @@ -441,13 +441,13 @@ def init_replay_state(): Main.setup_views_carousel(True) Main.set_view_carousel("fps") - Main.timestamp = 0 + Main.reset_timestamp() Main.flag_running = True vcr.validate_requested_state() def replay_state(dts): - Main.timestamp += 1 + Main.post_process.update_fading(dts) if Main.flag_control_views: diff --git a/source/vcr.py b/source/vcr.py index 265080f..f03d6b9 100644 --- a/source/vcr.py +++ b/source/vcr.py @@ -30,6 +30,7 @@ render_head = False timer = 0 previous_timer = 0 +recorded_min_time = 0 recorded_max_time = 0 recorded_fps = 60 @@ -104,7 +105,7 @@ def init(): table_users_exists = c.fetchone() c.execute('''CREATE TABLE IF NOT EXISTS users(id_user INTEGER PRIMARY KEY, name TEXT, info TEXT)''') - c.execute('''CREATE TABLE IF NOT EXISTS records(id_rec INTEGER PRIMARY KEY, name TEXT, max_clock FLOAT, fps INT,scene_items TEXT, id_user INTEGER REFERENCES users, CONSTRAINT fk_users FOREIGN KEY (id_user) REFERENCES users(id_user) ON DELETE CASCADE)''') + c.execute('''CREATE TABLE IF NOT EXISTS records(id_rec INTEGER PRIMARY KEY, name TEXT, min_clock FLOAT, max_clock FLOAT, fps INT,scene_items TEXT, id_user INTEGER REFERENCES users, CONSTRAINT fk_users FOREIGN KEY (id_user) REFERENCES users(id_user) ON DELETE CASCADE)''') # add default user if table_users_exists is None: @@ -123,7 +124,7 @@ def create_scene_items_list(): return scene_items def start_record(name_record): - global recording, records, timer, current_id_rec, last_value_recorded + global recording, records, current_id_rec, last_value_recorded # check if we are already start if recording: @@ -132,7 +133,6 @@ def start_record(name_record): recording = True records = None last_value_recorded = {} - timer = 0 scene_items = ":".join(create_scene_items_list()) @@ -183,8 +183,8 @@ def record_in_database(): i+=1 if (i % fps_record) == 0: yield - - c.execute(f"UPDATE records SET max_clock={timer} WHERE id_rec={current_id_rec};") + + c.execute(f"UPDATE records SET max_clock={timer}, min_clock={recorded_min_time} WHERE id_rec={current_id_rec};") #c.execute(f"UPDATE records SET fps={fps_record} WHERE id_rec={current_id_rec};") conn.commit() yield @@ -202,12 +202,12 @@ def stop_record(): def update_recording(main, dt): - global records, timer, previous_timer, last_value_recorded + global records, timer, previous_timer, last_value_recorded, recorded_min_time if records is None: records = {} - timer = 0 - previous_timer = 0 + previous_timer = main.timer + timer = main.timer if timer - previous_timer > 1.0 / fps_record: #print(str(fps_record) + " " + str(timer)) record = {} @@ -244,9 +244,12 @@ def update_recording(main, dt): if n not in last_value_recorded or (n in last_value_recorded and v != last_value_recorded[n]): last_value_recorded[n] = record[n] = v + if len(records) == 0: + recorded_min_time = timer records[timer] = record previous_timer = timer - timer += hg.time_to_sec_f(dt) + + #timer += hg.time_to_sec_f(dt) def create_scene(main, scene_items): for scene_item in scene_items: @@ -272,6 +275,8 @@ def start_play(main): global playing, timer c = conn.cursor() + + # Get record items and create scene c.execute(f"SELECT scene_items FROM records where id_rec={current_id_play} and id_user = {current_id_user};") r = c.fetchone() if r is not None: @@ -282,6 +287,8 @@ def start_play(main): timer = 0 playing = True + + def stop_play(main): global playing, pausing @@ -310,7 +317,7 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day return hg.TransformationMat4(pos, rot) ''' - + c = conn.cursor() for name, params in items.items(): @@ -359,7 +366,7 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day def update_gui_record(main): - global selected_item_idx, recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps, request_state + global selected_item_idx, recorded_min_time, recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps, request_state if hg.ImGuiBegin("Dogfight - Recorder"): if adding_user: @@ -419,14 +426,16 @@ def update_gui_record(main): if r is not None: current_id_play = r["id_rec"] - c.execute(f'''SELECT max_clock, fps FROM records WHERE id_rec={current_id_play}''') + c.execute(f'''SELECT max_clock, min_clock, fps FROM records WHERE id_rec={current_id_play}''') recorded_max_time = 0 + recorded_min_time = 0 recorded_fps = 60 r = c.fetchone() if r is not None: + recorded_min_time = r["min_clock"] recorded_max_time = r["max_clock"] recorded_fps = r["fps"] - hg.ImGuiText("Record infos: Duration: %.2f - FPS: %d" % (recorded_max_time, recorded_fps)) + hg.ImGuiText("Record infos: Duration: %.2f - FPS: %d" % (recorded_max_time - recorded_min_time, recorded_fps)) if hg.ImGuiButton("Enter replay mode"): request_new_state(main, "replay") From 3edac5c46e5e9d2c12e0250f08727ad379c927d5 Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Mon, 5 Dec 2022 13:13:13 +0100 Subject: [PATCH 11/17] addinh event listener system to Destroyable_Machine --- source/MachineDevice.py | 263 ++++++++++++++++++++-------------------- source/Machines.py | 31 ++--- source/master.py | 2 +- source/states.py | 2 +- source/vcr.py | 15 ++- 5 files changed, 161 insertions(+), 152 deletions(-) diff --git a/source/MachineDevice.py b/source/MachineDevice.py index 7809877..7bdd7c8 100644 --- a/source/MachineDevice.py +++ b/source/MachineDevice.py @@ -65,6 +65,7 @@ def __init__(self, name, machine, start_state=False): self.name = name self.wreck = False self.commands = {"RESET": self.reset, "ACTIVATE": self.activate, "DEACTIVATE": self.deactivate} + self.events_history = [] def record_start_state(self, start_state=None): if start_state is None: @@ -75,7 +76,7 @@ def record_start_state(self, start_state=None): def reset(self): self.activated = self.start_state - def update(self, dts): + def update(self, dts, timestamp): pass def activate(self): @@ -149,7 +150,7 @@ def deactivate(self): # self.gear_moving_delay = Get anim time self.gear_anim_play = self.scene.PlayAnim(self.retract_anim, hg.ALM_Once, hg.E_InOutSine, hg.time_from_sec_f(0), hg.time_from_sec_f(self.gear_moving_delay), False, 1) - def update(self, dts): + def update(self, dts, timestamp): if self.flag_gear_moving: lvl = self.gear_moving_t / self.gear_moving_delay if self.gear_direction < 0: @@ -320,7 +321,7 @@ def update_target_lock(self, dts): self.target_out_of_range = True self.target_locking_state = 0 - def update(self, dts): + def update(self, dts, timestamp): self.update_target_lock(dts) # ===================================================================================================== @@ -480,7 +481,7 @@ def strike(self, i): fb.reset() fb.flow = 3000 - def update(self, dts): + def update(self, dts, timestamp): td = self.machine.get_device("TargettingDevice") if td is not None: targets = td.destroyable_targets @@ -542,11 +543,13 @@ def set_num_bullets(self, num): self.bullets_particles.particles_cnt_max = int(num) self.bullets_particles.reset() - def fire_machine_gun(self): + def fire_machine_gun(self, timestamp): if not self.wreck: + self.events_history.append([timestamp, "fire"]) self.bullets_particles.flow = 24 / 2 - def stop_machine_gun(self): + def stop_machine_gun(self, timestamp): + self.events_history.append([timestamp, "stop"]) self.bullets_particles.flow = 0 def is_gun_activated(self): @@ -646,7 +649,7 @@ def __init__(self, name, machine, control_mode=ControlDevice.CM_KEYBOARD, start_ ControlDevice.__init__(self, name, machine, "", "MissileLauncherUserInputsMapping", control_mode, start_state) self.pos_mem = None - def update(self, dts): + def update(self, dts, timestamp): if self.is_activated(): mat, pos, rot, aX, aY, aZ = self.machine.decompose_matrix() step = 0.5 @@ -737,7 +740,7 @@ def update_cm_gamepad(self, dts): def update_cm_mouse(self, dts): im = self.inputs_mapping["MissileLauncherUserInputsMapping"]["Mouse"] - def update(self, dts): + def update(self, dts, timestamp): if self.activated: if self.flag_user_control and self.machine.has_focus(): if self.control_mode == ControlDevice.CM_KEYBOARD: @@ -958,141 +961,141 @@ def set_control_mode(self, cmode): # =================== Functions ================================================================= - def update_cm_la3(self, dts): + def update_cm_la3(self, dts, timestamp): im = self.inputs_mapping["AircraftUserInputsMapping"]["LogitechAttack3"] for cmd, input_code in im.items(): if cmd in self.commands and input_code != "": - self.commands[cmd](input_code) + self.commands[cmd](input_code, timestamp) - def update_cm_keyboard(self, dts): + def update_cm_keyboard(self, dts, timestamp): im = self.inputs_mapping["AircraftUserInputsMapping"]["Keyboard"] for cmd, input_code in im.items(): if cmd in self.commands and input_code != "": - self.commands[cmd](input_code) + self.commands[cmd](input_code, timestamp) - def update_cm_gamepad(self, dts): + def update_cm_gamepad(self, dts, timestamp): im = self.inputs_mapping["AircraftUserInputsMapping"]["GamePad"] for cmd, input_code in im.items(): if cmd in self.commands and input_code != "": - self.commands[cmd](input_code) + self.commands[cmd](input_code, timestamp) - def update_cm_mouse(self, dts): + def update_cm_mouse(self, dts, timestamp): im = self.inputs_mapping["AircraftUserInputsMapping"]["Mouse"] - def update(self, dts): + def update(self, dts, timestamp): if self.activated: if self.flag_user_control and self.machine.has_focus(): if self.control_mode == ControlDevice.CM_KEYBOARD: - self.update_cm_keyboard(dts) + self.update_cm_keyboard(dts, timestamp) elif self.control_mode == ControlDevice.CM_GAMEPAD: - self.update_cm_gamepad(dts) + self.update_cm_gamepad(dts, timestamp) elif self.control_mode == ControlDevice.CM_MOUSE: - self.update_cm_mouse(dts) + self.update_cm_mouse(dts, timestamp) elif self.control_mode == ControlDevice.CM_LOGITECH_ATTACK_3: - self.update_cm_la3(dts) + self.update_cm_la3(dts, timestamp) # =============================== Keyboard commands ==================================== - def switch_activation_kb(self, value): + def switch_activation_kb(self, value, timestamp): pass - def next_pilot_kb(self, value): + def next_pilot_kb(self, value, timestamp): pass - def increase_health_level_kb(self, value): + def increase_health_level_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_health_level(self.machine.health_level + 0.01) - def decrease_health_level_kb(self, value): + def decrease_health_level_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_health_level(self.machine.health_level - 0.01) - def increase_thrust_level_kb(self, value): + def increase_thrust_level_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_thrust_level(self.machine.thrust_level_dest + 0.01) - def decrease_thrust_level_kb(self, value): + def decrease_thrust_level_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_thrust_level(self.machine.thrust_level_dest - 0.01) def set_thrust_level_kb(self, value): pass - def increase_brake_level_kb(self, value): + def increase_brake_level_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_brake_level(self.machine.brake_level_dest + 0.01) - def decrease_brake_level_kb(self, value): + def decrease_brake_level_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_brake_level(self.machine.brake_level_dest - 0.01) - def increase_flaps_level_kb(self, value): + def increase_flaps_level_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_flaps_level(self.machine.flaps_level + 0.01) - def decrease_flaps_level_kb(self, value): + def decrease_flaps_level_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_flaps_level(self.machine.flaps_level - 0.01) - def roll_left_kb(self, value): + def roll_left_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_roll_level(1) elif ControlDevice.keyboard.Released(value): self.machine.set_roll_level(0) - def roll_right_kb(self, value): + def roll_right_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_roll_level(-1) elif ControlDevice.keyboard.Released(value): self.machine.set_roll_level(0) - def set_roll_kb(self, value): + def set_roll_kb(self, value, timestamp): pass - def pitch_up_kb(self, value): + def pitch_up_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_pitch_level(1) elif ControlDevice.keyboard.Released(value): self.machine.set_pitch_level(0) - def pitch_down_kb(self, value): + def pitch_down_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_pitch_level(-1) elif ControlDevice.keyboard.Released(value): self.machine.set_pitch_level(0) - def set_pitch_kb(self, value): + def set_pitch_kb(self, value, timestamp): pass - def yaw_left_kb(self, value): + def yaw_left_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_yaw_level(-1) elif ControlDevice.keyboard.Released(value): self.machine.set_yaw_level(0) - def yaw_right_kb(self, value): + def yaw_right_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): self.machine.set_yaw_level(1) elif ControlDevice.keyboard.Released(value): self.machine.set_yaw_level(0) - def set_yaw_kb(self, value): + def set_yaw_kb(self, value, timestamp): pass - def switch_post_combustion_kb(self, value): + def switch_post_combustion_kb(self, value, timestamp): if ControlDevice.keyboard.Pressed(value): if self.machine.post_combustion: self.machine.deactivate_post_combustion() else: self.machine.activate_post_combustion() - def next_target_kb(self, value): + def next_target_kb(self, value, timestamp): if ControlDevice.keyboard.Pressed(value): td = self.machine.get_device("TargettingDevice") if td is not None: td.next_target() - def switch_gear_kb(self, value): + def switch_gear_kb(self, value, timestamp): if ControlDevice.keyboard.Pressed(value): if "Gear" in self.machine.devices and self.machine.devices["Gear"] is not None: gear = self.machine.devices["Gear"] @@ -1102,7 +1105,7 @@ def switch_gear_kb(self, value): else: gear.activate() - def activate_autopilot_kb(self, value): + def activate_autopilot_kb(self, value, timestamp): if ControlDevice.keyboard.Pressed(value): autopilot_device = self.machine.get_device("AutopilotControlDevice") if autopilot_device is not None: @@ -1110,131 +1113,131 @@ def activate_autopilot_kb(self, value): autopilot_device.activate() - def activate_ia_kb(self, value): + def activate_ia_kb(self, value, timestamp): if ControlDevice.keyboard.Pressed(value): ia_device = self.machine.get_device("IAControlDevice") if ia_device is not None: self.deactivate() ia_device.activate() - def switch_easy_steering_kb(self, value): + def switch_easy_steering_kb(self, value, timestamp): if ControlDevice.keyboard.Pressed(value): self.machine.flag_easy_steering = not self.machine.flag_easy_steering - def fire_machine_gun_kb(self, value): + def fire_machine_gun_kb(self, value, timestamp): if ControlDevice.keyboard.Down(value): n = self.machine.get_machinegun_count() for i in range(n): mgd = self.machine.get_device("MachineGunDevice_%02d" % i) if mgd is not None and not mgd.is_gun_activated(): - mgd.fire_machine_gun() + mgd.fire_machine_gun(timestamp) elif ControlDevice.keyboard.Released(value): n = self.machine.get_machinegun_count() for i in range(n): mgd = self.machine.get_device("MachineGunDevice_%02d" % i) if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun() + mgd.stop_machine_gun(timestamp) - def fire_missile_kb(self, value): + def fire_missile_kb(self, value, timestamp): if ControlDevice.keyboard.Pressed(value): md = self.machine.get_device("MissilesDevice") if md is not None: md.fire_missile() - def rearm_kb(self, value): + def rearm_kb(self, value, timestamp): if ControlDevice.keyboard.Pressed(value): self.machine.rearm() # =============================== Logitech Attack 3 ==================================== - def switch_activation_la3(self, value): + def switch_activation_la3(self, value, timestamp): pass - def next_pilot_la3(self, value): + def next_pilot_la3(self, value, timestamp): pass - def increase_health_level_la3(self, value): + def increase_health_level_la3(self, value, timestamp): pass - def decrease_health_level_la3(self, value): + def decrease_health_level_la3(self, value, timestamp): pass - def increase_thrust_level_la3(self, value): + def increase_thrust_level_la3(self, value, timestamp): pass - def decrease_thrust_level_la3(self, value): + def decrease_thrust_level_la3(self, value, timestamp): pass - def set_thrust_level_la3(self, value): + def set_thrust_level_la3(self, value, timestamp): # epsilon = 0.1 # threshold to cancel the device's jitter v = -ControlDevice.generic_controller.Axes(value) self.machine.set_thrust_level((v + 1.0) / 2.0) - def increase_brake_level_la3(self, value): + def increase_brake_level_la3(self, value, timestamp): if ControlDevice.generic_controller.Down(value): self.machine.set_brake_level(self.machine.brake_level_dest + 0.01) - def decrease_brake_level_la3(self, value): + def decrease_brake_level_la3(self, value, timestamp): if ControlDevice.generic_controller.Down(value): self.machine.set_brake_level(self.machine.brake_level_dest - 0.01) - def increase_flaps_level_la3(self, value): + def increase_flaps_level_la3(self, value, timestamp): if ControlDevice.generic_controller.Down(value): self.machine.set_flaps_level(self.machine.flaps_level_dest + 0.01) - def decrease_flaps_level_la3(self, value): + def decrease_flaps_level_la3(self, value, timestamp): if ControlDevice.generic_controller.Down(value): self.machine.set_flaps_level(self.machine.flaps_level_dest - 0.01) - def roll_left_la3(self, value): + def roll_left_la3(self, value, timestamp): pass - def roll_right_la3(self, value): + def roll_right_la3(self, value, timestamp): pass - def set_roll_la3(self, value): + def set_roll_la3(self, value, timestamp): v = -ControlDevice.generic_controller.Axes(value) self.machine.set_roll_level(v) - def set_pitch_la3(self, value): + def set_pitch_la3(self, value, timestamp): v = -ControlDevice.generic_controller.Axes(value) self.machine.set_pitch_level(v) - def pitch_up_la3(self, value): + def pitch_up_la3(self, value, timestamp): pass - def pitch_down_la3(self, value): + def pitch_down_la3(self, value, timestamp): pass - def yaw_left_la3(self, value): + def yaw_left_la3(self, value, timestamp): if ControlDevice.generic_controller.Down(value): self.machine.set_yaw_level(-1) elif ControlDevice.generic_controller.Released(value): self.machine.set_yaw_level(0) - def yaw_right_la3(self, value): + def yaw_right_la3(self, value, timestamp): if ControlDevice.generic_controller.Down(value): self.machine.set_yaw_level(1) elif ControlDevice.generic_controller.Released(value): self.machine.set_yaw_level(0) - def set_yaw_la3(self, value): + def set_yaw_la3(self, value, timestamp): pass - def switch_post_combustion_la3(self, value): + def switch_post_combustion_la3(self, value, timestamp): if ControlDevice.generic_controller.Pressed(value): if self.machine.post_combustion: self.machine.deactivate_post_combustion() else: self.machine.activate_post_combustion() - def next_target_la3(self, value): + def next_target_la3(self, value, timestamp): if ControlDevice.generic_controller.Pressed(value): td = self.machine.get_device("TargettingDevice") if td is not None: td.next_target() - def switch_gear_la3(self, value): + def switch_gear_la3(self, value, timestamp): if ControlDevice.generic_controller.Pressed(value): if "Gear" in self.machine.devices and self.machine.devices["Gear"] is not None: gear = self.machine.devices["Gear"] @@ -1245,32 +1248,32 @@ def switch_gear_la3(self, value): gear.activate() - def activate_autopilot_la3(self, value): + def activate_autopilot_la3(self, value, timestamp): pass - def activate_ia_la3(self, value): + def activate_ia_la3(self, value, timestamp): pass - def switch_easy_steering_la3(self, value): + def switch_easy_steering_la3(self, value, timestamp): pass - def fire_machine_gun_la3(self, value): + def fire_machine_gun_la3(self, value, timestamp): if ControlDevice.generic_controller.Down(value): n = self.machine.get_machinegun_count() for i in range(n): mgd = self.machine.get_device("MachineGunDevice_%02d" % i) if mgd is not None and not mgd.is_gun_activated(): - mgd.fire_machine_gun() + mgd.fire_machine_gun(timestamp) elif ControlDevice.generic_controller.Released(value): n = self.machine.get_machinegun_count() for i in range(n): mgd = self.machine.get_device("MachineGunDevice_%02d" % i) if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun() + mgd.stop_machine_gun(timestamp) - def fire_missile_la3(self, value): + def fire_missile_la3(self, value, timestamp): if ControlDevice.generic_controller.Pressed(value): md = self.machine.get_device("MissilesDevice") if md is not None: @@ -1280,93 +1283,93 @@ def fire_missile_la3(self, value): # =============================== Gamepad commands ==================================== - def switch_activation_gp(self, value): + def switch_activation_gp(self, value, timestamp): pass - def next_pilot_gp(self, value): + def next_pilot_gp(self, value, timestamp): pass - def increase_health_level_gp(self, value): + def increase_health_level_gp(self, value, timestamp): pass - def decrease_health_level_gp(self, value): + def decrease_health_level_gp(self, value, timestamp): pass - def increase_thrust_level_gp(self, value): + def increase_thrust_level_gp(self, value, timestamp): pass - def decrease_thrust_level_gp(self, value): + def decrease_thrust_level_gp(self, value, timestamp): pass - def set_thrust_level_gp(self, value): + def set_thrust_level_gp(self, value, timestamp): epsilon = 0.1 v = -ControlDevice.gamepad.Axes(value) if v < - epsilon or v > epsilon: self.machine.set_thrust_level(self.machine.thrust_level_dest + v * 0.01) - def increase_brake_level_gp(self, value): + def increase_brake_level_gp(self, value, timestamp): if ControlDevice.gamepad.Down(value): self.machine.set_brake_level(self.machine.brake_level_dest + 0.01) - def decrease_brake_level_gp(self, value): + def decrease_brake_level_gp(self, value, timestamp): if ControlDevice.gamepad.Down(value): self.machine.set_brake_level(self.machine.brake_level_dest - 0.01) - def increase_flaps_level_gp(self, value): + def increase_flaps_level_gp(self, value, timestamp): if ControlDevice.gamepad.Down(value): self.machine.set_flaps_level(self.machine.flaps_level_dest + 0.01) - def decrease_flaps_level_gp(self, value): + def decrease_flaps_level_gp(self, value, timestamp): if ControlDevice.gamepad.Down(value): self.machine.set_flaps_level(self.machine.flaps_level_dest - 0.01) - def roll_left_gp(self, value): + def roll_left_gp(self, value, timestamp): pass - def roll_right_gp(self, value): + def roll_right_gp(self, value, timestamp): pass - def set_roll_gp(self, value): + def set_roll_gp(self, value, timestamp): v = -ControlDevice.gamepad.Axes(value) self.machine.set_roll_level(v) - def pitch_up_gp(self, value): + def pitch_up_gp(self, value, timestamp): pass - def pitch_down_gp(self, value): + def pitch_down_gp(self, value, timestamp): pass - def set_pitch_gp(self, value): + def set_pitch_gp(self, value, timestamp): v = -ControlDevice.gamepad.Axes(value) self.machine.set_pitch_level(v) - def yaw_left_gp(self, value): + def yaw_left_gp(self, value, timestamp): pass - def yaw_right_gp(self, value): + def yaw_right_gp(self, value, timestamp): pass - def set_yaw_gp(self, value): + def set_yaw_gp(self, value, timestamp): epsilon = 0.016 v = ControlDevice.gamepad.Axes(value) if -epsilon < v < epsilon: v = 0 self.machine.set_yaw_level(v) - def switch_post_combustion_gp(self, value): + def switch_post_combustion_gp(self, value, timestamp): if ControlDevice.gamepad.Pressed(value): if self.machine.post_combustion: self.machine.deactivate_post_combustion() else: self.machine.activate_post_combustion() - def next_target_gp(self, value): + def next_target_gp(self, value, timestamp): if ControlDevice.gamepad.Pressed(value): td = self.machine.get_device("TargettingDevice") if td is not None: td.next_target() - def switch_gear_gp(self, value): + def switch_gear_gp(self, value, timestamp): if ControlDevice.gamepad.Pressed(value): if "Gear" in self.machine.devices and self.machine.devices["Gear"] is not None: gear = self.machine.devices["Gear"] @@ -1376,34 +1379,34 @@ def switch_gear_gp(self, value): else: gear.activate() - def activate_autopilot_gp(self, value): + def activate_autopilot_gp(self, value, timestamp): pass - def activate_ia_gp(self, value): + def activate_ia_gp(self, value, timestamp): if ControlDevice.gamepad.Pressed(value): ia_device = self.machine.get_device("IAControlDevice") if ia_device is not None: self.deactivate() ia_device.activate() - def switch_easy_steering_gp(self, value): + def switch_easy_steering_gp(self, value, timestamp): pass - def fire_machine_gun_gp(self, value): + def fire_machine_gun_gp(self, value, timestamp): if ControlDevice.gamepad.Down(value): n = self.machine.get_machinegun_count() for i in range(n): mgd = self.machine.get_device("MachineGunDevice_%02d" % i) if mgd is not None and not mgd.is_gun_activated(): - mgd.fire_machine_gun() + mgd.fire_machine_gun(timestamp) elif ControlDevice.gamepad.Released(value): n = self.machine.get_machinegun_count() for i in range(n): mgd = self.machine.get_device("MachineGunDevice_%02d" % i) if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun() + mgd.stop_machine_gun(timestamp) - def fire_missile_gp(self, value): + def fire_missile_gp(self, value, timestamp): if ControlDevice.gamepad.Pressed(value): md = self.machine.get_device("MissilesDevice") if md is not None: @@ -1675,7 +1678,7 @@ def update_controlled_devices(self, dts): tp = max(-1, min(1, diff / 10)) aircraft.set_pitch_level(-tp) - def update(self, dts): + def update(self, dts, timestamp): if self.activated: if self.flag_user_control and self.machine.has_focus(): if self.control_mode == ControlDevice.CM_KEYBOARD: @@ -1839,19 +1842,19 @@ def calculate_landing_target_point(self, aircraft, landing_target, landing_proj) else: return landing_proj - def update_controlled_device(self, dts): + def update_controlled_device(self, dts, timestamp): aircraft = self.machine if not aircraft.wreck and not aircraft.flag_going_to_takeoff_position: if self.IA_command == AircraftIAControlDevice.IA_COM_IDLE: - self.update_IA_idle(aircraft) + self.update_IA_idle(aircraft, timestamp) elif self.IA_command == AircraftIAControlDevice.IA_COM_LIFTOFF: - self.update_IA_liftoff(aircraft, dts) + self.update_IA_liftoff(aircraft, dts, timestamp) elif self.IA_command == AircraftIAControlDevice.IA_COM_FIGHT: - self.update_IA_fight(aircraft, dts) + self.update_IA_fight(aircraft, dts, timestamp) elif self.IA_command == AircraftIAControlDevice.IA_COM_LANDING: - self.update_IA_landing(aircraft, dts) + self.update_IA_landing(aircraft, dts, timestamp) - def update_IA_liftoff(self, aircraft, dts): + def update_IA_liftoff(self, aircraft, dts, timestamp): self.IA_flag_landing_target_found = False aircraft.set_flaps_level(1) if self.flag_IA_start_liftoff: @@ -1884,7 +1887,7 @@ def update_IA_liftoff(self, aircraft, dts): else: self.IA_command = AircraftIAControlDevice.IA_COM_FIGHT - def update_IA_idle(self, aircraft): + def update_IA_idle(self, aircraft, timestamp): autopilot = aircraft.devices["AutopilotControlDevice"] if autopilot is not None: autopilot.set_autopilot_speed(400 / 3.6) @@ -1915,7 +1918,7 @@ def get_nearest_landing_target(self, aircraft): distances.sort(key=lambda p: p["distance"]) return distances[0]["landing_target"] - def update_IA_landing(self, aircraft, dts): + def update_IA_landing(self, aircraft, dts, timestamp): if "AutopilotControlDevice" in aircraft.devices and aircraft.devices["AutopilotControlDevice"] is not None: autopilot = aircraft.devices["AutopilotControlDevice"] if not self.IA_flag_landing_target_found: @@ -1996,7 +1999,7 @@ def update_IA_landing(self, aircraft, dts): self.IA_liftoff_delay = 2 self.IA_command = AircraftIAControlDevice.IA_COM_LIFTOFF - def update_IA_fight(self, aircraft, dts): + def update_IA_fight(self, aircraft, dts, timestamp): autopilot = aircraft.devices["AutopilotControlDevice"] if autopilot is not None: if "Gear" in aircraft.devices and aircraft.devices["Gear"] is not None: @@ -2066,13 +2069,13 @@ def update_IA_fight(self, aircraft, dts): for i in range(n): mgd = aircraft.get_device("MachineGunDevice_%02d" % i) if mgd is not None and not mgd.is_gun_activated(): - mgd.fire_machine_gun() + mgd.fire_machine_gun(timestamp) else: n = aircraft.get_machinegun_count() for i in range(n): mgd = aircraft.get_device("MachineGunDevice_%02d" % i) if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun() + mgd.stop_machine_gun(timestamp) flag_missiles_ok = False if md is not None: @@ -2145,29 +2148,29 @@ def activate_user_control_gp(self, value): # ==================================================================================== - def update_cm_keyboard(self, dts): + def update_cm_keyboard(self, dts, timestamp): im = self.inputs_mapping["AircraftIAInputsMapping"]["Keyboard"] for cmd, input_code in im.items(): if cmd in self.commands and input_code != "": self.commands[cmd](input_code) - def update_cm_gamepad(self, dts): + def update_cm_gamepad(self, dts, timestamp): im = self.inputs_mapping["AircraftIAInputsMapping"]["GamePad"] for cmd, input_code in im.items(): if cmd in self.commands and input_code != "": self.commands[cmd](input_code) - def update_cm_mouse(self, dts): + def update_cm_mouse(self, dts, timestamp): im = self.inputs_mapping["AircraftIAInputsMapping"]["Mouse"] - def update(self, dts): + def update(self, dts, timestamp): if self.activated: if self.flag_user_control and self.machine.has_focus(): if self.control_mode == ControlDevice.CM_KEYBOARD: - self.update_cm_keyboard(dts) + self.update_cm_keyboard(dts, timestamp) elif self.control_mode == ControlDevice.CM_GAMEPAD: - self.update_cm_gamepad(dts) + self.update_cm_gamepad(dts, timestamp) elif self.control_mode == ControlDevice.CM_MOUSE: - self.update_cm_mouse(dts) + self.update_cm_mouse(dts, timestamp) - self.update_controlled_device(dts) + self.update_controlled_device(dts, timestamp) diff --git a/source/Machines.py b/source/Machines.py index c1e22fa..1793f40 100644 --- a/source/Machines.py +++ b/source/Machines.py @@ -344,7 +344,8 @@ def __init__(self, name, model_name, scene: hg.Scene, scene_physics, pipeline_re self.flag_focus = False - self.flag_record_hits = False # Used by recorder. When machine is hitted, the event is recorded with hit damages level + self.event_listeners = [] #list of functions used to listen events - Current lestenable events : "hit" + # listener prototype: listener(str event_name, list parameters) self.hits = [] self.playfield_distance = 0 @@ -433,9 +434,9 @@ def remove_device(self, device_name): return self.devices.pop(device_name) return None - def update_devices(self, dts): + def update_devices(self, dts, timestamp): for name, device in self.devices.items(): - device.update(dts) + device.update(dts, timestamp) def get_device(self, device_name): if device_name in self.devices: @@ -495,8 +496,8 @@ def calculate_view_matrix(self, camera): def hit(self, value, position, timestamp = 0): if not self.wreck: self.set_health_level(self.health_level - value) - if self.flag_record_hits: - self.hits.append([timestamp, value, position]) # ??? position or hg.Vec3(position) ??? + for listener in self.event_listeners: + listener("hit",[value, position, timestamp]) # ??? position or hg.Vec3(position) ??? def destroy_nodes(self): AnimatedModel.destroy_nodes(self) @@ -799,7 +800,7 @@ def get_physics_parameters(self): "flag_easy_steering": False } - def update_kinetics(self, dts): + def update_kinetics(self, dts, timestamp): if self.activated: if self.custom_matrix is not None: matrix = self.custom_matrix @@ -831,7 +832,7 @@ def update_kinetics(self, dts): self.rec_linear_speed() self.update_linear_acceleration() - self.update_devices(dts) + self.update_devices(dts, timestamp) self.update_mobile_parts(dts) self.update_feedbacks(dts) @@ -1189,11 +1190,11 @@ def get_physics_parameters(self): "flag_easy_steering": False } - def update_kinetics(self, dts): + def update_kinetics(self, dts, timestamp): if self.activated: - self.update_devices(dts) + self.update_devices(dts, timestamp) self.life_cptr += dts @@ -1947,17 +1948,17 @@ def get_physics_parameters(self): "flag_easy_steering": self.flag_easy_steering } - def update_kinetics(self, dts): + def update_kinetics(self, dts, timestamp): # Custom physics (but keep inner collisions system) if self.flag_custom_physics_mode: - Destroyable_Machine.update_kinetics(self, dts) + Destroyable_Machine.update_kinetics(self, dts, timestamp) # Inner physics else: if self.activated: - self.update_devices(dts) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + self.update_devices(dts, timestamp) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # # ========================= Flight physics Repositionning after landing : @@ -2387,7 +2388,7 @@ def destroy(self): def hit(self, value, position): pass - def update_kinetics(self, dts): + def update_kinetics(self, dts, timestamp): rot = self.radar.GetTransform().GetRot() rot.y += radians(45 * dts) self.radar.GetTransform().SetRot(rot) @@ -2420,5 +2421,5 @@ def get_thrust_level(self): def get_brake_level(self): return self.brake_level - def update_kinetics(self, dts): - Destroyable_Machine.update_kinetics(self, dts) \ No newline at end of file + def update_kinetics(self, dts, timestamp): + Destroyable_Machine.update_kinetics(self, dts, timestamp) \ No newline at end of file diff --git a/source/master.py b/source/master.py index 5e078ed..50de632 100644 --- a/source/master.py +++ b/source/master.py @@ -1137,7 +1137,7 @@ def update_kinetics(cls, dts): # dm.update_collision_nodes_matrices() for dm in Destroyable_Machine.update_list: - dm.update_kinetics(dts) + dm.update_kinetics(dts, cls.timestamp) cls.display_machine_vectors(dm) diff --git a/source/states.py b/source/states.py index 29f4fd6..5a7519d 100644 --- a/source/states.py +++ b/source/states.py @@ -124,7 +124,7 @@ def menu_state(dts): tools.set_stereo_volume(Main.main_music_source, Main.master_sfx_volume) for carrier in Main.aircraft_carrier_allies: - carrier.update_kinetics(dts) + carrier.update_kinetics(dts, Main.timestamp) if Main.display_dark_design: Main.spr_design_menu.set_position(0.5 * Main.resolution.x, 0.5 * Main.resolution.y) diff --git a/source/vcr.py b/source/vcr.py index f03d6b9..8bc5918 100644 --- a/source/vcr.py +++ b/source/vcr.py @@ -284,7 +284,8 @@ def start_play(main): if len(scene_items) > 0: main.destroy_players() create_scene(main, scene_items) - timer = 0 + timer = recorded_min_time + print(str(timer)) playing = True @@ -447,7 +448,7 @@ def update_gui_record(main): def update_gui_replay(main, keyboard): - global selected_item_idx, recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps, request_state + global selected_item_idx, recorded_min_time, recorded_max_time, timer, fps_record, current_id_play, selected_record, current_id_user, adding_user, user_name, user_info, recorded_fps, request_state if hg.ImGuiBegin("Dogfight - Replayer"): if not playing: @@ -476,14 +477,16 @@ def update_gui_replay(main, keyboard): if r is not None: current_id_play = r["id_rec"] - c.execute(f'''SELECT max_clock, fps FROM records WHERE id_rec={current_id_play}''') + c.execute(f'''SELECT max_clock, min_clock, fps FROM records WHERE id_rec={current_id_play}''') + recorded_min_time = 0 recorded_max_time = 0 recorded_fps = 60 r = c.fetchone() if r is not None: + recorded_min_time = r["min_clock"] recorded_max_time = r["max_clock"] recorded_fps = r["fps"] - hg.ImGuiText("Record infos: Duration: %.2f - FPS: %d" % (recorded_max_time, recorded_fps)) + hg.ImGuiText("Record infos: Duration: %.2f - FPS: %d" % (recorded_max_time - recorded_min_time, recorded_fps)) if hg.ImGuiButton("Start play"): start_play(main) @@ -493,7 +496,7 @@ def update_gui_replay(main, keyboard): else: if recorded_max_time: - timer = hg.ImGuiSliderFloat("Timeline", timer, 0, recorded_max_time)[1] + timer = hg.ImGuiSliderFloat("Timeline", timer, recorded_min_time, recorded_max_time)[1] if pausing: lbl = "Resume" else: @@ -620,6 +623,8 @@ def deserialize_machine_state(machine:Machines.Destroyable_Machine, s:str): deserialize_aircraft_state(machine, s) elif machine.type == Machines.Destroyable_Machine.TYPE_MISSILE: deserialize_missile_state(machine, s) + elif machine.type == Machines.Destroyable_Machine.TYPE_MISSILE_LAUNCHER: + deserialize_missile_launcher_state(machine, s) elif machine.type == Machines.Destroyable_Machine.TYPE_SHIP: deserialize_ship_state(machine, s) From f1763533e8bf102508e0520aa2c1774034d149b8 Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Wed, 7 Dec 2022 08:24:51 +0100 Subject: [PATCH 12/17] events record WIP --- source/MachineDevice.py | 312 ++++++++++++++++++++------------------- source/Machines.py | 48 +++--- source/master.py | 14 +- source/network_server.py | 32 ++-- source/states.py | 2 +- source/vcr.py | 39 +++-- 6 files changed, 243 insertions(+), 204 deletions(-) diff --git a/source/MachineDevice.py b/source/MachineDevice.py index 7bdd7c8..a791c79 100644 --- a/source/MachineDevice.py +++ b/source/MachineDevice.py @@ -57,6 +57,9 @@ def get_landing_vector(self): class MachineDevice: + framecount = 0 #Updated with Main.framecount + timer = 0 + # Start state: activated or not. def __init__(self, name, machine, start_state=False): self.activated = start_state @@ -65,7 +68,6 @@ def __init__(self, name, machine, start_state=False): self.name = name self.wreck = False self.commands = {"RESET": self.reset, "ACTIVATE": self.activate, "DEACTIVATE": self.deactivate} - self.events_history = [] def record_start_state(self, start_state=None): if start_state is None: @@ -76,7 +78,7 @@ def record_start_state(self, start_state=None): def reset(self): self.activated = self.start_state - def update(self, dts, timestamp): + def update(self, dts): pass def activate(self): @@ -150,7 +152,7 @@ def deactivate(self): # self.gear_moving_delay = Get anim time self.gear_anim_play = self.scene.PlayAnim(self.retract_anim, hg.ALM_Once, hg.E_InOutSine, hg.time_from_sec_f(0), hg.time_from_sec_f(self.gear_moving_delay), False, 1) - def update(self, dts, timestamp): + def update(self, dts): if self.flag_gear_moving: lvl = self.gear_moving_t / self.gear_moving_delay if self.gear_direction < 0: @@ -321,7 +323,7 @@ def update_target_lock(self, dts): self.target_out_of_range = True self.target_locking_state = 0 - def update(self, dts, timestamp): + def update(self, dts): self.update_target_lock(dts) # ===================================================================================================== @@ -481,7 +483,7 @@ def strike(self, i): fb.reset() fb.flow = 3000 - def update(self, dts, timestamp): + def update(self, dts): td = self.machine.get_device("TargettingDevice") if td is not None: targets = td.destroyable_targets @@ -516,6 +518,8 @@ def update(self, dts, timestamp): break """ + + #Collision using raycast: rc_len = hg.Len(p1 - pos_fb) hit = self.scene_physics.RaycastFirstHit(self.scene, pos_fb, p1) if 0 < hit.t < rc_len: @@ -525,7 +529,7 @@ def update(self, dts, timestamp): cnds = target.get_collision_nodes() for nd in cnds: if nd == hit.node: - target.hit(0.1) + target.hit(0.1, hit.P) bullet.v_move = target.v_move self.strike(i) break @@ -543,20 +547,22 @@ def set_num_bullets(self, num): self.bullets_particles.particles_cnt_max = int(num) self.bullets_particles.reset() - def fire_machine_gun(self, timestamp): + def activate(self): if not self.wreck: - self.events_history.append([timestamp, "fire"]) + super().activate() self.bullets_particles.flow = 24 / 2 - - def stop_machine_gun(self, timestamp): - self.events_history.append([timestamp, "stop"]) + + def deactivate(self): + super().deactivate() self.bullets_particles.flow = 0 + """ def is_gun_activated(self): if self.bullets_particles.flow == 0: return False else: return True + """ def get_new_bullets_count(self): return self.bullets_particles.num_new @@ -649,7 +655,7 @@ def __init__(self, name, machine, control_mode=ControlDevice.CM_KEYBOARD, start_ ControlDevice.__init__(self, name, machine, "", "MissileLauncherUserInputsMapping", control_mode, start_state) self.pos_mem = None - def update(self, dts, timestamp): + def update(self, dts): if self.is_activated(): mat, pos, rot, aX, aY, aZ = self.machine.decompose_matrix() step = 0.5 @@ -740,7 +746,7 @@ def update_cm_gamepad(self, dts): def update_cm_mouse(self, dts): im = self.inputs_mapping["MissileLauncherUserInputsMapping"]["Mouse"] - def update(self, dts, timestamp): + def update(self, dts): if self.activated: if self.flag_user_control and self.machine.has_focus(): if self.control_mode == ControlDevice.CM_KEYBOARD: @@ -961,141 +967,141 @@ def set_control_mode(self, cmode): # =================== Functions ================================================================= - def update_cm_la3(self, dts, timestamp): + def update_cm_la3(self, dts): im = self.inputs_mapping["AircraftUserInputsMapping"]["LogitechAttack3"] for cmd, input_code in im.items(): if cmd in self.commands and input_code != "": - self.commands[cmd](input_code, timestamp) + self.commands[cmd](input_code) - def update_cm_keyboard(self, dts, timestamp): + def update_cm_keyboard(self, dts): im = self.inputs_mapping["AircraftUserInputsMapping"]["Keyboard"] for cmd, input_code in im.items(): if cmd in self.commands and input_code != "": - self.commands[cmd](input_code, timestamp) + self.commands[cmd](input_code) - def update_cm_gamepad(self, dts, timestamp): + def update_cm_gamepad(self, dts): im = self.inputs_mapping["AircraftUserInputsMapping"]["GamePad"] for cmd, input_code in im.items(): if cmd in self.commands and input_code != "": - self.commands[cmd](input_code, timestamp) + self.commands[cmd](input_code) - def update_cm_mouse(self, dts, timestamp): + def update_cm_mouse(self, dts): im = self.inputs_mapping["AircraftUserInputsMapping"]["Mouse"] - def update(self, dts, timestamp): + def update(self, dts): if self.activated: if self.flag_user_control and self.machine.has_focus(): if self.control_mode == ControlDevice.CM_KEYBOARD: - self.update_cm_keyboard(dts, timestamp) + self.update_cm_keyboard(dts) elif self.control_mode == ControlDevice.CM_GAMEPAD: - self.update_cm_gamepad(dts, timestamp) + self.update_cm_gamepad(dts) elif self.control_mode == ControlDevice.CM_MOUSE: - self.update_cm_mouse(dts, timestamp) + self.update_cm_mouse(dts) elif self.control_mode == ControlDevice.CM_LOGITECH_ATTACK_3: - self.update_cm_la3(dts, timestamp) + self.update_cm_la3(dts) # =============================== Keyboard commands ==================================== - def switch_activation_kb(self, value, timestamp): + def switch_activation_kb(self, value): pass - def next_pilot_kb(self, value, timestamp): + def next_pilot_kb(self, value): pass - def increase_health_level_kb(self, value, timestamp): + def increase_health_level_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_health_level(self.machine.health_level + 0.01) - def decrease_health_level_kb(self, value, timestamp): + def decrease_health_level_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_health_level(self.machine.health_level - 0.01) - def increase_thrust_level_kb(self, value, timestamp): + def increase_thrust_level_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_thrust_level(self.machine.thrust_level_dest + 0.01) - def decrease_thrust_level_kb(self, value, timestamp): + def decrease_thrust_level_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_thrust_level(self.machine.thrust_level_dest - 0.01) def set_thrust_level_kb(self, value): pass - def increase_brake_level_kb(self, value, timestamp): + def increase_brake_level_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_brake_level(self.machine.brake_level_dest + 0.01) - def decrease_brake_level_kb(self, value, timestamp): + def decrease_brake_level_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_brake_level(self.machine.brake_level_dest - 0.01) - def increase_flaps_level_kb(self, value, timestamp): + def increase_flaps_level_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_flaps_level(self.machine.flaps_level + 0.01) - def decrease_flaps_level_kb(self, value, timestamp): + def decrease_flaps_level_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_flaps_level(self.machine.flaps_level - 0.01) - def roll_left_kb(self, value, timestamp): + def roll_left_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_roll_level(1) elif ControlDevice.keyboard.Released(value): self.machine.set_roll_level(0) - def roll_right_kb(self, value, timestamp): + def roll_right_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_roll_level(-1) elif ControlDevice.keyboard.Released(value): self.machine.set_roll_level(0) - def set_roll_kb(self, value, timestamp): + def set_roll_kb(self, value): pass - def pitch_up_kb(self, value, timestamp): + def pitch_up_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_pitch_level(1) elif ControlDevice.keyboard.Released(value): self.machine.set_pitch_level(0) - def pitch_down_kb(self, value, timestamp): + def pitch_down_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_pitch_level(-1) elif ControlDevice.keyboard.Released(value): self.machine.set_pitch_level(0) - def set_pitch_kb(self, value, timestamp): + def set_pitch_kb(self, value): pass - def yaw_left_kb(self, value, timestamp): + def yaw_left_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_yaw_level(-1) elif ControlDevice.keyboard.Released(value): self.machine.set_yaw_level(0) - def yaw_right_kb(self, value, timestamp): + def yaw_right_kb(self, value): if ControlDevice.keyboard.Down(value): self.machine.set_yaw_level(1) elif ControlDevice.keyboard.Released(value): self.machine.set_yaw_level(0) - def set_yaw_kb(self, value, timestamp): + def set_yaw_kb(self, value): pass - def switch_post_combustion_kb(self, value, timestamp): + def switch_post_combustion_kb(self, value): if ControlDevice.keyboard.Pressed(value): if self.machine.post_combustion: self.machine.deactivate_post_combustion() else: self.machine.activate_post_combustion() - def next_target_kb(self, value, timestamp): + def next_target_kb(self, value): if ControlDevice.keyboard.Pressed(value): td = self.machine.get_device("TargettingDevice") if td is not None: td.next_target() - def switch_gear_kb(self, value, timestamp): + def switch_gear_kb(self, value): if ControlDevice.keyboard.Pressed(value): if "Gear" in self.machine.devices and self.machine.devices["Gear"] is not None: gear = self.machine.devices["Gear"] @@ -1105,7 +1111,7 @@ def switch_gear_kb(self, value, timestamp): else: gear.activate() - def activate_autopilot_kb(self, value, timestamp): + def activate_autopilot_kb(self, value): if ControlDevice.keyboard.Pressed(value): autopilot_device = self.machine.get_device("AutopilotControlDevice") if autopilot_device is not None: @@ -1113,131 +1119,131 @@ def activate_autopilot_kb(self, value, timestamp): autopilot_device.activate() - def activate_ia_kb(self, value, timestamp): + def activate_ia_kb(self, value): if ControlDevice.keyboard.Pressed(value): ia_device = self.machine.get_device("IAControlDevice") if ia_device is not None: self.deactivate() ia_device.activate() - def switch_easy_steering_kb(self, value, timestamp): + def switch_easy_steering_kb(self, value): if ControlDevice.keyboard.Pressed(value): self.machine.flag_easy_steering = not self.machine.flag_easy_steering - def fire_machine_gun_kb(self, value, timestamp): + def fire_machine_gun_kb(self, value): if ControlDevice.keyboard.Down(value): n = self.machine.get_machinegun_count() for i in range(n): mgd = self.machine.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and not mgd.is_gun_activated(): - mgd.fire_machine_gun(timestamp) + if mgd is not None and not mgd.is_activated(): + mgd.activate() elif ControlDevice.keyboard.Released(value): n = self.machine.get_machinegun_count() for i in range(n): mgd = self.machine.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun(timestamp) + if mgd is not None and mgd.is_activated(): + mgd.deactivate() - def fire_missile_kb(self, value, timestamp): + def fire_missile_kb(self, value): if ControlDevice.keyboard.Pressed(value): md = self.machine.get_device("MissilesDevice") if md is not None: md.fire_missile() - def rearm_kb(self, value, timestamp): + def rearm_kb(self, value): if ControlDevice.keyboard.Pressed(value): self.machine.rearm() # =============================== Logitech Attack 3 ==================================== - def switch_activation_la3(self, value, timestamp): + def switch_activation_la3(self, value): pass - def next_pilot_la3(self, value, timestamp): + def next_pilot_la3(self, value): pass - def increase_health_level_la3(self, value, timestamp): + def increase_health_level_la3(self, value): pass - def decrease_health_level_la3(self, value, timestamp): + def decrease_health_level_la3(self, value): pass - def increase_thrust_level_la3(self, value, timestamp): + def increase_thrust_level_la3(self, value): pass - def decrease_thrust_level_la3(self, value, timestamp): + def decrease_thrust_level_la3(self, value): pass - def set_thrust_level_la3(self, value, timestamp): + def set_thrust_level_la3(self, value): # epsilon = 0.1 # threshold to cancel the device's jitter v = -ControlDevice.generic_controller.Axes(value) self.machine.set_thrust_level((v + 1.0) / 2.0) - def increase_brake_level_la3(self, value, timestamp): + def increase_brake_level_la3(self, value): if ControlDevice.generic_controller.Down(value): self.machine.set_brake_level(self.machine.brake_level_dest + 0.01) - def decrease_brake_level_la3(self, value, timestamp): + def decrease_brake_level_la3(self, value): if ControlDevice.generic_controller.Down(value): self.machine.set_brake_level(self.machine.brake_level_dest - 0.01) - def increase_flaps_level_la3(self, value, timestamp): + def increase_flaps_level_la3(self, value): if ControlDevice.generic_controller.Down(value): self.machine.set_flaps_level(self.machine.flaps_level_dest + 0.01) - def decrease_flaps_level_la3(self, value, timestamp): + def decrease_flaps_level_la3(self, value): if ControlDevice.generic_controller.Down(value): self.machine.set_flaps_level(self.machine.flaps_level_dest - 0.01) - def roll_left_la3(self, value, timestamp): + def roll_left_la3(self, value): pass - def roll_right_la3(self, value, timestamp): + def roll_right_la3(self, value): pass - def set_roll_la3(self, value, timestamp): + def set_roll_la3(self, value): v = -ControlDevice.generic_controller.Axes(value) self.machine.set_roll_level(v) - def set_pitch_la3(self, value, timestamp): + def set_pitch_la3(self, value): v = -ControlDevice.generic_controller.Axes(value) self.machine.set_pitch_level(v) - def pitch_up_la3(self, value, timestamp): + def pitch_up_la3(self, value): pass - def pitch_down_la3(self, value, timestamp): + def pitch_down_la3(self, value): pass - def yaw_left_la3(self, value, timestamp): + def yaw_left_la3(self, value): if ControlDevice.generic_controller.Down(value): self.machine.set_yaw_level(-1) elif ControlDevice.generic_controller.Released(value): self.machine.set_yaw_level(0) - def yaw_right_la3(self, value, timestamp): + def yaw_right_la3(self, value): if ControlDevice.generic_controller.Down(value): self.machine.set_yaw_level(1) elif ControlDevice.generic_controller.Released(value): self.machine.set_yaw_level(0) - def set_yaw_la3(self, value, timestamp): + def set_yaw_la3(self, value): pass - def switch_post_combustion_la3(self, value, timestamp): + def switch_post_combustion_la3(self, value): if ControlDevice.generic_controller.Pressed(value): if self.machine.post_combustion: self.machine.deactivate_post_combustion() else: self.machine.activate_post_combustion() - def next_target_la3(self, value, timestamp): + def next_target_la3(self, value): if ControlDevice.generic_controller.Pressed(value): td = self.machine.get_device("TargettingDevice") if td is not None: td.next_target() - def switch_gear_la3(self, value, timestamp): + def switch_gear_la3(self, value): if ControlDevice.generic_controller.Pressed(value): if "Gear" in self.machine.devices and self.machine.devices["Gear"] is not None: gear = self.machine.devices["Gear"] @@ -1248,32 +1254,32 @@ def switch_gear_la3(self, value, timestamp): gear.activate() - def activate_autopilot_la3(self, value, timestamp): + def activate_autopilot_la3(self, value): pass - def activate_ia_la3(self, value, timestamp): + def activate_ia_la3(self, value): pass - def switch_easy_steering_la3(self, value, timestamp): + def switch_easy_steering_la3(self, value): pass - def fire_machine_gun_la3(self, value, timestamp): + def fire_machine_gun_la3(self, value): if ControlDevice.generic_controller.Down(value): n = self.machine.get_machinegun_count() for i in range(n): mgd = self.machine.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and not mgd.is_gun_activated(): - mgd.fire_machine_gun(timestamp) + if mgd is not None and not mgd.is_activated(): + mgd.activate() elif ControlDevice.generic_controller.Released(value): n = self.machine.get_machinegun_count() for i in range(n): mgd = self.machine.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun(timestamp) + if mgd is not None and mgd.is_activated(): + mgd.deactivate() - def fire_missile_la3(self, value, timestamp): + def fire_missile_la3(self, value): if ControlDevice.generic_controller.Pressed(value): md = self.machine.get_device("MissilesDevice") if md is not None: @@ -1283,93 +1289,93 @@ def fire_missile_la3(self, value, timestamp): # =============================== Gamepad commands ==================================== - def switch_activation_gp(self, value, timestamp): + def switch_activation_gp(self, value): pass - def next_pilot_gp(self, value, timestamp): + def next_pilot_gp(self, value): pass - def increase_health_level_gp(self, value, timestamp): + def increase_health_level_gp(self, value): pass - def decrease_health_level_gp(self, value, timestamp): + def decrease_health_level_gp(self, value): pass - def increase_thrust_level_gp(self, value, timestamp): + def increase_thrust_level_gp(self, value): pass - def decrease_thrust_level_gp(self, value, timestamp): + def decrease_thrust_level_gp(self, value): pass - def set_thrust_level_gp(self, value, timestamp): + def set_thrust_level_gp(self, value): epsilon = 0.1 v = -ControlDevice.gamepad.Axes(value) if v < - epsilon or v > epsilon: self.machine.set_thrust_level(self.machine.thrust_level_dest + v * 0.01) - def increase_brake_level_gp(self, value, timestamp): + def increase_brake_level_gp(self, value): if ControlDevice.gamepad.Down(value): self.machine.set_brake_level(self.machine.brake_level_dest + 0.01) - def decrease_brake_level_gp(self, value, timestamp): + def decrease_brake_level_gp(self, value): if ControlDevice.gamepad.Down(value): self.machine.set_brake_level(self.machine.brake_level_dest - 0.01) - def increase_flaps_level_gp(self, value, timestamp): + def increase_flaps_level_gp(self, value): if ControlDevice.gamepad.Down(value): self.machine.set_flaps_level(self.machine.flaps_level_dest + 0.01) - def decrease_flaps_level_gp(self, value, timestamp): + def decrease_flaps_level_gp(self, value): if ControlDevice.gamepad.Down(value): self.machine.set_flaps_level(self.machine.flaps_level_dest - 0.01) - def roll_left_gp(self, value, timestamp): + def roll_left_gp(self, value): pass - def roll_right_gp(self, value, timestamp): + def roll_right_gp(self, value): pass - def set_roll_gp(self, value, timestamp): + def set_roll_gp(self, value): v = -ControlDevice.gamepad.Axes(value) self.machine.set_roll_level(v) - def pitch_up_gp(self, value, timestamp): + def pitch_up_gp(self, value): pass - def pitch_down_gp(self, value, timestamp): + def pitch_down_gp(self, value): pass - def set_pitch_gp(self, value, timestamp): + def set_pitch_gp(self, value): v = -ControlDevice.gamepad.Axes(value) self.machine.set_pitch_level(v) - def yaw_left_gp(self, value, timestamp): + def yaw_left_gp(self, value): pass - def yaw_right_gp(self, value, timestamp): + def yaw_right_gp(self, value): pass - def set_yaw_gp(self, value, timestamp): + def set_yaw_gp(self, value): epsilon = 0.016 v = ControlDevice.gamepad.Axes(value) if -epsilon < v < epsilon: v = 0 self.machine.set_yaw_level(v) - def switch_post_combustion_gp(self, value, timestamp): + def switch_post_combustion_gp(self, value): if ControlDevice.gamepad.Pressed(value): if self.machine.post_combustion: self.machine.deactivate_post_combustion() else: self.machine.activate_post_combustion() - def next_target_gp(self, value, timestamp): + def next_target_gp(self, value): if ControlDevice.gamepad.Pressed(value): td = self.machine.get_device("TargettingDevice") if td is not None: td.next_target() - def switch_gear_gp(self, value, timestamp): + def switch_gear_gp(self, value): if ControlDevice.gamepad.Pressed(value): if "Gear" in self.machine.devices and self.machine.devices["Gear"] is not None: gear = self.machine.devices["Gear"] @@ -1379,34 +1385,34 @@ def switch_gear_gp(self, value, timestamp): else: gear.activate() - def activate_autopilot_gp(self, value, timestamp): + def activate_autopilot_gp(self, value): pass - def activate_ia_gp(self, value, timestamp): + def activate_ia_gp(self, value): if ControlDevice.gamepad.Pressed(value): ia_device = self.machine.get_device("IAControlDevice") if ia_device is not None: self.deactivate() ia_device.activate() - def switch_easy_steering_gp(self, value, timestamp): + def switch_easy_steering_gp(self, value): pass - def fire_machine_gun_gp(self, value, timestamp): + def fire_machine_gun_gp(self, value): if ControlDevice.gamepad.Down(value): n = self.machine.get_machinegun_count() for i in range(n): mgd = self.machine.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and not mgd.is_gun_activated(): - mgd.fire_machine_gun(timestamp) + if mgd is not None and not mgd.is_activated(): + mgd.activate() elif ControlDevice.gamepad.Released(value): n = self.machine.get_machinegun_count() for i in range(n): mgd = self.machine.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun(timestamp) + if mgd is not None and mgd.is_activated(): + mgd.deactivate() - def fire_missile_gp(self, value, timestamp): + def fire_missile_gp(self, value): if ControlDevice.gamepad.Pressed(value): md = self.machine.get_device("MissilesDevice") if md is not None: @@ -1678,7 +1684,7 @@ def update_controlled_devices(self, dts): tp = max(-1, min(1, diff / 10)) aircraft.set_pitch_level(-tp) - def update(self, dts, timestamp): + def update(self, dts): if self.activated: if self.flag_user_control and self.machine.has_focus(): if self.control_mode == ControlDevice.CM_KEYBOARD: @@ -1767,8 +1773,8 @@ def activate(self): n = aircraft.get_machinegun_count() for i in range(n): mgd = aircraft.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun() + if mgd is not None and mgd.is_activated(): + mgd.deactivate() self.IA_flag_go_to_target = False if aircraft.flag_landed: @@ -1793,8 +1799,8 @@ def deactivate(self): n = aircraft.get_machinegun_count() for i in range(n): mgd = aircraft.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun() + if mgd is not None and mgd.is_activated(): + mgd.deactivate() self.IA_flag_go_to_target = False aircraft.set_flaps_level(0) self.IA_flag_landing_target_found = False @@ -1842,19 +1848,19 @@ def calculate_landing_target_point(self, aircraft, landing_target, landing_proj) else: return landing_proj - def update_controlled_device(self, dts, timestamp): + def update_controlled_device(self, dts): aircraft = self.machine if not aircraft.wreck and not aircraft.flag_going_to_takeoff_position: if self.IA_command == AircraftIAControlDevice.IA_COM_IDLE: - self.update_IA_idle(aircraft, timestamp) + self.update_IA_idle(aircraft) elif self.IA_command == AircraftIAControlDevice.IA_COM_LIFTOFF: - self.update_IA_liftoff(aircraft, dts, timestamp) + self.update_IA_liftoff(aircraft, dts) elif self.IA_command == AircraftIAControlDevice.IA_COM_FIGHT: - self.update_IA_fight(aircraft, dts, timestamp) + self.update_IA_fight(aircraft, dts) elif self.IA_command == AircraftIAControlDevice.IA_COM_LANDING: - self.update_IA_landing(aircraft, dts, timestamp) + self.update_IA_landing(aircraft, dts) - def update_IA_liftoff(self, aircraft, dts, timestamp): + def update_IA_liftoff(self, aircraft, dts): self.IA_flag_landing_target_found = False aircraft.set_flaps_level(1) if self.flag_IA_start_liftoff: @@ -1887,15 +1893,15 @@ def update_IA_liftoff(self, aircraft, dts, timestamp): else: self.IA_command = AircraftIAControlDevice.IA_COM_FIGHT - def update_IA_idle(self, aircraft, timestamp): + def update_IA_idle(self, aircraft): autopilot = aircraft.devices["AutopilotControlDevice"] if autopilot is not None: autopilot.set_autopilot_speed(400 / 3.6) n = aircraft.get_machinegun_count() for i in range(n): mgd = aircraft.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun() + if mgd is not None and mgd.is_activated(): + mgd.deactivate() autopilot.set_autopilot_altitude(self.IA_cruising_altitude) autopilot.set_autopilot_heading(0) @@ -1918,15 +1924,15 @@ def get_nearest_landing_target(self, aircraft): distances.sort(key=lambda p: p["distance"]) return distances[0]["landing_target"] - def update_IA_landing(self, aircraft, dts, timestamp): + def update_IA_landing(self, aircraft, dts): if "AutopilotControlDevice" in aircraft.devices and aircraft.devices["AutopilotControlDevice"] is not None: autopilot = aircraft.devices["AutopilotControlDevice"] if not self.IA_flag_landing_target_found: n = aircraft.get_machinegun_count() for i in range(n): mgd = aircraft.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun() + if mgd is not None and mgd.is_activated(): + mgd.deactivate() self.IA_landing_target = self.get_nearest_landing_target(aircraft) if self.IA_landing_target is not None: self.IA_flag_landing_target_found = True @@ -1999,7 +2005,7 @@ def update_IA_landing(self, aircraft, dts, timestamp): self.IA_liftoff_delay = 2 self.IA_command = AircraftIAControlDevice.IA_COM_LIFTOFF - def update_IA_fight(self, aircraft, dts, timestamp): + def update_IA_fight(self, aircraft, dts): autopilot = aircraft.devices["AutopilotControlDevice"] if autopilot is not None: if "Gear" in aircraft.devices and aircraft.devices["Gear"] is not None: @@ -2068,14 +2074,14 @@ def update_IA_fight(self, aircraft, dts, timestamp): n = aircraft.get_machinegun_count() for i in range(n): mgd = aircraft.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and not mgd.is_gun_activated(): - mgd.fire_machine_gun(timestamp) + if mgd is not None and not mgd.is_activated(): + mgd.activate() else: n = aircraft.get_machinegun_count() for i in range(n): mgd = aircraft.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun(timestamp) + if mgd is not None and mgd.is_activated(): + mgd.deactivate() flag_missiles_ok = False if md is not None: @@ -2095,8 +2101,8 @@ def update_IA_fight(self, aircraft, dts, timestamp): n = aircraft.get_machinegun_count() for i in range(n): mgd = aircraft.get_device("MachineGunDevice_%02d" % i) - if mgd is not None and mgd.is_gun_activated(): - mgd.stop_machine_gun() + if mgd is not None and mgd.is_activated(): + mgd.deactivate() self.IA_flag_landing_target_found = False self.IA_command = AircraftIAControlDevice.IA_COM_LANDING # self.set_autopilot_altitude(self.IA_cruising_altitude) @@ -2148,29 +2154,29 @@ def activate_user_control_gp(self, value): # ==================================================================================== - def update_cm_keyboard(self, dts, timestamp): + def update_cm_keyboard(self, dts): im = self.inputs_mapping["AircraftIAInputsMapping"]["Keyboard"] for cmd, input_code in im.items(): if cmd in self.commands and input_code != "": self.commands[cmd](input_code) - def update_cm_gamepad(self, dts, timestamp): + def update_cm_gamepad(self, dts): im = self.inputs_mapping["AircraftIAInputsMapping"]["GamePad"] for cmd, input_code in im.items(): if cmd in self.commands and input_code != "": self.commands[cmd](input_code) - def update_cm_mouse(self, dts, timestamp): + def update_cm_mouse(self, dts): im = self.inputs_mapping["AircraftIAInputsMapping"]["Mouse"] - def update(self, dts, timestamp): + def update(self, dts): if self.activated: if self.flag_user_control and self.machine.has_focus(): if self.control_mode == ControlDevice.CM_KEYBOARD: - self.update_cm_keyboard(dts, timestamp) + self.update_cm_keyboard(dts) elif self.control_mode == ControlDevice.CM_GAMEPAD: - self.update_cm_gamepad(dts, timestamp) + self.update_cm_gamepad(dts) elif self.control_mode == ControlDevice.CM_MOUSE: - self.update_cm_mouse(dts, timestamp) + self.update_cm_mouse(dts) - self.update_controlled_device(dts, timestamp) + self.update_controlled_device(dts) diff --git a/source/Machines.py b/source/Machines.py index 1793f40..1ac823d 100644 --- a/source/Machines.py +++ b/source/Machines.py @@ -19,6 +19,8 @@ class Collisions_Object(MachineDevice): _instances = [] + framecount = 0 #Updated with Main.framecount + timer = 0 @classmethod def reset_collisions_objects(cls): @@ -42,6 +44,9 @@ def __init__(self, name): Collisions_Object._instances.append(self) self.instance_id = len(Collisions_Object._instances) - 1 + self.event_listeners = [] #list of functions used to listen events - Current lestenable events : "hit" + # listener prototype: listener(str event_name, dict parameters) + def get_collision_nodes(self): return self.collision_nodes @@ -51,8 +56,12 @@ def test_collision(self, nd: hg.Node): if nd == ndt: return True return False + def add_listener(self, listener_call_back): + self.event_listeners.append(listener_call_back) + def hit(self, value, position): - pass + for listener in self.event_listeners: + listener("hit",{"value":value, "position": position, "timestamp": Collisions_Object.timer}) # ??? position or hg.Vec3(position) ??? # ===================================================================================================== @@ -344,8 +353,6 @@ def __init__(self, name, model_name, scene: hg.Scene, scene_physics, pipeline_re self.flag_focus = False - self.event_listeners = [] #list of functions used to listen events - Current lestenable events : "hit" - # listener prototype: listener(str event_name, list parameters) self.hits = [] self.playfield_distance = 0 @@ -434,9 +441,9 @@ def remove_device(self, device_name): return self.devices.pop(device_name) return None - def update_devices(self, dts, timestamp): + def update_devices(self, dts): for name, device in self.devices.items(): - device.update(dts, timestamp) + device.update(dts) def get_device(self, device_name): if device_name in self.devices: @@ -493,11 +500,11 @@ def calculate_view_matrix(self, camera): self.mat_view_prec = self.mat_view self.mat_view = cam_mat_view * self.parent_node.GetTransform().GetWorld() - def hit(self, value, position, timestamp = 0): + + def hit(self, value, position): + Collisions_Object.hit(self, value, position) if not self.wreck: self.set_health_level(self.health_level - value) - for listener in self.event_listeners: - listener("hit",[value, position, timestamp]) # ??? position or hg.Vec3(position) ??? def destroy_nodes(self): AnimatedModel.destroy_nodes(self) @@ -800,7 +807,7 @@ def get_physics_parameters(self): "flag_easy_steering": False } - def update_kinetics(self, dts, timestamp): + def update_kinetics(self, dts): if self.activated: if self.custom_matrix is not None: matrix = self.custom_matrix @@ -832,7 +839,7 @@ def update_kinetics(self, dts, timestamp): self.rec_linear_speed() self.update_linear_acceleration() - self.update_devices(dts, timestamp) + self.update_devices(dts) self.update_mobile_parts(dts) self.update_feedbacks(dts) @@ -1190,11 +1197,11 @@ def get_physics_parameters(self): "flag_easy_steering": False } - def update_kinetics(self, dts, timestamp): + def update_kinetics(self, dts): if self.activated: - self.update_devices(dts, timestamp) + self.update_devices(dts) self.life_cptr += dts @@ -1948,17 +1955,17 @@ def get_physics_parameters(self): "flag_easy_steering": self.flag_easy_steering } - def update_kinetics(self, dts, timestamp): + def update_kinetics(self, dts): # Custom physics (but keep inner collisions system) if self.flag_custom_physics_mode: - Destroyable_Machine.update_kinetics(self, dts, timestamp) + Destroyable_Machine.update_kinetics(self, dts) # Inner physics else: if self.activated: - self.update_devices(dts, timestamp) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + self.update_devices(dts) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # # ========================= Flight physics Repositionning after landing : @@ -2386,9 +2393,9 @@ def destroy(self): self.flag_destroyed = True def hit(self, value, position): - pass + Collisions_Object.hit(self, value, position) - def update_kinetics(self, dts, timestamp): + def update_kinetics(self, dts): rot = self.radar.GetTransform().GetRot() rot.y += radians(45 * dts) self.radar.GetTransform().SetRot(rot) @@ -2421,5 +2428,8 @@ def get_thrust_level(self): def get_brake_level(self): return self.brake_level - def update_kinetics(self, dts, timestamp): - Destroyable_Machine.update_kinetics(self, dts, timestamp) \ No newline at end of file + def hit(self, value, position): + Collisions_Object.hit(self, value, position) + + def update_kinetics(self, dts): + Destroyable_Machine.update_kinetics(self, dts) \ No newline at end of file diff --git a/source/master.py b/source/master.py index 50de632..7944446 100644 --- a/source/master.py +++ b/source/master.py @@ -72,7 +72,7 @@ class Main: flag_exit = False win = None - timestamp = 0 # Frame count. + framecount = 0 # Frame count. timer = 0 # clock in s (incremented at each frame) timestep = 1 / 60 # Frame dt @@ -1137,7 +1137,7 @@ def update_kinetics(cls, dts): # dm.update_collision_nodes_matrices() for dm in Destroyable_Machine.update_list: - dm.update_kinetics(dts, cls.timestamp) + dm.update_kinetics(dts) cls.display_machine_vectors(dm) @@ -1467,13 +1467,19 @@ def update_inputs(cls): @classmethod def reset_timestamp(cls): - cls.timestamp = 0 + cls.framecount = 0 cls.timer = 0 + MachineDevice.framecount = 0 + Collisions_Object.framecount = 0 @classmethod def update_timestamp(cls, dts): - cls.timestamp += 1 + cls.framecount += 1 cls.timer += dts + MachineDevice.framecount = cls.framecount + MachineDevice.timer = cls.timer + Collisions_Object.framecount = cls.framecount + Collisions_Object.timer = cls.timer @classmethod def client_update(cls): diff --git a/source/network_server.py b/source/network_server.py index a44f6a4..d62bc99 100644 --- a/source/network_server.py +++ b/source/network_server.py @@ -257,7 +257,7 @@ def set_machine_custom_physics_mode(args): def get_machine_custom_physics_mode(args): machine = main.destroyables_items[args["machine_id"]] state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "custom_physics_mode": machine.flag_custom_physics_mode } @@ -348,7 +348,7 @@ def get_mobile_parts_list(args): def get_machine_gun_state(args): machine = main.destroyables_items[args["machine_id"]] state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "MachineGunDevices": {} } @@ -376,7 +376,7 @@ def get_missiles_device_slots_state(args): md = machine.get_device("MissilesDevice") if md is not None: state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "missiles_slots": md.get_missiles_state() } @@ -431,7 +431,7 @@ def deactivate_machine_gun(args): def get_health(args): machine = main.destroyables_items[args["machine_id"]] state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "health_level": machine.get_health_level() } @@ -453,7 +453,7 @@ def get_target_idx(args): td = machine.get_device("TargettingDevice") if td is not None: state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "target_idx": td.get_target_id() } @@ -462,7 +462,7 @@ def get_target_idx(args): print(str(state)) else: state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "target_idx": 0 } @@ -523,7 +523,7 @@ def is_autopilot_activated(args): apctrl = machine.get_device("AutopilotControlDevice") if apctrl is not None: state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "autopilot": apctrl.is_activated() } @@ -532,7 +532,7 @@ def is_autopilot_activated(args): print(str(state)) else: state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "autopilot": False } @@ -544,7 +544,7 @@ def is_ia_activated(args): iactrl = machine.get_device("IAControlDevice") if iactrl is not None: state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "ia": iactrl.is_activated() } @@ -553,7 +553,7 @@ def is_ia_activated(args): print(str(state)) else: state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "ia": False } @@ -565,7 +565,7 @@ def is_user_control_activated(args): uctrl = machine.get_device("UserControlDevice") if uctrl is not None: state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "user": uctrl.is_activated() } @@ -574,7 +574,7 @@ def is_user_control_activated(args): print(str(state)) else: state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "user": False } @@ -662,7 +662,7 @@ def get_plane_state(args): rotation = machine.get_Euler() v_move = machine.get_move_vector() state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "position": [position.x, position.y, position.z], "Euler_angles": [rotation.x, rotation.y, rotation.z], @@ -743,7 +743,7 @@ def set_plane_thrust(args): def get_plane_thrust(args): machine = main.destroyables_items[args["plane_id"]] state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "thrust_level": machine.get_thrust_level() } @@ -899,7 +899,7 @@ def get_missile_launcher_state(args): target_id = "- ! No TargettingDevice ! -" target_locked = False state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "position": [position.x, position.y, position.z], "Euler_angles": [rotation.x, rotation.y, rotation.z], @@ -940,7 +940,7 @@ def get_missile_state(args): v_move = machine.get_move_vector() h_spd, v_spd = machine.get_world_speed() state = { - "timestamp": main.timestamp, + "timestamp": main.framecount, "timestep": main.timestep, "type": Destroyable_Machine.types_labels[machine.type], "position": [position.x, position.y, position.z], diff --git a/source/states.py b/source/states.py index 5a7519d..29f4fd6 100644 --- a/source/states.py +++ b/source/states.py @@ -124,7 +124,7 @@ def menu_state(dts): tools.set_stereo_volume(Main.main_music_source, Main.master_sfx_volume) for carrier in Main.aircraft_carrier_allies: - carrier.update_kinetics(dts, Main.timestamp) + carrier.update_kinetics(dts) if Main.display_dark_design: Main.spr_design_menu.set_position(0.5 * Main.resolution.x, 0.5 * Main.resolution.y) diff --git a/source/vcr.py b/source/vcr.py index 8bc5918..cb7b663 100644 --- a/source/vcr.py +++ b/source/vcr.py @@ -40,6 +40,7 @@ selected_item_idx = 0 selected_record = 0 records = None +records_events = None last_value_recorded = {} task_record_in_database = None @@ -64,12 +65,10 @@ def setup_items(main): #container: if not None, contain the value to record def AddItem(item, params=[], name=None, container=None): global items, items_list, items_names - if isinstance(item, hg.Node) and name is None: - name = f"{item.GetName()} {item.GetTransform().GetPos().x:.2}{item.GetTransform().GetPos().y:.2}{item.GetTransform().GetPos().z:.2}" - elif isinstance(item, str) and name is None: - name = item - elif isinstance(item, Machines.Destroyable_Machine) and name is None: - name = item.name + if isinstance(item, Machines.Destroyable_Machine): + item.add_listener(event_call_back) + if name is None: + name = item.name item.name = dc.conform_string(name) # !!! This could change the machine name in the scene !!! Need valid machine name to keep right links references (targets, missiles parents....) name = dc.conform_string(name) @@ -80,12 +79,18 @@ def AddItem(item, params=[], name=None, container=None): items_names.append(name) return items[name] +def event_call_back(event_name:str, params:list): + global records_events + if recording: + records_events.append([params["timestamp"], event_name + ":" + str(params["value"]) + ":" + dc.serialize_vec3(params["position"])]) + def clear_items(): - global items, items_list, selected_item_idx, items_names + global items, items_list, selected_item_idx, items_names, records_events selected_item_idx = 0 items = {} items_list = [] items_names = [] + records_events = [] def is_init(): return flag_init @@ -106,6 +111,7 @@ def init(): c.execute('''CREATE TABLE IF NOT EXISTS users(id_user INTEGER PRIMARY KEY, name TEXT, info TEXT)''') c.execute('''CREATE TABLE IF NOT EXISTS records(id_rec INTEGER PRIMARY KEY, name TEXT, min_clock FLOAT, max_clock FLOAT, fps INT,scene_items TEXT, id_user INTEGER REFERENCES users, CONSTRAINT fk_users FOREIGN KEY (id_user) REFERENCES users(id_user) ON DELETE CASCADE)''') + c.execute('''CREATE TABLE IF NOT EXISTS events(id_rec INTEGER REFERENCES records, c FLOAT, v TEXT)''') # add default user if table_users_exists is None: @@ -124,14 +130,14 @@ def create_scene_items_list(): return scene_items def start_record(name_record): - global recording, records, current_id_rec, last_value_recorded + global recording, records, records_events, current_id_rec, last_value_recorded # check if we are already start if recording: return - recording = True records = None + records_events = [] last_value_recorded = {} scene_items = ":".join(create_scene_items_list()) @@ -150,6 +156,8 @@ def start_record(name_record): current_id_rec = r["id_rec"] conn.commit() print(f"create record: {name_record}, {current_id_rec}") + + recording = True # Coroutine def record_in_database(): @@ -160,6 +168,7 @@ def record_in_database(): # create db for items print("Record items table if necessary") i = 0 + n_steps = 2*len(records) + len(records_events) for t, record in records.items(): for name, value in record.items(): if isinstance(value, str): @@ -171,7 +180,7 @@ def record_in_database(): elif isinstance(value, float): c.execute(f"CREATE TABLE IF NOT EXISTS {name}(id_rec INTEGER, c FLOAT, v FLOAT, PRIMARY KEY (id_rec, c), CONSTRAINT fk_record FOREIGN KEY (id_rec) REFERENCES records(id_rec) ON DELETE CASCADE) WITHOUT ROWID;") #c.execute(f"DELETE FROM {name} WHERE id_rec={current_id_rec};") - progress_cptr = i / (2*len(records)) + progress_cptr = i / n_steps i+=1 if (i % fps_record) == 0: yield @@ -179,10 +188,18 @@ def record_in_database(): for t, record in records.items(): for name, value in record.items(): c.execute(f"INSERT INTO {name}(id_rec, c, v) VALUES ({current_id_rec}, {t}, \"{value}\");") - progress_cptr = i / (2*len(records)) + progress_cptr = i / n_steps i+=1 if (i % fps_record) == 0: yield + # record events: + for evt in records_events: + c.execute(f"INSERT INTO events(id_rec, c, v) VALUES ({current_id_rec}, {evt[0]}, \"{evt[1]}\");") + progress_cptr = i / n_steps + i+=1 + if (i % 10) == 0: + yield + c.execute(f"UPDATE records SET max_clock={timer}, min_clock={recorded_min_time} WHERE id_rec={current_id_rec};") #c.execute(f"UPDATE records SET fps={fps_record} WHERE id_rec={current_id_rec};") From 11f711ca4692ec972cb62ce768c3bed73d150bf9 Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Fri, 9 Dec 2022 13:09:04 +0100 Subject: [PATCH 13/17] hit events record and replay OK - Display WIP --- source/Machines.py | 2 +- source/vcr.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/source/Machines.py b/source/Machines.py index 1ac823d..330941d 100644 --- a/source/Machines.py +++ b/source/Machines.py @@ -1714,7 +1714,7 @@ def start_explosion(self): for i in range(n): mgd = self.get_device("MachineGunDevice_%02d" % i) if mgd is not None: - mgd.stop_machine_gun() + mgd.deactivate() self.wreck = True diff --git a/source/vcr.py b/source/vcr.py index cb7b663..1f7ca72 100644 --- a/source/vcr.py +++ b/source/vcr.py @@ -111,7 +111,7 @@ def init(): c.execute('''CREATE TABLE IF NOT EXISTS users(id_user INTEGER PRIMARY KEY, name TEXT, info TEXT)''') c.execute('''CREATE TABLE IF NOT EXISTS records(id_rec INTEGER PRIMARY KEY, name TEXT, min_clock FLOAT, max_clock FLOAT, fps INT,scene_items TEXT, id_user INTEGER REFERENCES users, CONSTRAINT fk_users FOREIGN KEY (id_user) REFERENCES users(id_user) ON DELETE CASCADE)''') - c.execute('''CREATE TABLE IF NOT EXISTS events(id_rec INTEGER REFERENCES records, c FLOAT, v TEXT)''') + c.execute('''CREATE TABLE IF NOT EXISTS events(id_rec INTEGER, c FLOAT, v TEXT)''') # add default user if table_users_exists is None: @@ -372,6 +372,13 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day item = v else: eval(p["load"]) + # Events: + c.execute(f"SELECT * FROM events where id_rec={current_id_play} and c <= {timer + 2} and c >= {timer - 2} ORDER BY c DESC;") + r = c.fetchall() + if r is not None and len(r)>0: + print("----------EVENTS !") + for row in r: + print("timestamp:" + str(row["c"]) + " - value:" + row["v"]) if not pausing: timer += hg.time_to_sec_f(dt) From 8621d09743e94544dd3291acbe316698bcbb9fe2 Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Mon, 12 Dec 2022 12:19:20 +0100 Subject: [PATCH 14/17] replay hit events OK --- source/master.py | 4 +++ source/overlays.py | 89 +++++++++++++++++++++++++++++++++------------- source/vcr.py | 29 ++++++++++++--- 3 files changed, 92 insertions(+), 30 deletions(-) diff --git a/source/master.py b/source/master.py index 7944446..8370a02 100644 --- a/source/master.py +++ b/source/master.py @@ -1147,6 +1147,7 @@ def clear_display_lists(cls): #cls.texts_display_list = [] Overlays.texts2D_display_list = [] Overlays.texts3D_display_list = [] + Overlays.primitives3D_display_list = [] Overlays.lines = [] @classmethod @@ -1223,6 +1224,7 @@ def render_frame_vr(cls): hg.SetViewClear(vid, hg.CF_Depth, 0, 1.0, 0) hg.SetViewTransform(vid, vs_left.view, vs_left.proj) eye_left = cls.vr_state.head * cls.vr_state.left.offset + Overlays.display_primitives3D(vid, eye_left) Overlays.display_texts3D(vid, eye_left) Overlays.draw_lines(vid) vid += 1 @@ -1233,6 +1235,7 @@ def render_frame_vr(cls): hg.SetViewClear(vid, hg.CF_Depth, 0, 1.0, 0) hg.SetViewTransform(vid, cls.vr_viewstate.vs_right.view, cls.vr_viewstate.vs_right.proj) eye_right = cls.vr_state.head * cls.vr_state.right.offset + Overlays.display_primitives3D(vid, eye_right) Overlays.display_texts3D(vid, eye_right) Overlays.draw_lines(vid) vid += 1 @@ -1366,6 +1369,7 @@ def render_frame(cls): #Overlays.add_text3D("HELLO WORLD", hg.Vec3(0, 50, 200), 1, hg.Color.Red) + Overlays.display_primitives3D(vid, cls.scene.GetCurrentCamera().GetTransform().GetWorld()) Overlays.display_texts3D(vid, cls.scene.GetCurrentCamera().GetTransform().GetWorld()) Overlays.draw_lines(vid) if cls.flag_display_physics_debug: diff --git a/source/overlays.py b/source/overlays.py index 89e08e4..689faae 100644 --- a/source/overlays.py +++ b/source/overlays.py @@ -1,38 +1,47 @@ # Copyright (C) 2018-2021 Eric Kernin, NWNC HARFANG. import harfang as hg -from math import atan +from math import atan, cos, sin, pi class Overlays: + + uniforms_values_list = hg.UniformSetValueList() + uniforms_textures_list = hg.UniformSetTextureList() + flat_render_state = None + # ================= Lines 3D vtx_decl_lines = None - lines_program = None + shader_flat = None lines = [] # ================= Texts 3D font_program = None debug_font = None text_matrx = None - text_uniform_set_values = hg.UniformSetValueList() - text_uniform_set_texture_list = hg.UniformSetTextureList() - text_render_state = None texts3D_display_list = [] texts2D_display_list = [] + # ================= Primitives 3D + vtx_layout_flat = None + vtx_flat = None + uniforms_values_list = hg.UniformSetValueList() + uniforms_textures_list = hg.UniformSetTextureList() + circle_num_sections = 32 + primitives3D_display_list = [] + @classmethod def init(cls): - cls.vtx_decl_lines = hg.VertexLayout() - cls.vtx_decl_lines.Begin() - cls.vtx_decl_lines.Add(hg.A_Position, 3, hg.AT_Float) - cls.vtx_decl_lines.Add(hg.A_Color0, 3, hg.AT_Float) - cls.vtx_decl_lines.End() - cls.lines_program = hg.LoadProgramFromAssets("shaders/pos_rgb") + cls.vtx_decl_lines = hg. VertexLayoutPosFloatColorFloat() + cls.shader_flat = hg.LoadProgramFromAssets("shaders/pos_rgb") cls.font_program = hg.LoadProgramFromAssets("core/shader/font.vsb", "core/shader/font.fsb") cls.debug_font = hg.LoadFontFromAssets("font/default.ttf", 64) cls.text_matrx = hg.TransformationMat4(hg.Vec3(0, 0, 0), hg.Vec3(hg.Deg(0), hg.Deg(0), hg.Deg(0)), hg.Vec3(1, -1, 1)) - cls.text_uniform_set_values.push_back(hg.MakeUniformSetValue("u_color", hg.Vec4(1, 1, 0, 1))) - cls.text_render_state = hg.ComputeRenderState(hg.BM_Alpha, hg.DT_Disabled, hg.FC_Disabled) + cls.uniforms_values_list.push_back(hg.MakeUniformSetValue("u_color", hg.Vec4(1, 1, 0, 1))) + cls.flat_render_state = hg.ComputeRenderState(hg.BM_Alpha, hg.DT_Disabled, hg.FC_Disabled) + + cls.vtx_layout_flat = hg. VertexLayoutPosFloatColorFloat() + cls.vtx_flat = hg.Vertices(cls.vtx_layout_flat, cls.circle_num_sections + 1) @classmethod def display_named_vector(cls, position, direction, label, label_offset2D, color, label_size=0.012): @@ -71,11 +80,11 @@ def draw_lines(cls, vid): for i, line in enumerate(cls.lines): vtx.Begin(i * 2).SetPos(line[0]).SetColor0(line[2]).End() vtx.Begin(i * 2 + 1).SetPos(line[1]).SetColor0(line[3]).End() - hg.DrawLines(vid, vtx, cls.lines_program) + hg.DrawLines(vid, vtx, cls.shader_flat) @classmethod def display_physics_debug(cls, vid, physics): - physics.RenderCollision(vid, cls.vtx_decl_lines, cls.lines_program, hg.ComputeRenderState(hg.BM_Opaque, hg.DT_Disabled, hg.FC_Disabled), 1) + physics.RenderCollision(vid, cls.vtx_decl_lines, cls.shader_flat, hg.ComputeRenderState(hg.BM_Opaque, hg.DT_Disabled, hg.FC_Disabled), 1) @classmethod def get_2d(cls, camera, point3d: hg.Vec3, resolution: hg.Vec2): @@ -105,6 +114,37 @@ def get_2d_vr(cls, vr_hud_pos: hg.Vec3, point3d: hg.Vec3, resolution: hg.Vec2, h def add_text3D(cls, text, pos, size, color, h_align=hg.DTHA_Left): cls.texts3D_display_list.append({"text": text, "pos": pos, "size": size, "color": color, "h_align": h_align, "font": cls.debug_font}) + + @classmethod + def add_circle3D(cls, position:hg.Vec3, r:float, color:hg.Color): + cls.primitives3D_display_list.append({"type": "circle", "pos": position, "r": r, "color":color}) + + @classmethod + def display_circle3D(cls, vid, camera_rotation_matrix, pos, r, color, angle_start=0, angle=2*pi): + cls.vtx_flat.Clear() + cls.uniforms_values_list.clear() + cls.uniforms_textures_list.clear() + matrix = hg.TransformationMat4(pos, camera_rotation_matrix) + cls.vtx_flat.Begin(0).SetPos(pos).SetColor0(color).SetTexCoord0(hg.Vec2(0, 0)).End() + + idx = [] + step = angle / cls.circle_num_sections + + for i in range(cls.circle_num_sections + 1): + alpha = i * step + angle_start + cls.vtx_flat.Begin(i + 1).SetPos(matrix * hg.Vec3(cos(alpha) * r, sin(alpha) * r, 0)).SetColor0(color).End() + if i > 0: + idx += [0, i + 1, i] + + hg.DrawTriangles(vid, idx, cls.vtx_flat, cls.shader_flat, cls.uniforms_values_list, cls.uniforms_textures_list, cls.flat_render_state) + + @classmethod + def display_primitives3D(cls, vid, camera_matrix): + rotmat = hg.GetRotationMatrix(camera_matrix) + for primitive in cls.primitives3D_display_list: + if primitive["type"] == "circle": + cls.display_circle3D(vid, rotmat, primitive["pos"], primitive["r"], primitive["color"]) + @classmethod def display_texts3D(cls, vid, camera_matrix): for txt in cls.texts3D_display_list: @@ -112,7 +152,6 @@ def display_texts3D(cls, vid, camera_matrix): @classmethod def display_text3D(cls, vid, camera_matrix, text, pos, size, font, color, h_align=hg.DTHA_Center): - """ cam_pos = hg.GetT(cam_mat) az = hg.Normalize(pos-cam_pos) @@ -121,13 +160,13 @@ def display_text3D(cls, vid, camera_matrix, text, pos, size, font, color, h_alig mat = hg.Mat3(ax, ay, az) """ mat = hg.GetRotationMatrix(camera_matrix) - cls.text_uniform_set_values.clear() - cls.text_uniform_set_values.push_back(hg.MakeUniformSetValue("u_color", hg.Vec4(color.r, color.g, color.b, color.a))) # Color + cls.uniforms_values_list.clear() + cls.uniforms_values_list.push_back(hg.MakeUniformSetValue("u_color", hg.Vec4(color.r, color.g, color.b, color.a))) # Color hg.DrawText(vid, font, text, cls.font_program, "u_tex", 0, hg.TransformationMat4(pos, mat, hg.Vec3(1, -1, 1) * size), # * (size * resolution.y / 64)), hg.Vec3(0, 0, 0), h_align, hg.DTVA_Bottom, - cls.text_uniform_set_values, cls.text_uniform_set_texture_list, cls.text_render_state) + cls.uniforms_values_list, cls.uniforms_textures_list, cls.flat_render_state) @classmethod def add_text2D_from_3D_position(cls, text, pos3D, offset2D, size, color, font=None, h_align=hg.DTHA_Left): @@ -153,13 +192,13 @@ def display_texts2D(cls, vid, camera, resolution): @classmethod def display_text2D(cls, vid, resolution, text, pos, size, font, color, h_align): - cls.text_uniform_set_values.clear() - cls.text_uniform_set_values.push_back(hg.MakeUniformSetValue("u_color", hg.Vec4(color.r, color.g, color.b, color.a))) # Color + cls.uniforms_values_list.clear() + cls.uniforms_values_list.push_back(hg.MakeUniformSetValue("u_color", hg.Vec4(color.r, color.g, color.b, color.a))) # Color hg.DrawText(vid, font, text, cls.font_program, "u_tex", 0, hg.TransformationMat4(hg.Vec3(pos.x * resolution.x, pos.y * resolution.y, 1), hg.Vec3(0, 0, 0), hg.Vec3(1, -1, 1) * (size * resolution.y / 64)), hg.Vec3(0, 0, 0), h_align, hg.DTVA_Bottom, - cls.text_uniform_set_values, cls.text_uniform_set_texture_list, cls.text_render_state) + cls.uniforms_values_list, cls.uniforms_textures_list, cls.flat_render_state) @classmethod def display_texts2D_vr(cls, vid, head_matrix: hg.Mat4, z_near, z_far, resolution, vr_matrix, vr_hud_pos): @@ -180,10 +219,10 @@ def display_text2D_vr(cls, v_id, vr_matrix, vr_hud_pos, resolution, text, pos, s scale_vr = hg.Vec3(scale2D.x / resolution.x * vr_hud_pos.x, scale2D.y / resolution.y * vr_hud_pos.y, 1) matrix = vr_matrix * hg.TransformationMat4(pos_vr, hg.Vec3(0, 0, 0), scale_vr) - cls.text_uniform_set_values.clear() - cls.text_uniform_set_values.push_back(hg.MakeUniformSetValue("u_color", hg.Vec4(color.r, color.g, color.b, color.a))) # Color + cls.uniforms_values_list.clear() + cls.uniforms_values_list.push_back(hg.MakeUniformSetValue("u_color", hg.Vec4(color.r, color.g, color.b, color.a))) # Color hg.DrawText(v_id, font, text, cls.font_program, "u_tex", 0, matrix, hg.Vec3(0, 0, 0), h_align, hg.DTVA_Bottom, - cls.text_uniform_set_values, cls.text_uniform_set_texture_list, cls.text_render_state) + cls.uniforms_values_list, cls.uniforms_textures_list, cls.flat_render_state) diff --git a/source/vcr.py b/source/vcr.py index 1f7ca72..0ea00a4 100644 --- a/source/vcr.py +++ b/source/vcr.py @@ -10,6 +10,7 @@ import sqlite3 import Machines import MissileLauncherS400 +from overlays import * flag_init = False conn = None @@ -52,6 +53,12 @@ state = "disable" request_state = "disable" +color_before_event = hg.Color(1, 1, 0, 1) +color_after_event = hg.Color(1, 0, 0, 1) +event_max_size = 20 +event_timer_bound_futur = 0.25 +event_timer_bound_past = 2 + # Create recordable items from dogfight scene def setup_items(main): clear_items() @@ -306,8 +313,6 @@ def start_play(main): playing = True - - def stop_play(main): global playing, pausing playing = False @@ -372,13 +377,27 @@ def interpolate_mat(name_record): # TODO not used yet but can be one day item = v else: eval(p["load"]) + # Events: - c.execute(f"SELECT * FROM events where id_rec={current_id_play} and c <= {timer + 2} and c >= {timer - 2} ORDER BY c DESC;") + c.execute(f"SELECT * FROM events where id_rec={current_id_play} and c <= {timer + event_timer_bound_futur} and c >= {timer - event_timer_bound_past} ORDER BY c DESC;") r = c.fetchall() if r is not None and len(r)>0: - print("----------EVENTS !") for row in r: - print("timestamp:" + str(row["c"]) + " - value:" + row["v"]) + #print("timestamp:" + str(row["c"]) + " - value:" + row["v"]) + v = row["v"].split(":") + if v[0]=="hit": + if timer < row["c"]: + t = 1 - (row["c"] - timer) / event_timer_bound_futur + c = hg.Color(color_before_event) + else: + t = 1 - (timer - row["c"]) / event_timer_bound_past + c = hg.Color(color_after_event) + + c.a = t + + if t > 1e-5: + Overlays.add_circle3D(dc.deserialize_vec3(v[2]), float(v[1]) * event_max_size * t, c) + if not pausing: timer += hg.time_to_sec_f(dt) From b7122b08b15b68c3629069d11d4131a4e2f313c6 Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Tue, 13 Dec 2022 09:48:43 +0100 Subject: [PATCH 15/17] update test inputs devices --- source/test.py | 57 ++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/source/test.py b/source/test.py index ecb7dda..f2013ba 100644 --- a/source/test.py +++ b/source/test.py @@ -8,12 +8,14 @@ hg.WindowSystemInit() res_x, res_y = 600, 800 -win = hg.RenderInit('Harfang - Read Gamepad', res_x, res_y, hg.RF_VSync) +win = hg.NewWindow(res_x, res_y) +hg.RenderInit(win, hg.RT_OpenGL) +hg.RenderReset(res_x, res_y, hg.RF_MSAA4X | hg.RF_MaxAnisotropy) -hg.AddAssetsFolder('assets_compiled') +hg.AddAssetsFolder("assets_compiled") -imgui_prg = hg.LoadProgramFromAssets('core/shader/imgui') -imgui_img_prg = hg.LoadProgramFromAssets('core/shader/imgui_image') +imgui_prg = hg.LoadProgramFromAssets("core/shader/imgui") +imgui_img_prg = hg.LoadProgramFromAssets("core/shader/imgui_image") hg.ImGuiInit(10, imgui_prg, imgui_img_prg) @@ -21,11 +23,14 @@ while not hg.ReadKeyboard().Key(hg.K_Escape): hg.ImGuiBeginFrame(res_x, res_y, hg.TickClock(), hg.ReadMouse(), hg.ReadKeyboard()) - for i in range(16): - generic_controller = hg.Joystick(f"generic_controller_slot_{i}") + + joysticks = hg.GetJoystickNames() + + for joystick_name in joysticks: + generic_controller = hg.Joystick(joystick_name) generic_controller.Update() if generic_controller.IsConnected(): - if hg.ImGuiCollapsingHeader(f"generic_controller_slot_{i}"): + if hg.ImGuiCollapsingHeader(joystick_name): hg.ImGuiIndent() for j in range(generic_controller.ButtonsCount()): hg.ImGuiText(f"button {j}: {generic_controller.Down(j)}") @@ -33,27 +38,29 @@ hg.ImGuiText(f"axe {j}: {generic_controller.Axes(j)}") hg.ImGuiUnindent() else: - hg.ImGuiText(f"Generic Controller: {i} not connected") - - - gamepad_controller = hg.Gamepad() - gamepad_controller.Update() - if gamepad_controller.IsConnected(): - if hg.ImGuiCollapsingHeader("gamepad"): - hg.ImGuiIndent() - for j in range(10): - hg.ImGuiText(f"button {j}: {gamepad_controller.Down(j)}") - for j in range(10): - hg.ImGuiText(f"axe {j}: {gamepad_controller.Axes(j)}") - hg.ImGuiUnindent() - else: - hg.ImGuiText("Gamepad Controller not connected") - - + hg.ImGuiText(f"Joystick not connected - " + joystick_name) + + gamespads = hg.GetGamepadNames() + + for gp_name in gamespads: + gamepad_controller = hg.Gamepad(gp_name) + gamepad_controller.Update() + if gamepad_controller.IsConnected(): + if hg.ImGuiCollapsingHeader("gamepad: " + gp_name): + hg.ImGuiIndent() + for j in range(16): + hg.ImGuiText(f"button {j}: {gamepad_controller.Down(j)}") + for j in range(10): + hg.ImGuiText(f"axe {j}: {gamepad_controller.Axes(j)}") + hg.ImGuiUnindent() + else: + hg.ImGuiText("Gamepad Controller not connected - " + gp_name) + - hg.SetView2D(0, res_x, res_y, -1, 1, 1, 100, hg.CF_Color | hg.CF_Depth, hg.Color.Black, 1, 0) + hg.SetView2D(0, 0, 0, res_x, res_y, -1, 1, hg.CF_Color | hg.CF_Depth, hg.Color.Black, 1, 0) hg.ImGuiEndFrame(0) hg.Frame() hg.UpdateWindow(win) +hg.RenderShutdown() hg.DestroyWindow(win) From 12327405dfeeae6a4f0a5b75f965973cc539ca16 Mon Sep 17 00:00:00 2001 From: ErkMkd Date: Tue, 13 Dec 2022 11:19:23 +0100 Subject: [PATCH 16/17] Added recorder help in readme --- README.md | 31 +++++++++++++++++++++++++++++++ screenshots/recorder_02.png | Bin 0 -> 10153 bytes screenshots/recorder_03.png | Bin 0 -> 3966 bytes screenshots/recorder_04.png | Bin 0 -> 12245 bytes screenshots/recorder_05.png | Bin 0 -> 201471 bytes 5 files changed, 31 insertions(+) create mode 100644 screenshots/recorder_02.png create mode 100644 screenshots/recorder_03.png create mode 100644 screenshots/recorder_04.png create mode 100644 screenshots/recorder_05.png diff --git a/README.md b/README.md index 48d685d..f8c6cce 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,37 @@ The source code and the graphics assets are made available for studying purpose. * HTC Vive Pro * Meta Quest 2 (in Oculus Link mode) + +## Record / Replay API overview: + +1. Choose the mission you want to record. +2. Type "F9" to open the recorder interface: +![Recorder](screenshots/recorder_02.png) + * `Add user` : You can add a user. Each user has his own list of records. + * `Users` : Use this combo boxe to selected the user. + * `Item`: List of recordable items. + * `Start recording`: Record the simulation. + * `Recording FPS` : Recording frequency (Frame Per Second). + * `Records`: Select record to replay. + * `Enter replay mode`: Replayer. + +3. Replay: +![Recorder](screenshots/recorder_03.png) +* Selected the user and record you want to replay. +* Press `Start play` + The items are created. You can pause the replay, and move the Timeline cursor. +![Recorder](screenshots/recorder_04.png) +* `Display selected item`: Display a sight on selected item, to identify the item in 3D view. +* `Prev frame`, `Next frame`: Backward / foreward frame by frame. You can also press `-`, `+` on keyboard. + +### Events: +Hits (missiles, machine gun, crashes) are recorded and displayed as circles during replay: +![Recorder](screenshots/recorder_05.png) +Yellow circles : before the event +Red circles : after the event +The maximum size of the circle depends on the power of the collision. + + ## Network mode overview The "Network" mode allows you to control the planes from a third party machine. diff --git a/screenshots/recorder_02.png b/screenshots/recorder_02.png new file mode 100644 index 0000000000000000000000000000000000000000..5162b1fd7e19e1accc663bd6a6805a636c293764 GIT binary patch literal 10153 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 literal 0 HcmV?d00001 diff --git a/screenshots/recorder_04.png b/screenshots/recorder_04.png new file mode 100644 index 0000000000000000000000000000000000000000..abee2894c9ea50a9ec4476e9220a9a3bb8854bf8 GIT binary patch literal 12245 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�HqvL_t@M(qH}QAS{3#@4ORAdC2du>D-bQ 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

6TF1qc}q0B~|^w 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

9i=cZmTEOC9NXWErjuG@mv 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 Date: Tue, 13 Dec 2022 11:27:44 +0100 Subject: [PATCH 17/17] fix readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f8c6cce..ee1a44e 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ The "Network" mode allows you to control the planes from a third party machine. | Command | Key | | ------- | :-: | | Back view | 2 | -| Front view | 2 | +| Front view | 8 | | Left view | 4 | | Right view | 6 | | Satellite view | 5 |