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/README.md b/README.md index 88b4320..d337371 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,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 0000000..5162b1f Binary files /dev/null and b/screenshots/recorder_02.png differ diff --git a/screenshots/recorder_03.png b/screenshots/recorder_03.png new file mode 100644 index 0000000..ac0aed0 Binary files /dev/null and b/screenshots/recorder_03.png differ diff --git a/screenshots/recorder_04.png b/screenshots/recorder_04.png new file mode 100644 index 0000000..abee289 Binary files /dev/null and b/screenshots/recorder_04.png differ diff --git a/screenshots/recorder_05.png b/screenshots/recorder_05.png new file mode 100644 index 0000000..a5d22e2 Binary files /dev/null and b/screenshots/recorder_05.png differ 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 c78b871..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 @@ -231,6 +234,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): @@ -334,10 +342,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 @@ -510,6 +518,8 @@ def update(self, dts): 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: @@ -519,7 +529,7 @@ def update(self, dts): 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 @@ -537,18 +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): + def activate(self): if not self.wreck: + super().activate() self.bullets_particles.flow = 24 / 2 - - def stop_machine_gun(self): + + 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 @@ -564,6 +578,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 @@ -1120,14 +1135,14 @@ def fire_machine_gun_kb(self, 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() + 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() + if mgd is not None and mgd.is_activated(): + mgd.deactivate() def fire_missile_kb(self, value): if ControlDevice.keyboard.Pressed(value): @@ -1254,14 +1269,14 @@ def fire_machine_gun_la3(self, 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() + 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() + if mgd is not None and mgd.is_activated(): + mgd.deactivate() def fire_missile_la3(self, value): @@ -1388,14 +1403,14 @@ def fire_machine_gun_gp(self, 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() + 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() + if mgd is not None and mgd.is_activated(): + mgd.deactivate() def fire_missile_gp(self, value): if ControlDevice.gamepad.Pressed(value): @@ -1758,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: @@ -1784,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 @@ -1885,8 +1900,8 @@ def update_IA_idle(self, aircraft): 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) @@ -1916,8 +1931,8 @@ def update_IA_landing(self, aircraft, dts): 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 @@ -2059,14 +2074,14 @@ def update_IA_fight(self, aircraft, dts): 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() + 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() + if mgd is not None and mgd.is_activated(): + mgd.deactivate() flag_missiles_ok = False if md is not None: @@ -2086,8 +2101,8 @@ def update_IA_fight(self, aircraft, dts): 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) diff --git a/source/Machines.py b/source/Machines.py index 9ecb97c..330941d 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 @@ -50,6 +55,13 @@ def test_collision(self, nd: hg.Node): for ndt in self.collision_nodes: 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): + for listener in self.event_listeners: + listener("hit",{"value":value, "position": position, "timestamp": Collisions_Object.timer}) # ??? position or hg.Vec3(position) ??? # ===================================================================================================== @@ -341,6 +353,8 @@ def __init__(self, name, model_name, scene: hg.Scene, scene_physics, pipeline_re self.flag_focus = False + self.hits = [] + self.playfield_distance = 0 self.commands.update({"SET_HEALTH_LEVEL": self.set_health_level}) @@ -453,6 +467,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 +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): - self.set_health_level(self.health_level - value) + + def hit(self, value, position): + Collisions_Object.hit(self, value, position) + if not self.wreck: + self.set_health_level(self.health_level - value) def destroy_nodes(self): AnimatedModel.destroy_nodes(self) @@ -970,6 +988,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 +1168,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 +1468,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 +1554,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 @@ -1695,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 @@ -1855,7 +1874,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 +1913,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,8 +2392,8 @@ def destroy(self): self.destroy_nodes() self.flag_destroyed = True - def hit(self, value): - pass + def hit(self, value, position): + Collisions_Object.hit(self, value, position) def update_kinetics(self, dts): rot = self.radar.GetTransform().GetRot() @@ -2409,5 +2428,8 @@ def get_thrust_level(self): def get_brake_level(self): return self.brake_level + 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/Missions.py b/source/Missions.py index bfdff72..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() @@ -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/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/assets/sprites/machine_gun_sight.png b/source/assets/sprites/machine_gun_sight.png index 5ff5b3a..ab4dcb3 100644 Binary files a/source/assets/sprites/machine_gun_sight.png and b/source/assets/sprites/machine_gun_sight.png differ diff --git a/source/assets/sprites/missile_sight.png b/source/assets/sprites/missile_sight.png index 3033aa2..3e444d1 100644 Binary files a/source/assets/sprites/missile_sight.png and b/source/assets/sprites/missile_sight.png differ diff --git a/source/assets/sprites/target_sight.png b/source/assets/sprites/target_sight.png index 6d95cd0..0abf30c 100644 Binary files a/source/assets/sprites/target_sight.png and b/source/assets/sprites/target_sight.png differ diff --git a/source/data_converter.py b/source/data_converter.py index 03283ff..68a9905 100644 --- a/source/data_converter.py +++ b/source/data_converter.py @@ -65,6 +65,38 @@ 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) + +# 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(";") + 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 71379b7..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: @@ -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..8370a02 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 @@ -70,8 +72,11 @@ 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 + simulation_dt = 0 # dt in ns used by simulation (kinetics & renderer) flag_network_mode = False flag_client_update_mode = False @@ -126,8 +131,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 @@ -160,10 +166,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 = [] @@ -217,7 +219,7 @@ class Main: #======= Aircrafts view: selected_aircraft_id = 0 - selected_aircraft = None + selected_machine = None @classmethod def init(cls): @@ -300,7 +302,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 @@ -442,8 +444,20 @@ 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: aircraft.destroy() for aircraft in cls.players_allies: @@ -456,6 +470,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() @@ -472,6 +489,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): @@ -479,78 +507,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_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(cls, machine:Destroyable_Machine, smoke_color): + 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(missile_type + machine.name + "." + 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) - if missile_type == Mica.model_name: - missile = Mica(missile_type + machine.name + "." + 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) - if missile_type == Karaoke.model_name: - missile = Karaoke(missile_type + machine.name + "." + 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) - 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 = 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 = [] @@ -558,61 +617,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() @@ -654,30 +665,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 = {} @@ -687,6 +698,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): @@ -703,14 +721,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 @@ -1061,22 +1079,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 @@ -1125,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 @@ -1201,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 @@ -1211,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 @@ -1344,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: @@ -1420,10 +1446,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(): @@ -1442,6 +1469,22 @@ def update_inputs(cls): else: cls.flag_generic_controller = False + @classmethod + def reset_timestamp(cls): + cls.framecount = 0 + cls.timer = 0 + MachineDevice.framecount = 0 + Collisions_Object.framecount = 0 + + @classmethod + def update_timestamp(cls, dts): + 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): cls.flag_client_ask_update_scene = True @@ -1467,46 +1510,72 @@ 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()) - 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,cls.keyboard) + # =========== State update: if cls.flag_renderless: used_dt = forced_dt + Main.simulation_dt = used_dt else: 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 + 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: 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/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/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/states.py b/source/states.py index d64618e..29f4fd6 100644 --- a/source/states.py +++ b/source/states.py @@ -2,14 +2,20 @@ 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_display_selected_aircraft = False Main.flag_running = False + + vcr.request_new_state(Main, "disable") + Main.smart_camera.flag_inertia = True + Main.set_renderless_mode(False) Main.flag_network_mode = False Main.fading_to_next_state = False @@ -104,10 +110,12 @@ 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: @@ -115,15 +123,6 @@ def update_menu_phase(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) @@ -228,33 +227,60 @@ def update_menu_phase(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): 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(): + Main.flag_display_selected_aircraft = False + Main.smart_camera.flag_inertia = True + vcr.request_new_state(Main, "record") Main.flag_running = False Main.fading_to_next_state = False Main.post_process.setup_fading(1, 1) @@ -266,8 +292,8 @@ def init_main_phase(): 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() @@ -276,17 +302,19 @@ 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.setup_items(Main) Main.num_start_frames = 10 - Main.timestamp = 0 + Main.reset_timestamp() Main.flag_running = True - return update_main_phase - + vcr.validate_requested_state() + return main_state -def update_main_phase(dts): - Main.timestamp += 1 +def main_state(dts): + + Main.update_timestamp(dts) if not Main.flag_renderless: Main.post_process.update_fading(dts) if Main.flag_sfx: @@ -303,8 +331,8 @@ def update_main_phase(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: @@ -317,12 +345,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: @@ -336,6 +364,10 @@ def update_main_phase(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: @@ -374,18 +406,124 @@ 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 + - return update_main_phase +# =================================== 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 + 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.reset_timestamp() + Main.flag_running = True + vcr.validate_requested_state() + + +def replay_state(dts): + + 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_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: + 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) + + if vcr.is_init(): + vcr.update(Main, Main.simulation_dt) + + 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) + + + if not Main.fading_to_next_state: + + if Main.keyboard.Pressed(hg.K_Tab) or vcr.request_state == "disable": + vcr.request_new_state(Main, "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(): + Main.flag_display_selected_aircraft = False + Main.smart_camera.flag_inertia = True Main.set_renderless_mode(False) Main.flag_running = False Main.deactivate_cockpit_view() @@ -422,43 +560,56 @@ 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 - + return end_state -def update_end_phase(dts): - - Main.smart_camera.update(Main.camera, dts) +def end_state(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_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 + 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_phase() - return update_menu_phase - - return update_end_phase + 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/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) diff --git a/source/vcr.py b/source/vcr.py new file mode 100644 index 0000000..0ea00a4 --- /dev/null +++ b/source/vcr.py @@ -0,0 +1,743 @@ +#!/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 +import Machines +import MissileLauncherS400 +from overlays import * + +flag_init = False +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 +updating_database = False +progress_cptr = 0 +render_head = False +timer = 0 +previous_timer = 0 +recorded_min_time = 0 +recorded_max_time = 0 +recorded_fps = 60 + +items = {} +items_list = [] +items_names = [] +selected_item_idx = 0 +selected_record = 0 +records = None +records_events = None +last_value_recorded = {} + +task_record_in_database = None + +items_words_list = ["world", "int", "float", "machine_state"] + +fps_record = 60 + +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() + 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 +def AddItem(item, params=[], name=None, container=None): + global items, items_list, items_names + 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) + #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 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, records_events + selected_item_idx = 0 + items = {} + items_list = [] + items_names = [] + records_events = [] + +def is_init(): + return flag_init + +def init(): + global conn, selected_record, flag_init + + # 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, 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, c FLOAT, v TEXT)''') + + # add default user + 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 + +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 + ";" + str(item.nationality) + ";" + name) + return scene_items + +def start_record(name_record): + global recording, records, records_events, current_id_rec, last_value_recorded + + # check if we are already start + if recording: + return + + records = None + records_events = [] + last_value_recorded = {} + + 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, 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() + + if r is not None: + current_id_rec = r["id_rec"] + conn.commit() + print(f"create record: {name_record}, {current_id_rec}") + + recording = True + +# 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 + n_steps = 2*len(records) + len(records_events) + 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};") + progress_cptr = i / n_steps + 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 / 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};") + 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() + updating_database = True + + +def update_recording(main, dt): + global records, timer, previous_timer, last_value_recorded, recorded_min_time + if records is None: + records = {} + previous_timer = main.timer + + timer = main.timer + 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 == "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: + 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 == "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 + + if len(records) == 0: + recorded_min_time = timer + records[timer] = record + previous_timer = timer + + #timer += hg.time_to_sec_f(dt) + +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() + main.user_aircraft = None + main.setup_views_carousel(True) + main.set_view_carousel("fps") + setup_items(main) + + +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: + scene_items = r["scene_items"].split(":") + if len(scene_items) > 0: + main.destroy_players() + create_scene(main, scene_items) + timer = recorded_min_time + print(str(timer)) + playing = True + + +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(main, 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 == "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: + 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"]) + + # Events: + 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: + for row in r: + #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) + + if timer >= recorded_max_time: + timer = recorded_max_time + if not pausing: + pause_play() + + +def update_gui_record(main): + + 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: + 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, 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_min_time, recorded_fps)) + + if hg.ImGuiButton("Enter replay mode"): + request_new_state(main, "replay") + + elif recording: + if hg.ImGuiButton("Stop recording"): + stop_record() + hg.ImGuiEnd() + +def update_gui_replay(main, keyboard): + + 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: + + 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] + 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, 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_min_time, recorded_fps)) + + if hg.ImGuiButton("Start play"): + start_play(main) + + if hg.ImGuiButton("Exit replay mode"): + request_state = "disable" + + else: + if recorded_max_time: + timer = hg.ImGuiSliderFloat("Timeline", timer, recorded_min_time, 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(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(): + 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() + +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(main, req_state): + global request_state + request_state = req_state + clear_items() + if req_state == "disable": + if recording: + stop_record() + elif playing: + stop_play(main) + elif req_state == "record": + if playing: + stop_play(main) + elif req_state =="replay": + if recording: + stop_record() + +# Call this when the scene to record/replay is ready: + +def validate_requested_state(): + global state + state = request_state + + +def update_gui(main,keyboard): + + if updating_database: + update_gui_updating_database() + elif state != request_state: + update_gui_wait_request() + elif state == "record": + update_gui_record(main) + elif state == "replay": + update_gui_replay(main, keyboard) + elif state == "disable": + update_gui_disable() + + +def update(main, dt): + global 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 + if conn is not None: + conn.commit() + conn.close() + conn = None + + +def serialize_machine_state(machine:Machines.Destroyable_Machine): + if machine.type == Machines.Destroyable_Machine.TYPE_AIRCRAFT: + return serialize_aircraft_state(machine) + 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) + 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) + + +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()) + 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()) + 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()) + 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()) + 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]) + 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.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]) + machine.v_move = dc.deserialize_vec3(f[1]) + machine.wreck = bool(f[2]) + machine.get_parent_node().GetTransform().SetWorld(matrix) + +def deserialize_ship_state(machine:Machines.Carrier, s:str): + 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(":") + machine.wreck = bool(f[1]) + matrix = dc.deserialize_mat4(f[0]) + machine.get_parent_node().GetTransform().SetWorld(matrix) \ No newline at end of file