├── src ├── environments │ ├── dogfightEnv │ │ ├── models │ │ │ ├── Flare.py │ │ │ └── __init__.py │ │ ├── evade.bat │ │ ├── dogfight.bat │ │ ├── initialization │ │ │ ├── __init__.py │ │ │ ├── ActionSpace.py │ │ │ ├── TakeOff.py │ │ │ └── ObservationSpace.py │ │ ├── __init__.py │ │ └── dogfightEnv.py │ ├── dfEnv │ │ ├── __init__.py │ │ └── dfEnv.py │ └── jsbsimEnv │ │ ├── __init__.py │ │ ├── jsbsimFdm.py │ │ └── jsbsimEnv.py └── models │ ├── sac_jsbsim.py │ ├── sac_df.py │ └── evade_sb3.py ├── images ├── host.png ├── logo.png ├── heading.png ├── fgattribute.png ├── roll_attitude.png ├── pitch_attitude.png └── coordinate_system.png ├── requirements.txt ├── example ├── launch.py ├── ego_oppo_plane.py └── maneuvering.py ├── .gitignore ├── README.md ├── doc ├── properties_of_harfang_df.md ├── QuickStart_zh.md └── QuickStart_en.md ├── df2.py ├── tutorial.ipynb └── bak ├── Missions.bak ├── Missions_evade.bak └── Missions_dogfight.bak /src/environments/dogfightEnv/models/Flare.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/environments/dogfightEnv/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .Flare import * -------------------------------------------------------------------------------- /images/host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrwangyou/DBRL/HEAD/images/host.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrwangyou/DBRL/HEAD/images/logo.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.23.4 2 | gym==0.21.0 3 | stable-baselines3==1.6.2 4 | -------------------------------------------------------------------------------- /images/heading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrwangyou/DBRL/HEAD/images/heading.png -------------------------------------------------------------------------------- /images/fgattribute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrwangyou/DBRL/HEAD/images/fgattribute.png -------------------------------------------------------------------------------- /images/roll_attitude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrwangyou/DBRL/HEAD/images/roll_attitude.png -------------------------------------------------------------------------------- /images/pitch_attitude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrwangyou/DBRL/HEAD/images/pitch_attitude.png -------------------------------------------------------------------------------- /images/coordinate_system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrwangyou/DBRL/HEAD/images/coordinate_system.png -------------------------------------------------------------------------------- /src/environments/dogfightEnv/evade.bat: -------------------------------------------------------------------------------- 1 | copy ..\..\..\bak\Missions_evade.bak .\dogfight_sandbox_hg2\source\Missions.py 2 | 3 | echo Done. 4 | -------------------------------------------------------------------------------- /src/environments/dogfightEnv/dogfight.bat: -------------------------------------------------------------------------------- 1 | copy ..\..\..\bak\Missions_dogfight.bak .\dogfight_sandbox_hg2\source\Missions.py 2 | 3 | echo Done. 4 | -------------------------------------------------------------------------------- /src/environments/dogfightEnv/initialization/__init__.py: -------------------------------------------------------------------------------- 1 | from .ActionSpace import * 2 | from .ObservationSpace import * 3 | from .TakeOff import * 4 | -------------------------------------------------------------------------------- /src/environments/dfEnv/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | from src.environments.dfEnv.dfEnv import DfEnv 3 | except: 4 | from gym.envs.dfEnv.dfEnv import DfEnv 5 | -------------------------------------------------------------------------------- /src/environments/dogfightEnv/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | from .dogfightEnv import DogfightEnv 3 | except: 4 | from gym.envs.dogfightEnv.dogfightEnv import DogfightEnv 5 | -------------------------------------------------------------------------------- /src/environments/jsbsimEnv/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | from src.environments.jsbsimEnv.jsbsimEnv import JsbsimEnv 3 | from src.environments.jsbsimEnv.jsbsimFdm import JsbsimFdm 4 | except: 5 | from gym.envs.jsbsimEnv.jsbsimEnv import JsbsimEnv 6 | from gym.envs.jsbsimEnv.jsbsimFdm import JsbsimFdm 7 | 8 | -------------------------------------------------------------------------------- /src/environments/dogfightEnv/initialization/ActionSpace.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from gym.spaces import Box 3 | 4 | def action_space( 5 | pitch_enable=True, 6 | roll_enable=True, 7 | yaw_enable=True, 8 | flaps_enable=True, 9 | throttle_enable=False, 10 | flare_enable=False, 11 | ): 12 | action_infimum = np.array([]) 13 | action_supermum = np.array([]) 14 | action_type = [] 15 | 16 | if pitch_enable: # 俯仰角 17 | action_infimum = np.append(action_infimum, -1) 18 | action_supermum = np.append(action_supermum, 1) 19 | action_type = action_type + ['_df.set_plane_pitch'] 20 | if roll_enable: # 滚转角 21 | action_infimum = np.append(action_infimum, -1) 22 | action_supermum = np.append(action_supermum, 1) 23 | action_type = action_type + ['_df.set_plane_roll'] 24 | if yaw_enable: # 偏航角 25 | action_infimum = np.append(action_infimum, -1) 26 | action_supermum = np.append(action_supermum, 1) 27 | action_type = action_type + ['_df.set_plane_yaw'] 28 | if flaps_enable: # 襟翼 29 | action_infimum = np.append(action_infimum, 0) 30 | action_supermum = np.append(action_supermum, 1) 31 | action_type = action_type + ['_df.set_plane_flaps'] 32 | if throttle_enable: # 油门 33 | action_infimum = np.append(action_infimum, 0) 34 | action_supermum = np.append(action_supermum, 1) 35 | action_type = action_type + ['_df.set_plane_thrust'] 36 | if flare_enable: # 干扰弹 37 | action_infimum = np.append(action_infimum, 0) 38 | action_supermum = np.append(action_supermum, 1) 39 | action_type = action_type + ['Flare'] 40 | 41 | action = Box( 42 | low=action_infimum, 43 | high=action_supermum, 44 | dtype=float 45 | ) 46 | 47 | return action, action_type 48 | -------------------------------------------------------------------------------- /src/environments/dogfightEnv/initialization/TakeOff.py: -------------------------------------------------------------------------------- 1 | 2 | def start_on_carrier( 3 | df, 4 | planeID, 5 | enemyID, 6 | missile_slot, 7 | missileID, 8 | ): 9 | t = 0 10 | while t < 1: 11 | plane_state = df.get_plane_state(enemyID) 12 | df.update_scene() 13 | t = plane_state["thrust_level"] 14 | 15 | df.activate_post_combustion(enemyID) 16 | df.activate_post_combustion(planeID) 17 | 18 | df.set_plane_pitch(enemyID, -0.5) 19 | df.set_plane_pitch(planeID, -0.5) 20 | 21 | p = 0 22 | while p < 15: 23 | plane_state = df.get_plane_state(enemyID) 24 | df.update_scene() 25 | p = plane_state["pitch_attitude"] 26 | 27 | df.stabilize_plane(enemyID) 28 | df.stabilize_plane(planeID) 29 | 30 | df.retract_gear(enemyID) 31 | df.retract_gear(planeID) 32 | 33 | s = 0 34 | while s < 1000: 35 | plane_state = df.get_plane_state(enemyID) 36 | df.update_scene() 37 | s = plane_state["altitude"] 38 | 39 | df.set_plane_yaw(planeID, 1) 40 | 41 | missiles = df.get_machine_missiles_list(enemyID) 42 | missileID = missiles[missile_slot] 43 | 44 | df.fire_missile(enemyID, missile_slot) 45 | 46 | df.set_missile_target(missileID, planeID) 47 | df.set_missile_life_delay(missileID, 30) 48 | 49 | 50 | def start_in_sky( 51 | df, 52 | planeID, 53 | enemyID, 54 | missile_slot, 55 | missileID, 56 | ): 57 | t = 0 58 | while t < 1: 59 | plane_state = df.get_plane_state(planeID) 60 | df.update_scene() 61 | t = plane_state["thrust_level"] 62 | df.activate_post_combustion(planeID) 63 | 64 | df.fire_missile(enemyID, missile_slot) 65 | 66 | df.set_missile_target(missileID, planeID) 67 | df.set_missile_life_delay(missileID, 10) 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /example/launch.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import argparse 4 | 5 | sys.path.append('.') 6 | 7 | from src.environments.dogfightEnv.dogfight_sandbox_hg2.network_client_example import dogfight_client as df 8 | 9 | def parse_args(): 10 | parser = argparse.ArgumentParser(description='TBD') 11 | parser.add_argument('--host', default='10.184.0.0', help='specifies Harfang host id') 12 | parser.add_argument('--port', default='50888', help='specifies Harfang port id') 13 | args = parser.parse_args() 14 | return args 15 | 16 | args = parse_args() 17 | 18 | df.connect(args.host, int(args.port)) 19 | 20 | planes = df.get_planes_list() 21 | 22 | for i in planes: 23 | df.reset_machine(i) 24 | 25 | df.set_plane_thrust(planes[1], 1) 26 | 27 | df.set_client_update_mode(True) 28 | 29 | # Thrust 30 | t = 0 31 | while t < 1: 32 | time.sleep(1/60) 33 | plane_state = df.get_plane_state(planes[1]) 34 | df.update_scene() 35 | t = plane_state["thrust_level"] 36 | 37 | df.activate_post_combustion(planes[1]) 38 | 39 | df.set_plane_pitch(planes[1], -0.5) 40 | 41 | # Pitch 42 | p = 0 43 | while p < 15: 44 | time.sleep(1/60) 45 | plane_state = df.get_plane_state(planes[1]) 46 | df.update_scene() 47 | p = plane_state["pitch_attitude"] 48 | 49 | df.stabilize_plane(planes[1]) 50 | 51 | df.retract_gear(planes[1]) 52 | 53 | s = 0 54 | while s < 100: 55 | time.sleep(1/60) 56 | plane_state = df.get_plane_state(planes[1]) 57 | df.update_scene() 58 | s = plane_state["altitude"] 59 | 60 | df.set_plane_pitch(planes[1], 0.1) 61 | 62 | p = 15 63 | while p > 0: 64 | time.sleep(1/60) 65 | plane_state = df.get_plane_state(planes[1]) 66 | df.update_scene() 67 | p = plane_state["pitch_attitude"] 68 | 69 | df.stabilize_plane(planes[1]) 70 | 71 | while True: 72 | time.sleep(1/60) 73 | df.update_scene() 74 | # Print anything you want to see here while controlling the plane with your keyboard 75 | # print('') 76 | -------------------------------------------------------------------------------- /src/environments/dogfightEnv/initialization/ObservationSpace.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from gym.spaces import Box 3 | 4 | def observation_space( 5 | ego_plane_position=True, 6 | ego_plane_attitude=True, 7 | oppo_plane_position=False, 8 | oppo_plane_attitude=False, 9 | missile_position=True, 10 | missile_attitude=True, 11 | missile_relative_azimuth=False, 12 | ): 13 | observation_infimum = np.array([]) 14 | observation_supermum = np.array([]) 15 | observation_type = [] 16 | 17 | if ego_plane_position: # 我方飞机位置 18 | observation_infimum = np.append(observation_infimum, [-300, -300, -1]) 19 | observation_supermum = np.append(observation_supermum, [300, 300, 200]) 20 | if ego_plane_attitude: # 我方飞机姿态 21 | observation_infimum = np.append(observation_infimum, [0, -360, -360]) 22 | observation_supermum = np.append(observation_supermum, [360, 360, 360]) 23 | if oppo_plane_position: # 敌方飞机位置 24 | observation_infimum = np.append(observation_infimum, [-300, -300, -1]) 25 | observation_supermum = np.append(observation_supermum, [300, 300, 200]) 26 | if oppo_plane_attitude: # 敌方飞机姿态 27 | observation_infimum = np.append(observation_infimum, [0, -360, -360]) 28 | observation_supermum = np.append(observation_supermum, [360, 360, 360]) 29 | if missile_position: # 导弹位置 30 | observation_infimum = np.append(observation_infimum, [-300, -300, -1]) 31 | observation_supermum = np.append(observation_supermum, [300, 300, 200]) 32 | if missile_attitude: # 导弹姿态 33 | observation_infimum = np.append(observation_infimum, [-315, -315, -315]) 34 | observation_supermum = np.append(observation_supermum, [315, 315, 315]) 35 | if missile_relative_azimuth: # 导弹相对方位角 36 | observation_infimum = np.append(observation_infimum, [-1, -1, -1]) 37 | observation_supermum = np.append(observation_supermum, [1, 1, 1]) 38 | 39 | observation = Box( 40 | low=observation_infimum, 41 | high=observation_supermum, 42 | dtype=np.float64 43 | ) 44 | 45 | return observation 46 | -------------------------------------------------------------------------------- /src/models/sac_jsbsim.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import time 4 | 5 | import gym 6 | import numpy as np 7 | from stable_baselines3 import SAC 8 | from stable_baselines3.common.noise import (NormalActionNoise, 9 | OrnsteinUhlenbeckActionNoise) 10 | 11 | 12 | def parse_args(): 13 | parser = argparse.ArgumentParser(description='TBD') 14 | # parser.add_argument('--modelPath', default='/data/wnn_data/bestModel/', metavar='str', help='specifies the pre-trained model') 15 | parser.add_argument('--playSpeed', default=0, metavar='double', help='specifies to run in real world time') 16 | parser.add_argument('--trainPolicy', default='Level', metavar='string', help='specifies the opposite plane\'s strategy of dogfighting') 17 | parser.add_argument('--train', action='store_true', help='specifies the running mode of DBRL') 18 | parser.add_argument('--test', action='store_true', help='specifies the running mode of DBRL') 19 | parser.add_argument('--timesteps', default=10000000, metavar='int', help='specifies the training timesteps. Only works when --train is specified') 20 | args = parser.parse_args() 21 | return args 22 | 23 | args = parse_args() 24 | 25 | env = gym.make( 26 | "DBRLJsbsim-v0", 27 | policy2=args.trainPolicy 28 | ) 29 | 30 | n_actions = env.action_space.shape[-1] 31 | action_noise = NormalActionNoise(mean=np.zeros(n_actions), sigma=0.1 * np.ones(n_actions)) 32 | 33 | model = SAC( 34 | "MlpPolicy", 35 | env, 36 | verbose=1, 37 | action_noise=action_noise, 38 | ) 39 | 40 | path = r'./log/' 41 | if not os.path.exists(path): 42 | os.mkdir(path) 43 | 44 | if args.train: 45 | # try: 46 | # model.set_parameters("./log/sac_jsbsim") 47 | # except: 48 | # pass 49 | model.learn(total_timesteps=int(args.timesteps), log_interval=1) 50 | model.save("./log/sac_jsbsim") 51 | 52 | if args.test: 53 | model = SAC.load("./log/sac_jsbsim") 54 | 55 | win = 0 56 | episode = 0 57 | 58 | obs = env.reset() 59 | while True: 60 | action, _states = model.predict(obs) 61 | obs, rewards, dones, info = env.step(action) 62 | env.render() 63 | if dones == True: 64 | f = open('./log/sac_jsbsim_record.txt', 'a') 65 | if rewards == 50: 66 | win += 1 67 | episode += 1 68 | f.write("{} / {}\n".format(win, episode)) 69 | print("Done! episode: {}\tacc: {}".format(episode, win / episode)) 70 | time.sleep(2) 71 | env.reset() 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Customized 132 | bestModel/ 133 | src/environments/dogfightEnv/dogfight_sandbox_hg2/ 134 | log/ 135 | test/ 136 | .vscode/ 137 | tmp/ 138 | catalog/ 139 | -------------------------------------------------------------------------------- /example/ego_oppo_plane.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | import gym 5 | import numpy as np 6 | from stable_baselines3 import SAC 7 | from stable_baselines3.common.noise import (NormalActionNoise, 8 | OrnsteinUhlenbeckActionNoise) 9 | 10 | 11 | 12 | env = gym.make( 13 | "DBRLDogfight-v0", 14 | host='192.168.239.1', 15 | port='50888', 16 | rendering=True, 17 | ) 18 | 19 | n_actions = env.action_space.shape[-1] 20 | action_noise = NormalActionNoise(mean=np.zeros(n_actions), sigma=0.1 * np.ones(n_actions)) 21 | 22 | model = SAC( 23 | "MlpPolicy", 24 | env, 25 | verbose=1, 26 | action_noise=action_noise, 27 | ) 28 | 29 | path = r'./log/' 30 | if not os.path.exists(path): 31 | os.mkdir(path) 32 | 33 | 34 | model = SAC.load("./log/sac_df2") 35 | 36 | win = 0 37 | episode = 0 38 | 39 | 40 | env.setProperty('plane', 0) # TFX 41 | env.setProperty('enemy', 3) # 42 | env.setProperty('missile', 0) # Mica 43 | obs = env.reset() 44 | times = 0 45 | sett = 0 46 | while True: 47 | if times == 1 and sett == 0: 48 | env.setProperty('plane', 0) # TFX 49 | env.setProperty('enemy', 3) # 50 | env.setProperty('missile', 1) # Meteor 51 | obs = env.reset() 52 | sett = 1 53 | if times == 2 and sett == 0: 54 | env.setProperty('plane', 1) # F-16 55 | env.setProperty('enemy', 3) # 56 | env.setProperty('missile', 0) # Mica 57 | obs = env.reset() 58 | sett = 1 59 | if times == 3 and sett == 0: 60 | env.setProperty('plane', 1) # F-16 61 | env.setProperty('enemy', 3) # 62 | env.setProperty('missile', 1) # Meteor 63 | obs = env.reset() 64 | sett = 1 65 | if times == 4 and sett == 0: 66 | env.setProperty('plane', 2) # Eurofighter Typhoon 67 | env.setProperty('enemy', 1) # 68 | env.setProperty('missile', 0) # AIM 69 | obs = env.reset() 70 | sett = 1 71 | if times == 5 and sett == 0: 72 | env.setProperty('plane', 3) # Rafale 73 | env.setProperty('enemy', 1) # 74 | env.setProperty('missile', 2) # Karaok 75 | obs = env.reset() 76 | sett = 1 77 | if times == 6: 78 | break 79 | 80 | 81 | action, _states = model.predict(obs) 82 | obs, rewards, dones, info = env.step(action) 83 | env.render() 84 | if dones == True: 85 | if rewards == 50: 86 | win += 1 87 | episode += 1 88 | print("Done! episode: {}\tacc: {}".format(episode, win / episode)) 89 | time.sleep(2) 90 | env.reset() 91 | times = times + 1 92 | sett = 0 93 | -------------------------------------------------------------------------------- /src/models/sac_df.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import time 4 | 5 | import gym 6 | import numpy as np 7 | from stable_baselines3 import SAC 8 | from stable_baselines3.common.noise import (NormalActionNoise, 9 | OrnsteinUhlenbeckActionNoise) 10 | 11 | 12 | def parse_args(): 13 | parser = argparse.ArgumentParser(description='TBD') 14 | parser.add_argument('--host', default='10.184.0.0', metavar='str', help='specifies Harfang host id') 15 | parser.add_argument('--port', default='50888', metavar='str', help='specifies Harfang port id') 16 | parser.add_argument('--planeSlot', default=1, metavar='int', help='specifies the ego plane') 17 | parser.add_argument('--enemySlot', default=3, metavar='int', help='specifies the enemy plane') 18 | parser.add_argument('--playSpeed', default=0, metavar='double', help='specifies to run in real world time') 19 | parser.add_argument('--train', action='store_true', help='specifies the running mode of DBRL') 20 | parser.add_argument('--test', action='store_true', help='specifies the running mode of DBRL') 21 | parser.add_argument('--timesteps', default=10000000, metavar='double', help='specifies the training timesteps. Only works when --train is specified') 22 | # parser.add_argument('--modelPath', default=None, metavar='str', help='specifies the pre-trained model. Only works when --train is specified') 23 | parser.add_argument('--record', action='store_true', help='specifies whether to record the evaluating result of DBRL. Only works when --test is specified') 24 | args = parser.parse_args() 25 | return args 26 | 27 | args = parse_args() 28 | 29 | env = gym.make( 30 | "DBRLDf-v0", 31 | host=args.host, 32 | port=args.port, 33 | plane_slot=args.planeSlot, 34 | enemy_slot=args.enemySlot, 35 | rendering=True if args.playSpeed else False 36 | ) 37 | 38 | n_actions = env.action_space.shape[-1] 39 | action_noise = NormalActionNoise(mean=np.zeros(n_actions), sigma=0.1 * np.ones(n_actions)) 40 | 41 | model = SAC( 42 | "MlpPolicy", 43 | env, 44 | verbose=1, 45 | action_noise=action_noise, 46 | ) 47 | 48 | path = r'./log/' 49 | if not os.path.exists(path): 50 | os.mkdir(path) 51 | 52 | if args.train: 53 | try: 54 | model = SAC.load("./log/sac_df", env) 55 | model.load_replay_buffer("./log/sac_df_rb") 56 | except: 57 | pass 58 | model.learn(total_timesteps=10000000, log_interval=1) 59 | model.save("./log/sac_df") 60 | model.save_replay_buffer("./log/sac_df_rb") 61 | 62 | if args.test: 63 | model = SAC.load("./log/sac_df") 64 | 65 | win = 0 66 | episode = 0 67 | 68 | if args.record: 69 | f = open('./log/sac_df_record.txt', 'r') 70 | for line in f: 71 | pass 72 | win = int(line.split()[0]) 73 | episode = int(line.split()[2]) 74 | 75 | f.close() 76 | 77 | obs = env.reset() 78 | while True: 79 | action, _states = model.predict(obs) 80 | obs, rewards, dones, info = env.step(action) 81 | env.render() 82 | if dones == True: 83 | if rewards == 50: 84 | win += 1 85 | episode += 1 86 | if args.record: 87 | f = open('./log/sac_df_record.txt', 'a') 88 | f.write("{} / {}\n".format(win, episode)) 89 | f.close() 90 | print("Done! episode: {}\tacc: {}".format(episode, win / episode)) 91 | time.sleep(2) 92 | env.reset() 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

DBRL: A Gym Dogfighting Simulation Benchmark for Reinforcement Learning Research

3 | 4 | 5 | 6 | 7 | [Reference Manual](doc/QuickStart_en.md) | [简体中文使用手册](doc/QuickStart_zh.md) 8 | 9 |
10 | 11 | ##
Introduction
12 | 13 | DBRL is an air combat simulation benchmark based on JSBSim and Dogfight 2. 14 | 15 | We adopt JSBSim as flight dynamic model to build our dogfight environment, and Dogfight 2 to simulate the situation when the plane is confronted with a missile. 16 | 17 | If you are new to air combat simulation or reinforcement learning algorithm, we advise you to learn [DBRL tutorial](tutorial.ipynb) first. 18 | 19 | ##
Gym Parameters
20 | 21 | ```python 22 | # JSBSim 23 | observation_space = Box( 24 | low=np.array([ 25 | -360, # Latitude 26 | -360, # Longitude 27 | 0, # Height above sea level 28 | -360, # Yaw 29 | -360, # Pitch 30 | -360, # Roll 31 | ] * 2), 32 | high=np.array([ 33 | 360, 34 | 360, 35 | 60000, 36 | 360, 37 | 360, 38 | 360, 39 | ] * 2) 40 | ) 41 | 42 | action_space = Box( 43 | low=np.array([ 44 | -1, # Aileron 45 | -1, # Elevator 46 | -1, # Rudder 47 | 0, # Throttle 48 | 0, # Flap 49 | 0, # Speed brake 50 | 0, # Spoiler 51 | ]), 52 | high=np.array([ 53 | 1, 54 | 1, 55 | 1, 56 | 1, 57 | 1, 58 | 1, 59 | 1, 60 | ]), 61 | ) 62 | 63 | 64 | # Dogfight 2 65 | if ego_plane_position: 66 | observation_infimum = np.append(observation_infimum, [-300, -300, -1]) 67 | observation_supermum = np.append(observation_supermum, [300, 300, 200]) 68 | if ego_plane_attitude: 69 | observation_infimum = np.append(observation_infimum, [0, -360, -360]) 70 | observation_supermum = np.append(observation_supermum, [360, 360, 360]) 71 | if oppo_plane_position: 72 | observation_infimum = np.append(observation_infimum, [-300, -300, -1]) 73 | observation_supermum = np.append(observation_supermum, [300, 300, 200]) 74 | if oppo_plane_attitude: 75 | observation_infimum = np.append(observation_infimum, [0, -360, -360]) 76 | observation_supermum = np.append(observation_supermum, [360, 360, 360]) 77 | if missile_position: 78 | observation_infimum = np.append(observation_infimum, [-300, -300, -1]) 79 | observation_supermum = np.append(observation_supermum, [300, 300, 200]) 80 | if missile_attitude: 81 | observation_infimum = np.append(observation_infimum, [-315, -315, -315]) 82 | observation_supermum = np.append(observation_supermum, [315, 315, 315]) 83 | if missile_relative_azimuth: 84 | observation_infimum = np.append(observation_infimum, [-1, -1, -1]) 85 | observation_supermum = np.append(observation_supermum, [1, 1, 1]) 86 | observation_space = Box( 87 | low=observation_infimum, 88 | high=observation_supermum, 89 | ) 90 | 91 | if pitch_enable: 92 | action_infimum = np.append(action_infimum, -1) 93 | action_supermum = np.append(action_supermum, 1) 94 | if roll_enable: 95 | action_infimum = np.append(action_infimum, -1) 96 | action_supermum = np.append(action_supermum, 1) 97 | if yaw_enable: 98 | action_infimum = np.append(action_infimum, -1) 99 | action_supermum = np.append(action_supermum, 1) 100 | if flaps_enable: 101 | action_infimum = np.append(action_infimum, 0) 102 | action_supermum = np.append(action_supermum, 1) 103 | if throttle_enable: 104 | action_infimum = np.append(action_infimum, 0) 105 | action_supermum = np.append(action_supermum, 1) 106 | if flare_enable: 107 | action_infimum = np.append(action_infimum, 0) 108 | action_supermum = np.append(action_supermum, 1) 109 | action_space = Box( 110 | low=action_infimum, 111 | high=action_supermum, 112 | ) 113 | 114 | ``` 115 | 116 | 117 | ##
Welcome PR
118 | 119 | If you find any mistakes while using, or have any suggestions and advices, please point it out in Github Issues. We're also looking forward to your contributions, such as your air combat reinforcement learning models, to this dogfight benchmark. 120 | 121 | -------------------------------------------------------------------------------- /src/models/evade_sb3.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import time 4 | 5 | import gym 6 | import numpy as np 7 | from stable_baselines3 import DDPG, TD3, SAC 8 | from stable_baselines3.common.noise import (NormalActionNoise, 9 | OrnsteinUhlenbeckActionNoise) 10 | 11 | 12 | def parse_args(): 13 | parser = argparse.ArgumentParser(description='TBD') 14 | parser.add_argument('--host', default='10.184.0.0', help='specifies Harfang host id') 15 | parser.add_argument('--port', default='50888', help='specifies Harfang port id') 16 | parser.add_argument('--planeSlot', default=1, help='specifies the ego plane') 17 | parser.add_argument('--enemySlot', default=3, help='specifies the enemy plane') 18 | parser.add_argument('--missileSlot', default=1, help='specifies the missile') 19 | parser.add_argument('--playSpeed', type=int, default=0, help='specifies to run in real world time') 20 | parser.add_argument('--train', action='store_true', help='specifies the running mode of DBRL') 21 | parser.add_argument('--test', action='store_true', help='specifies the running mode of DBRL') 22 | parser.add_argument('--timesteps', type=int, default=10000000, help='specifies the training timesteps. Only works when --train is specified') 23 | parser.add_argument('--model', default='SAC', help='specifies the DRL model used in algorithm training. Only works when --train or --test is specified') 24 | # parser.add_argument('--modelPath', default=None, help='specifies the pre-trained model. Only works when --train is specified') 25 | parser.add_argument('--recordResult', action='store_true', help='specifies whether to record the evaluating result of DBRL. Only works when --test is specified') 26 | parser.add_argument('--recordStatus', type=int, default=0, help='specifies the frequency of aircraft status recording during test flight. Only works when --test is specified') 27 | parser.add_argument('--flareEnable', action='store_true', help='specifies whether to activate the flare countermeasure') 28 | args = parser.parse_args() 29 | return args 30 | 31 | args = parse_args() 32 | 33 | env = gym.make( 34 | "DBRLDogfight-v0", 35 | host=args.host, 36 | port=args.port, 37 | plane_slot=args.planeSlot, 38 | enemy_slot=args.enemySlot, 39 | missile_slot=args.missileSlot, 40 | rendering=True if args.playSpeed else False, 41 | record_status=args.recordStatus, 42 | flare_enable=args.flareEnable, 43 | ) 44 | 45 | n_actions = env.action_space.shape[-1] 46 | action_noise = NormalActionNoise(mean=np.zeros(n_actions), sigma=0.1 * np.ones(n_actions)) 47 | 48 | method = { 49 | 'DDPG': 'ddpg', 50 | 'TD3': 'td3', 51 | 'SAC': 'sac', 52 | } 53 | 54 | assert args.model in method, 'Model {} not defined'.format(args.model) 55 | 56 | msg = "evade_{}_{}".format(args.model, args.timesteps) 57 | 58 | model = eval(args.model)( 59 | "MlpPolicy", 60 | env, 61 | verbose=1, 62 | action_noise=action_noise, 63 | ) 64 | 65 | path = r'./log/' 66 | if not os.path.exists(path): 67 | os.mkdir(path) 68 | 69 | if args.train: 70 | # try: 71 | # model = eval(args.model).load("./log/{}_evade".format(method[args.model]), env) 72 | # model.load_replay_buffer("./log/{}_evade_rb".format(method[args.model])) 73 | # except: 74 | # pass 75 | model.learn(total_timesteps=args.timesteps, log_interval=1) 76 | model.save("./log/{}".format(msg)) 77 | model.save_replay_buffer("./log/{}_rb".format(msg)) 78 | 79 | if args.test: 80 | model = eval(args.model).load("./log/{}".format(msg)) 81 | 82 | if args.recordResult: 83 | if os.path.exists('./log/{}_recordResult.txt'.format(msg)): 84 | f = open('./log/{}_recordResult.txt'.format(msg), 'r') 85 | for line in f: 86 | pass 87 | win = int(line.split()[0]) 88 | episode = int(line.split()[2]) 89 | f.close() 90 | else: 91 | win = 0 92 | episode = 0 93 | 94 | obs = env.reset() 95 | while True: 96 | action, _states = model.predict(obs) 97 | obs, rewards, dones, info = env.step(action) 98 | env.render() 99 | if dones == True: 100 | if rewards == 50: 101 | win += 1 102 | episode += 1 103 | if args.recordResult: 104 | f = open('./log/{}_recordResult.txt'.format(msg), 'a') 105 | f.write("{} / {}\n".format(win, episode)) 106 | f.close() 107 | print("Episode: {}\tacc: {}".format(episode, win / episode)) 108 | time.sleep(2) 109 | env.reset() 110 | -------------------------------------------------------------------------------- /doc/properties_of_harfang_df.md: -------------------------------------------------------------------------------- 1 | ## df.get_plane_state(self.planeID) 2 | 3 | Return a `dict` 4 | 5 | Example: 6 | 7 | { 8 | 'timestamp': 103981, 9 | 'timestep': 0.016666666666666666, 10 | 'position': [ 11 | 7.842685222625732, 12 | 19.442346572875977, 13 | 104.7667007446289 14 | ], 15 | 'Euler_angles': [ 16 | 0.0, 17 | -0.23409990966320038, 18 | 0.0 19 | ], 20 | 'easy_steering': True, 21 | 'health_level': 1, 22 | 'destroyed': False, 23 | 'wreck': False, 24 | 'crashed': False, 25 | 'active': True, 26 | 'type': 'AICRAFT', 27 | 'nationality': 1, 28 | 'thrust_level': 1, 29 | 'brake_level': 0, 30 | 'flaps_level': 0, 31 | 'horizontal_speed': 56.026527404785156, 32 | 'vertical_speed': -0.5931200385093689, 33 | 'linear_speed': 56.029666900634766, 34 | 'move_vector': [ 35 | -3.7959580421447754, 36 | -0.5931200385093689, 37 | 55.897789001464844 38 | ], 39 | 'linear_acceleration': 1.3036510467529325, 40 | 'altitude': 19.442346572875977, 41 | 'heading': 346.5870820059287, 42 | 'pitch_attitude': 0.0, 43 | 'roll_attitude': 0.0, 44 | 'post_combustion': False, 45 | 'user_pitch_level': 0.0, 46 | 'user_roll_level': 0.0, 47 | 'user_yaw_level': -1.0, 48 | 'gear': True, 49 | 'ia': False, 50 | 'autopilot': False, 51 | 'autopilot_heading': 0, 52 | 'autopilot_speed': -1, 53 | 'autopilot_altitude': 500, 54 | 'target_id': '', 55 | 'target_locked': False 56 | } 57 | 58 | ## df.get_missile_state(self.missileID) 59 | 60 | Return a `dict` 61 | 62 | Example: 63 | 64 | { 65 | 'timestamp': 485000, 66 | 'timestep': 0.016666666666666666, 67 | 'type': 'MISSILE', 68 | 'position': [ 69 | -11877.857421875, 70 | 1046.38427734375, 71 | 462.67791748046875 72 | ], 73 | 'Euler_angles': [ 74 | -0.01395590603351593, 75 | 1.195150375366211, 76 | 0.046285953372716904 77 | ], 78 | 'move_vector': [ 79 | 453.8008117675781, 80 | 8.936182975769043, 81 | 167.3896942138672 82 | ], 83 | 'destroyed': False, 84 | 'wreck': False, 85 | 'crashed': False, 86 | 'active': True, 87 | 'nationality': 2, 88 | 'altitude': 1046.38427734375, 89 | 'heading': 68.4770805117828, 90 | 'pitch_attitude': 0.8042992707191573, 91 | 'roll_attitude': 2.652989167383663, 92 | 'horizontal_speed': 483.68841552734375, 93 | 'vertical_speed': 8.936182975769043, 94 | 'linear_speed': 483.7709655761719, 95 | 'target_id': 'ally_2', 96 | 'life_delay': 30, 97 | 'life_time': 4.133333333333324 98 | } 99 | 100 | ## df.get_plane_state(self.planeID)['timestamp'] 101 | 102 | Returns an `int`, recording the timestamp elapsed since the simulation was started. 103 | 104 | ## df.get_plane_state(self.planeID)['position'] 105 | 106 | Return a `list[3]=[x, z, y]`, which is the position of plane *planeID*. The coordinate system is shown in the following figure. 107 | 108 | ![position](../images/coordinate_system.png) 109 | 110 | ## df.get_plane_state(self.planeID)['pitch_attitude'] 111 | 112 | Return a `float` (degree). The value is shown in the following figure. 113 | 114 | ![pitch_attitude](../images/pitch_attitude.png) 115 | 116 | ## df.get_missile_state(self.missileID)['pitch_attitude'] 117 | 118 | Same to `df.get_plane_state(self.planeID)['pitch_attitude']` except changing each $0 \degree$ into approximately $\pm 75 \degree$. Besides, it seems nonlinear. 119 | 120 | ## df.get_plane_state(self.planeID)['roll_attitude'] 121 | 122 | Return a `float` (degree). The value is shown in the following figure. **This is not a bijection.** 123 | 124 | ![pitch_attitude](../images/roll_attitude.png) 125 | 126 | ## df.get_missile_state(self.missileID)['roll_attitude'] 127 | 128 | Same to `df.get_plane_state(self.planeID)['roll_attitude']` except changing each $0 \degree$ into approximately $\pm 75 \degree$. Besides, it seems nonlinear. 129 | 130 | ## df.get_plane_state(self.planeID)['heading'] 131 | 132 | Return a `float` (degree). The value is shown in the following figure. 133 | 134 | ![pitch_attitude](../images/heading.png) 135 | 136 | ## df.get_missile_state(self.missileID)['heading'] 137 | 138 | Same to `df.get_plane_state(self.planeID)['heading']` 139 | 140 | ## df.get_plane_state(self.planeID)['horizontal_speed'] 141 | 142 | Return a `float`. This value is always positive. 143 | 144 | ## df.get_plane_state(self.planeID)['vertical_speed'] 145 | 146 | Return a `float`. 147 | 148 | ## df.get_plane_state(self.planeID)['linear_speed'] 149 | 150 | Return a `float`. This value equals to the geometric mean of 'horizontal_speed' and 'vertical_speed'. 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /example/maneuvering.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import argparse 4 | 5 | sys.path.append('.') 6 | 7 | from src.environments.dogfightEnv.dogfight_sandbox_hg2.network_client_example import dogfight_client as df 8 | 9 | def parse_args(): 10 | parser = argparse.ArgumentParser(description='TBD') 11 | parser.add_argument('--host', default='10.184.0.0', help='specifies Harfang host id') 12 | parser.add_argument('--port', default='50888', help='specifies Harfang port id') 13 | args = parser.parse_args() 14 | return args 15 | 16 | args = parse_args() 17 | 18 | df.connect(args.host, int(args.port)) 19 | 20 | planes = df.get_planes_list() 21 | 22 | for i in planes: 23 | df.reset_machine(i) 24 | 25 | df.set_plane_thrust(planes[1], 1) 26 | 27 | df.set_client_update_mode(True) 28 | 29 | # Thrust 30 | t = 0 31 | while t < 1: 32 | df.display_2DText([0.4, 0.7], "Thrust", 0.1, [1, 0.5, 0, 1]) 33 | time.sleep(1/60) 34 | plane_state = df.get_plane_state(planes[1]) 35 | df.update_scene() 36 | t = plane_state["thrust_level"] 37 | 38 | df.activate_post_combustion(planes[1]) 39 | 40 | df.set_plane_pitch(planes[1], -0.5) 41 | 42 | # Pitch 43 | p = 0 44 | while p < 15: 45 | df.display_2DText([0.4, 0.7], "Pitch", 0.1, [1, 0.5, 0, 1]) 46 | time.sleep(1/60) 47 | plane_state = df.get_plane_state(planes[1]) 48 | df.update_scene() 49 | p = plane_state["pitch_attitude"] 50 | 51 | df.stabilize_plane(planes[1]) 52 | 53 | df.retract_gear(planes[1]) 54 | 55 | 56 | s = 0 57 | while s < 500: 58 | time.sleep(1/60) 59 | plane_state = df.get_plane_state(planes[1]) 60 | df.update_scene() 61 | s = plane_state["altitude"] 62 | 63 | plane_id = planes[1] 64 | 65 | missiles = df.get_machine_missiles_list(plane_id) 66 | 67 | missile_slot = 0 68 | missile_id = missiles[missile_slot] 69 | 70 | 71 | df.fire_missile(plane_id, missile_slot) 72 | 73 | df.set_machine_custom_physics_mode(missile_id, True) 74 | 75 | df.set_missile_life_delay(missile_id, 10) 76 | 77 | df.update_scene() 78 | 79 | 80 | missile_state = df.get_missile_state(missile_id) 81 | x, y, z = missile_state["position"][0], missile_state["position"][1], missile_state["position"][2] 82 | y_b = y 83 | z_b = z 84 | v_0 = df.get_plane_state(planes[1])['linear_speed'] 85 | y_0 = df.get_plane_state(planes[1])['vertical_speed'] 86 | 87 | df.set_renderless_mode(False) 88 | 89 | 90 | missile_matrix = [1, 0, 0, 91 | 0, 1, 0, 92 | 0, 0, 1, 93 | x, y, z] 94 | 95 | # Linear displacement vector in m.s-1 96 | missile_speed_vector = [1, 1, 1] 97 | 98 | frame_time_step = 1/60 99 | 100 | # Custom missile movements 101 | t = 0 102 | while not missile_state["wreck"]: 103 | df.display_2DText([0.4, 0.7], "Flare", 0.1, [1, 0.5, 0, 1]) 104 | time.sleep(1/60) 105 | missile_state = df.get_missile_state(missile_id) 106 | missile_matrix[9] = x 107 | missile_matrix[10] = y 108 | missile_matrix[11] = z 109 | 110 | df.update_machine_kinetics(missile_id, missile_matrix, missile_speed_vector) 111 | df.update_scene() 112 | x = x 113 | y = y_b + y_0 * t - 0.5 * 10 * t * t 114 | z = z_b + v_0 * t 115 | 116 | 117 | # Compute speed vector, used by missile engine smoke 118 | missile_speed_vector = [(x-missile_matrix[9]) / frame_time_step, (y - missile_matrix[10]) / frame_time_step, (z - missile_matrix[11]) / frame_time_step] 119 | 120 | t += frame_time_step 121 | 122 | 123 | # Custom physics off 124 | df.set_machine_custom_physics_mode(missile_id, False) 125 | 126 | df.set_plane_roll(planes[1], .5) 127 | 128 | s = 0 129 | while s < 120: 130 | df.display_2DText([0.4, 0.7], "Roll", 0.1, [1, 0.5, 0, 1]) 131 | time.sleep(1/60) 132 | df.update_scene() 133 | s = s + 1 134 | 135 | df.set_plane_roll(planes[1], -.5) 136 | 137 | s = 0 138 | while s < 120: 139 | df.display_2DText([0.4, 0.7], "Roll", 0.1, [1, 0.5, 0, 1]) 140 | time.sleep(1/60) 141 | df.update_scene() 142 | s = s + 1 143 | 144 | 145 | df.stabilize_plane(planes[1]) 146 | 147 | s = 0 148 | while s < 120: 149 | time.sleep(1/60) 150 | df.update_scene() 151 | s = s + 1 152 | 153 | df.set_plane_yaw(planes[1], .5) 154 | s = 0 155 | while s < 120: 156 | df.display_2DText([0.4, 0.7], "Yaw", 0.1, [1, 0.5, 0, 1]) 157 | time.sleep(1/60) 158 | df.update_scene() 159 | s = s + 1 160 | 161 | df.set_plane_yaw(planes[1], -.5) 162 | s = 0 163 | while s < 120: 164 | df.display_2DText([0.4, 0.7], "Yaw", 0.1, [1, 0.5, 0, 1]) 165 | time.sleep(1/60) 166 | df.update_scene() 167 | s = s + 1 168 | 169 | df.stabilize_plane(planes[1]) 170 | 171 | while s < 120: 172 | time.sleep(1/60) 173 | df.update_scene() 174 | s = s + 1 175 | 176 | df.set_plane_flaps(planes[1], 1) 177 | s = 0 178 | while s < 120: 179 | df.display_2DText([0.4, 0.7], "Flaps", 0.1, [1, 0.5, 0, 1]) 180 | time.sleep(1/60) 181 | df.update_scene() 182 | s = s + 1 183 | 184 | 185 | df.set_plane_flaps(planes[1], -1) 186 | s = 0 187 | while s < 120: 188 | df.display_2DText([0.4, 0.7], "Flaps", 0.1, [1, 0.5, 0, 1]) 189 | time.sleep(1/60) 190 | df.update_scene() 191 | s = s + 1 192 | 193 | 194 | df.set_plane_brake(planes[1], 1) 195 | df.set_plane_thrust(planes[1], 0) 196 | s = 0 197 | while s < 120: 198 | df.display_2DText([0.4, 0.7], "Speed brake", 0.1, [1, 0.5, 0, 1]) 199 | time.sleep(1/60) 200 | df.update_scene() 201 | s = s + 1 202 | 203 | df.deploy_gear(planes[1]) 204 | 205 | 206 | # Client update mode OFF 207 | df.set_client_update_mode(False) 208 | 209 | # Disconnect from the Dogfight server 210 | df.disconnect() 211 | 212 | 213 | -------------------------------------------------------------------------------- /df2.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import time 4 | 5 | import gym 6 | import numpy as np 7 | from stable_baselines3 import DDPG, TD3, SAC 8 | from stable_baselines3.common.noise import (NormalActionNoise, 9 | OrnsteinUhlenbeckActionNoise) 10 | 11 | 12 | def parse_args(): 13 | parser = argparse.ArgumentParser(description='DBRL is an air combat simulation benchmark based on JSBSim and Dogfight 2. For more information, please visit https://github.com/mrwangyou/DBRL') 14 | parser.add_argument('--task', choices=['evade', 'dogfight'], required=True, help='specifies the simulation task') 15 | parser.add_argument('--host', default='10.184.0.0', help='specifies Harfang host id') 16 | parser.add_argument('--port', default='50888', help='specifies Harfang port id') 17 | parser.add_argument('--plane-slot', type=int, default=1, help='specifies the ego plane') 18 | parser.add_argument('--enemy-slot', type=int, default=3, help='specifies the enemy plane') 19 | parser.add_argument('--missile-slot', type=int, default=1, help='specifies the missile to escape from') 20 | parser.add_argument('--realtime', action='store_true', help='specifies to run in real world time while training. Only works when --trian is specified') 21 | parser.add_argument('--train', action='store_true', help='specifies the running mode of DBRL') 22 | parser.add_argument('--test', action='store_true', help='specifies the running mode of DBRL') 23 | parser.add_argument('--timesteps', type=int, default=10000000, help='specifies the training timesteps') 24 | parser.add_argument('--model', choices=['DDPG', 'TD3', 'SAC'], default='SAC', help='specifies the DRL model used in algorithm training') 25 | parser.add_argument('--initial-state', choices=['air', 'carrier'], default='carrier', help='') 26 | # parser.add_argument('--modelPath', default=None, help='specifies the pre-trained model. Only works when --train is specified') 27 | parser.add_argument('--record-result', action='store_true', help='specifies to record the evaluating result of DBRL. Only works when --test is specified') 28 | parser.add_argument('--record-status', type=int, default=0, help='specifies the recording period for aircraft status recording during test flights. Only works when --test is specified') 29 | parser.add_argument('--throttle-enable', action='store_true', help='specifies whether to enable the throttle control') 30 | parser.add_argument('--flare-enable', action='store_true', help='specifies whether to enable the decoy flare') 31 | parser.add_argument('--ego-pose-enable', action='store_true', help='') 32 | parser.add_argument('--oppo-pose-enable', action='store_true', help='') 33 | parser.add_argument('--missile-pose-enable', action='store_true', help='') 34 | parser.add_argument('--missile-relative-azimuth-enable', action='store_true', help='') 35 | args = parser.parse_args() 36 | return args 37 | 38 | args = parse_args() 39 | 40 | msg = "{}_{}_{}".format(args.task, args.model, args.timesteps) 41 | print(msg) 42 | 43 | env = gym.make( 44 | "DBRLDogfight-v0" if args.task == 'evade' else "DBRLDf-v0", 45 | host=args.host, 46 | port=args.port, 47 | plane_slot=args.plane_slot, 48 | enemy_slot=args.enemy_slot, 49 | missile_slot=args.missile_slot, 50 | # rendering=args.realtime, 51 | rendering=False, 52 | record_status=args.record_status, 53 | initial_state=args.initial_state, 54 | throttle_enable=args.throttle_enable, 55 | flare_enable=args.flare_enable, 56 | ego_pose_enable=True, 57 | oppo_pose_enable=False, 58 | missile_pose_enable=True, 59 | missile_relative_azimuth_enable=True, 60 | msg=msg 61 | ) 62 | 63 | n_actions = env.action_space.shape[-1] 64 | action_noise = NormalActionNoise( 65 | mean=np.zeros(n_actions), 66 | sigma=0.1 * np.ones(n_actions) 67 | ) 68 | 69 | model = eval(args.model)( 70 | "MlpPolicy", 71 | env, 72 | verbose=1, 73 | action_noise=action_noise, 74 | ) 75 | 76 | path = r'./log/' 77 | if not os.path.exists(path): 78 | os.mkdir(path) 79 | 80 | if args.train: 81 | # try: 82 | # model = eval(args.model).load("./log/{}_evade".format(method[args.model]), env) 83 | # model.load_replay_buffer("./log/{}_evade_rb".format(method[args.model])) 84 | # except: 85 | # pass 86 | model.learn(total_timesteps=args.timesteps, log_interval=1) 87 | model.save("./log/{}".format(msg)) 88 | model.save_replay_buffer("./log/{}_rb".format(msg)) 89 | 90 | if args.test: 91 | model = eval(args.model).load("./log/{}".format(msg)) 92 | 93 | if args.record_result and os.path.exists('./log/{}_record_result.txt'.format(msg)): 94 | f = open('./log/{}_record_result.txt'.format(msg), 'r') 95 | for line in f: 96 | pass 97 | win = int(line.split()[0]) 98 | episodes = int(line.split()[2]) 99 | f.close() 100 | else: 101 | win = 0 102 | episodes = 0 103 | 104 | obs = env.reset() 105 | while True: 106 | action, _states = model.predict(obs) 107 | obs, rewards, dones, info = env.step(action) 108 | env.render() 109 | if dones == True: 110 | if rewards == 50: 111 | win += 1 112 | episodes += 1 113 | if args.record_result: 114 | f = open('./log/{}_record_result.txt'.format(msg), 'a') 115 | f.write("{} / {}\n".format(win, episodes)) 116 | f.close() 117 | print("Episodes: {}\tacc: {}".format(episodes, win / episodes)) 118 | time.sleep(2) 119 | env.reset() 120 | -------------------------------------------------------------------------------- /src/environments/jsbsimEnv/jsbsimFdm.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import jsbsim 4 | 5 | 6 | class JsbsimFdm(): 7 | 8 | def __init__( 9 | self, 10 | fdm_id = 1, 11 | fdm_aircraft = 'f16', 12 | fdm_ic_v = 500, # Calibrated Velocity (knots) https://skybrary.aero/articles/calibrated-airspeed-cas 13 | fdm_ic_lat = 0, # Latitude (degree) 纬度 recommend .01 14 | fdm_ic_long = 0, # Longitude (degree) 经度 recommend .003 15 | fdm_ic_h = 20005.5, # Height above sea level (feet) 海拔 16 | fdm_ic_psi = 0, # Yaw 偏航角,绕Z轴,JSBSim将按照这里列出的顺序先后进行欧拉角计算 17 | fdm_ic_theta = 0, # Pitch 俯仰角,绕Y轴 18 | fdm_ic_phi = 0, # Roll 翻滚角,绕X轴 19 | 20 | fdm_hp = 1, 21 | fdm_fgfs = False, # Visualization 22 | flight_mode = 1, # 0 for flight test 23 | ) -> None: 24 | 25 | # FDM Initialization 空气动力学模型初始化 26 | self.fdm = jsbsim.FGFDMExec(None) 27 | 28 | # Aircraft Loading 加载飞机模型 29 | self.fdm.load_model(fdm_aircraft) 30 | 31 | # FlightGear Visualization 可视化 32 | if fdm_fgfs is True: 33 | self.fdm.set_output_directive('./data_output/flightgear{}.xml'.format(fdm_id)) 34 | 35 | # Velocity Initialization 速度初始化 36 | self.fdm['ic/vc-kts'] = fdm_ic_v 37 | 38 | # Position Initialization 位置初始化 39 | self.fdm["ic/lat-gc-deg"] = fdm_ic_lat 40 | self.fdm["ic/long-gc-deg"] = fdm_ic_long 41 | self.fdm["ic/h-sl-ft"] = fdm_ic_h 42 | 43 | # Attitude Initialization 姿态初始化 44 | self.fdm["ic/psi-true-deg"] = fdm_ic_psi 45 | self.fdm["ic/theta-deg"] = fdm_ic_theta 46 | self.fdm["ic/phi-deg"] = fdm_ic_phi 47 | 48 | ########################## 49 | ## Model Initialization ## 50 | ## 模型初始化 ## 51 | self.fdm.run_ic() ## 52 | ########################## 53 | 54 | # Turning on the Engine 启动引擎 55 | self.fdm["propulsion/starter_cmd"] = 1 56 | 57 | # Refueling 无限燃料 58 | if flight_mode == 0: 59 | self.fdm["propulsion/refuel"] = 1 60 | 61 | # First but not Initial 第一帧设置 62 | self.fdm.run() 63 | self.fdm["propulsion/active_engine"] = True 64 | self.fdm["propulsion/set-running"] = -1 65 | 66 | # Number of Frames 帧数 67 | self.nof = 1 68 | 69 | # Setting HP 生命值 70 | self.fdm_hp = fdm_hp 71 | 72 | # Getting parameters 记录参数 73 | self.param = { 74 | 'fdm_id' : fdm_id, 75 | 'fdm_aircraft' : fdm_aircraft, 76 | 'fdm_ic_v' : fdm_ic_v, 77 | 'fdm_ic_lat' : fdm_ic_lat, 78 | 'fdm_ic_long' : fdm_ic_long, 79 | 'fdm_ic_h' : fdm_ic_h, 80 | 'fdm_ic_psi' : fdm_ic_psi, 81 | 'fdm_ic_theta' : fdm_ic_theta, 82 | 'fdm_ic_phi' : fdm_ic_phi, 83 | 'fdm_hp' : fdm_hp, 84 | 'fdm_fgfs' : fdm_fgfs, 85 | 'flight_mode' : flight_mode, 86 | } 87 | 88 | def getProperty( 89 | self, 90 | prop, 91 | ) -> list: 92 | if prop == 'position': 93 | prop = [ 94 | "position/lat-gc-deg", # Latitude 纬度 95 | "position/long-gc-deg", # Longitude 经度 96 | "position/h-sl-ft", # Altitude above sea level(feet) 海拔(英尺) 97 | ] 98 | elif prop == 'positionEci': # Earth-centered inertial(feet) 地心惯性坐标系(英尺),与Ecef相比相对静止 99 | prop = [ 100 | "position/eci-x-ft", # 指向春分点 101 | "position/eci-y-ft", # 左手系决定 102 | "position/eci-z-ft", # 指向北极点 103 | ] 104 | elif prop == 'positionEcef': # Earth-centered, Earth-fixed coordinate system(feet) 地心地固坐标系(英尺) https://zhuanlan.zhihu.com/p/360744867 105 | prop = [ 106 | "position/ecef-x-ft", # 指向经纬度为0的点(本初子午线与赤道的交点) 107 | "position/ecef-y-ft", # 左手系决定 108 | "position/ecef-z-ft", # 指向北极点 109 | ] 110 | elif prop == 'attitudeRad': # Attitude(Rad) 姿态(弧度) 111 | prop = [ 112 | "attitude/psi-rad", # Yaw 偏航角 113 | "attitude/theta-rad", # Pitch 俯仰角 114 | "attitude/phi-rad", # Roll 翻滚角 115 | ] 116 | elif prop == 'attitudeDeg': # Attitude(Deg) 姿态(角度) 117 | prop = [ 118 | "attitude/psi-deg", # Yaw 偏航角 119 | "attitude/theta-deg", # Pitch 俯仰角 120 | "attitude/phi-deg", # Roll 翻滚角 121 | ] 122 | elif prop == 'pose': # Pose(deg) 位姿(角度) 123 | prop = [ 124 | "position/lat-gc-deg", 125 | "position/long-gc-deg", 126 | "position/h-sl-ft", 127 | "attitude/psi-deg", 128 | "attitude/theta-deg", 129 | "attitude/phi-deg", 130 | ] 131 | elif prop == 'velocity': # Velocity(fps) 速度(英尺每秒) 132 | prop = [ 133 | "velocities/v-north-fps", 134 | "velocities/v-east-fps", 135 | "velocities/v-down-fps", 136 | ] 137 | elif prop == 'acceleration': # Acceleration 加速度 138 | raise NotImplementedError('Hasn\'t finished yet!') 139 | prop = [ 140 | 141 | ] 142 | elif prop == 'all': # nominal 'all' 143 | prop = [ 144 | "position/lat-gc-deg", 145 | "position/long-gc-deg", 146 | "position/h-sl-ft", 147 | "attitude/psi-deg", 148 | "attitude/theta-deg", 149 | "attitude/phi-deg", 150 | "velocities/v-north-fps", 151 | "velocities/v-east-fps", 152 | "velocities/v-down-fps", 153 | ] 154 | else: 155 | return self.fdm[prop] 156 | 157 | return [ 158 | self.fdm[item] for item in prop 159 | ] 160 | 161 | def sendAction( 162 | self, 163 | action, # List of size [num_of_action] 164 | actionType=None, 165 | ): 166 | if actionType is None: 167 | action_space = [ 168 | "fcs/aileron-cmd-norm", # 副翼 169 | "fcs/elevator-cmd-norm", # 升降舵 170 | "fcs/rudder-cmd-norm", # 方向舵 171 | "fcs/throttle-cmd-norm", # 油门 172 | "fcs/flap-cmd-norm", # 襟翼 173 | "fcs/speedbrake-cmd-norm", # 减速板 174 | "fcs/spoiler-cmd-norm", # 扰流片 175 | ] 176 | for i in range(len(action_space)): 177 | self.fdm[action_space[i]] = action[i] 178 | else: 179 | self.fdm[actionType] = action 180 | 181 | def damage(self, value): 182 | self.fdm_hp = self.fdm_hp - value 183 | 184 | def step(self, playSpeed=0): 185 | self.nof = self.nof + 1 186 | self.fdm.run() 187 | 188 | if playSpeed != 0: 189 | time.sleep(self.fdm.get_delta_t() / playSpeed) 190 | 191 | def terminate(self): 192 | if self.fdm_hp <= 0: 193 | return 1 194 | else: 195 | return 0 196 | -------------------------------------------------------------------------------- /doc/QuickStart_zh.md: -------------------------------------------------------------------------------- 1 |
2 |

DBRL: A Gym Dogfighting Simulation Benchmark for Reinforcement Learning Research

3 | 4 |
5 | 6 | 7 | ##
快速入门
8 | 9 |
10 | 准备工作 11 | 12 | DBRL基于空气动力学软件JSBSimDogfight 2搭建空战仿真平台。 13 | 14 | 如需使用基于JSBSim搭建的视距内缠斗仿真环境,可以运行如下代码安装Python-JSBSim: 15 | 16 | ```bash 17 | pip install jsbsim 18 | ``` 19 | 20 | 安装的JSBSim可能不包括[JSBSim Github页面](https://github.com/JSBSim-Team/jsbsim)中的全部文件,建议您将如上链接中的文件拷贝进`JSBSim`源文件地址中。 21 | 22 | 这里只提供Dogfight 2环境在Windows系统下运行的示例,如果需要在Linux系统中运行Harfang3D Dog-Fight,可以下载Linux版Harfang。如需使用基于Dogfight 2搭建的导弹躲避仿真环境,可以在Windows PowerShell中运行如下代码进行软件下载与配置: 23 | 24 | ```bash 25 | wget -Uri https://github.com/harfang3d/dogfight-sandbox-hg2/releases/download/1.0.2/dogfight-sandbox-hg2-win64.7z -OutFile "df2.7z" 26 | 27 | 7z x -odf2/ df2.7z 28 | 29 | mv ./df2/dogfight-sandbox-hg2/ {DBRL}/src/environments/dogfightEnv/dogfight_sandbox_hg2/ 30 | 31 | del df2/, df2.7z 32 | ``` 33 | 其中,`{DBRL}`需要替换为本项目所在的路径,在命令行中使用7z指令需要将您的7zip安装位置添加至path系统变量,您同样可以手动进行文件下载,解压缩,重命名与移动操作。 34 | 35 | 36 | 37 | 38 | JSBSim的可视化功能基于FlightGear实现,如需在使用JSBSim的过程中将空战的飞行过程可视化显示,请在FlightGear官网中安装FlightGear软件。如需对缠斗中的两架飞机分别进行可视化,请在`{JSBSim}/data_output/`下复制两份`flightgear.xml`文件,分别命名为`flightgear{1/2}.xml`,并将`flightgear2.xml`第18行中的`5550`修改为`5551`。上述操作中的`{JSBSim}`为Python JSBSim库所在的路径;如需查看`JSBSim`源文件地址,可以运行如下代码: 39 | 40 | ```python 41 | import jsbsim 42 | 43 | print(jsbsim.get_default_root_dir()) 44 | ``` 45 | 46 | 在运行需要可视化的接战仿真中,请在FlightGear启动时使用如下参数: 47 | 48 | ```bash 49 | --fdm=null --native-fdm=socket,in,60,,5550,udp 50 | ``` 51 | > 如果您的系统语言非英语,且出现了FlightGear在操纵键盘后卡住的情况,您可以尝试使用其他输入法来解决这个问题。详情可见[论坛](https://forum.flightgear.org/viewtopic.php?f=22&t=40704)。 52 | 53 | 在运行同时对两架飞机可视化的仿真中,请分别使用 54 | ```bash 55 | --fdm=null --native-fdm=socket,in,60,,5550,udp --multiplay=out,10,127.0.0.1,5000 --multiplay=in,10,127.0.0.1,5001 --callsign=Test1 56 | ``` 57 | 与 58 | ```bash 59 | --fdm=null --native-fdm=socket,in,60,,5550,udp --multiplay=out,10,127.0.0.1,5001 --multiplay=in,10,127.0.0.1,5000 --callsign=Test2 60 | ``` 61 | 参数。 62 | 63 | 如需使用本项目中的强化学习模型`src/models/*.py`,请将本项目文件夹置于JSBSim目录下,即: 64 | 65 | ``` 66 | JSBSim/ 67 | ├── DBRL/ 68 | │ ├── doc/ 69 | │ ├── log/ 70 | │ ├── src/ 71 | │ ├── test/ 72 | │ └── ... 73 | ├── aircraft/ 74 | ├── scripts/ 75 | └── ... 76 | ``` 77 | 78 |
79 | 80 | 81 |
82 | 使用方法 83 | 84 | DBRL提供了OpenAI Gym格式的强化学习环境,可以通过Gym库进行调用。如需使用`gym.make`调用环境,请在`Gym/envs`下复制`src/environments/*Env/`文件夹,并在`Gym/envs/__init__.py`中添加如下代码: 85 | 86 | ```python 87 | register( 88 | id="DBRL{Jsbsim/Dogfight}-v0", 89 | entry_point="gym.envs.{jsbsim/dogfight}Env:{Jsbsim/Dogfight}Env", 90 | ) 91 | ``` 92 | 93 | 如需查看`gym`源文件地址,可以运行如下代码: 94 | 95 | ```bash 96 | pip show gym 97 | ``` 98 | 99 | 调用环境时可以采用如下代码: 100 | 101 | ```python 102 | import gym 103 | 104 | env = gym.make('DBRL{Jsbsim/Dogfight}-v0') 105 | ``` 106 | 107 | 114 | 115 | 116 | 117 |
118 | 119 | 120 |
121 | 环境特征 122 | 123 | DBRL-JSBSim提供了一个基于强化学习框架Gym的智能空战仿真环境,它的动作空间为: 124 | 125 | ```python 126 | gym.spaces.Box( 127 | low=np.array([-1, -1, -1, 0, 0, 0, 0]), 128 | high=np.array([1, 1, 1, 1, 1, 1, 1]) 129 | ) 130 | ``` 131 | 其中六个维度分别表示对副翼(Aileron)、升降舵(Elevator)、方向舵(Rudder)、油门(Throttle)、襟翼(Flap)、减速板(Speed brake)、扰流片(Spoiler)的操控。 132 | 133 | 状态空间为: 134 | 135 | ```python 136 | gym.spaces.Box( 137 | low=np.array([-360, -360, 0, -360, -360, -360] * 2), 138 | high=np.array([360, 360, 60000, 360, 360, 360] * 2) 139 | ) 140 | ``` 141 | 其中六个维度分别表示飞机的纬度(Latitude)、经度(Longitude)、海拔(Height above sea level)、偏航角(Yaw)、俯仰角(Pitch)、滚转角(Roll),单位为度(Degree)与英尺(Feet)。 142 | 143 | 作为一场接战环境的初始化,`class JSBSimEnv()`接受两个`class JSBSimFdm()`作为输入变量: 144 | 145 | ```python 146 | class JsbsimEnv(Env): 147 | 148 | def __init__( 149 | self, 150 | fdm1=Fdm(fdm_id=1), 151 | fdm2=Fdm(fdm_id=2), 152 | ) -> None: 153 | ``` 154 | 155 | JSBSimFdm类储存在`DBRL/src/environments/jsbsimEnv/jsbsimFdm.py`中,它的构造函数接受如下参数作为输入: 156 | 157 | ```python 158 | class JsbsimFdm(): 159 | 160 | def __init__( 161 | self, 162 | fdm_id = 1, 163 | fdm_aircraft = 'f16', 164 | fdm_ic_v = 500, 165 | fdm_ic_lat = 0, 166 | fdm_ic_long = 0, 167 | fdm_ic_h = 20005.5, 168 | fdm_ic_psi = 0, 169 | fdm_ic_theta = 0, 170 | fdm_ic_phi = 0, 171 | fdm_hp = 1, 172 | fdm_fgfs = False, 173 | flight_mode = 1 174 | ) -> None: 175 | ``` 176 | 其中:`fdm_id`表示接战中飞机的编号,需要分别设置为`1`与`2`;`fdm_aircraft`表示接战中飞机的型号;`fdm_ic_*`表示飞机的初始化特征;`fdm_ic_v`表示飞机的初始校正空速,单位为节(knot);`fdm_ic_lat`表示飞机的初始纬度;`fdm_ic_long`表示飞机的初始经度;`fdm_ic_h`表示飞机的初始海拔高度,单位为英尺;`fdm_ic_psi`、`fdm_ic_theta`、`fdm_ic_phi`分别表示飞机的偏航角,俯仰角与滚转角;`fdm_hp`表示飞机的初始血量;`fdm_fgfs`表示飞机是否需要在FlightGear上进行可视化;`flight_mode`表示飞行模式(暂无效果)。 177 | 178 | > JSBSim将按照此处列出的三个欧拉角的先后顺序进行飞机姿态的计算,即,先设定飞机的偏航角,再在此基础上,依照参数设定飞机的俯仰角与滚转角。 179 | 180 | DBRL提供的回报函数为当前帧对敌机造成的血量损失大小与敌机对自身造成伤害的差值,但也同样可以用JSBSimFdm类的`getProperty()`函数获得一些其他特征作为不同强化学习模型的回报函数。 181 | 182 | ```python 183 | def getProperty( 184 | self, 185 | prop, 186 | ) -> list: 187 | ``` 188 | 189 | --- 190 | --- 191 | 192 | 193 | DBRL-Dogfight提供了一个基于强化学习框架Gym的战斗机机动躲避智能决策仿真环境,作为环境的初始状态,智能体需要操控的飞机被另一架飞机发射的“流星”空空导弹锁定,智能体需要采取机动操纵躲避导弹的威胁。 194 | 195 | Dogfight 2环境提供可选的动作空间包括: 196 | 197 | ```python 198 | if pitch_enable: 199 | action_infimum = np.append(action_infimum, -1) 200 | action_supermum = np.append(action_supermum, 1) 201 | if roll_enable: 202 | action_infimum = np.append(action_infimum, -1) 203 | action_supermum = np.append(action_supermum, 1) 204 | if yaw_enable: 205 | action_infimum = np.append(action_infimum, -1) 206 | action_supermum = np.append(action_supermum, 1) 207 | if flaps_enable: 208 | action_infimum = np.append(action_infimum, 0) 209 | action_supermum = np.append(action_supermum, 1) 210 | if throttle_enable: 211 | action_infimum = np.append(action_infimum, 0) 212 | action_supermum = np.append(action_supermum, 1) 213 | if flare_enable: 214 | action_infimum = np.append(action_infimum, 0) 215 | action_supermum = np.append(action_supermum, 1) 216 | action_space = Box( 217 | low=action_infimum, 218 | high=action_supermum, 219 | ) 220 | ``` 221 | 其中六个维度分别表示对俯仰角(Pitch)、滚转角(Roll)、偏航角(Yaw)、襟翼(Flaps)、油门(Throttle)、干扰弹(Decoy flare)的操控。 222 | 223 | 可选状态空间包括: 224 | 225 | ```python 226 | if ego_plane_position: 227 | observation_infimum = np.append(observation_infimum, [-300, -300, -1]) 228 | observation_supermum = np.append(observation_supermum, [300, 300, 200]) 229 | if ego_plane_attitude: 230 | observation_infimum = np.append(observation_infimum, [0, -360, -360]) 231 | observation_supermum = np.append(observation_supermum, [360, 360, 360]) 232 | if oppo_plane_position: 233 | observation_infimum = np.append(observation_infimum, [-300, -300, -1]) 234 | observation_supermum = np.append(observation_supermum, [300, 300, 200]) 235 | if oppo_plane_attitude: 236 | observation_infimum = np.append(observation_infimum, [0, -360, -360]) 237 | observation_supermum = np.append(observation_supermum, [360, 360, 360]) 238 | if missile_position: 239 | observation_infimum = np.append(observation_infimum, [-300, -300, -1]) 240 | observation_supermum = np.append(observation_supermum, [300, 300, 200]) 241 | if missile_attitude: 242 | observation_infimum = np.append(observation_infimum, [-315, -315, -315]) 243 | observation_supermum = np.append(observation_supermum, [315, 315, 315]) 244 | if missile_relative_azimuth: 245 | observation_infimum = np.append(observation_infimum, [-1, -1, -1]) 246 | observation_supermum = np.append(observation_supermum, [1, 1, 1]) 247 | observation_space = Box( 248 | low=observation_infimum, 249 | high=observation_supermum, 250 | ) 251 | ``` 252 | 可选飞机的X坐标(÷100)、Y坐标(÷100)、Z坐标(÷50)、偏航角、俯仰角(×4)、滚转角(×4),导弹的X坐标(÷100)、Y坐标(÷100)、Z坐标(÷50)、偏航角(×100)、俯仰角(×100)、滚转角(×100),导弹相对于飞机的方位角等。 253 | 254 | DogfightEnv的初始化需要与Dogfight 2软件进行连接,接受如下参数作为输入变量: 255 | 256 | ```python 257 | class DogfightEnv(Env): 258 | 259 | def __init__( 260 | self, 261 | host='10.184.0.0', 262 | port='50888', 263 | plane_slot=1, 264 | enemy_slot=3, 265 | missile_slot=1, 266 | rendering=False, 267 | record_status=0, 268 | initial_state='carrier', 269 | throttle_enable=False, 270 | flare_enable=False, 271 | ego_pose_enable=True, 272 | oppo_pose_enable=False, 273 | missile_pose_enable=True, 274 | missile_relative_azimuth_enable=False, 275 | msg=None, 276 | ) -> None: 277 | ``` 278 | 279 | 在使用DogfightEnv环境前,需要先启动Dogfight 2软件,选择Network Mode,并将软件左上角显示的Host与Port作为参数传进环境中。 280 | 281 | 282 |
283 | 284 | 285 | 294 | 295 | 296 | ##
欢迎交流
297 | 298 | 如果您在使用过程中发现了任何错误,或者有任何需求与改进的建议,欢迎在Github Issues中提出,谢谢! 299 | -------------------------------------------------------------------------------- /tutorial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### Tutorial of dogfight simulation algorithm training and testing" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "#### Clone GitHub repository and install dependencies." 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": { 21 | "vscode": { 22 | "languageId": "powershell" 23 | } 24 | }, 25 | "outputs": [], 26 | "source": [ 27 | "pip install jsbsim" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 1, 33 | "metadata": {}, 34 | "outputs": [ 35 | { 36 | "name": "stdout", 37 | "output_type": "stream", 38 | "text": [ 39 | "c:\\Users\\mrwan\\.conda\\envs\\jsbsim\\share\\JSBSim\n" 40 | ] 41 | } 42 | ], 43 | "source": [ 44 | "import jsbsim\n", 45 | "\n", 46 | "print(jsbsim.get_default_root_dir())" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": null, 52 | "metadata": { 53 | "vscode": { 54 | "languageId": "powershell" 55 | } 56 | }, 57 | "outputs": [], 58 | "source": [ 59 | "cd c:/Users/mrwan/.conda/envs/jsbsim/share\n", 60 | "\n", 61 | "git clone git@github.com:JSBSim-Team/jsbsim.git\n", 62 | "\n", 63 | "cd jsbsim\n", 64 | "\n", 65 | "# Skip the next line of code if you've already cloned DBRL in Dogfight2 environment\n", 66 | "git clone https://github.com/mrwangyou/DBRL.git\n", 67 | "\n", 68 | "cd DBRL" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": {}, 74 | "source": [ 75 | "#### Download FlightGear" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "metadata": {}, 81 | "source": [ 82 | "If you are using Windows, please download [FlightGear for Windows](https://sourceforge.net/projects/flightgear/files/release-2020.3/FlightGear-2020.3.13.exe/download). If you are using Linux x86 systems, download [AppImage binary release](https://sourceforge.net/projects/flightgear/files/release-2020.3/FlightGear-2020.3.13-x86_64.AppImage/download).\n", 83 | "\n", 84 | "Run two FlightGear separately with the attributes below\n", 85 | "\n", 86 | "```bash\n", 87 | "--fdm=null --native-fdm=socket,in,60,,5550,udp --multiplay=out,10,127.0.0.1,5000 --multiplay=in,10,127.0.0.1,5001 --callsign=Test1\n", 88 | "\n", 89 | "--fdm=null --native-fdm=socket,in,60,,5550,udp --multiplay=out,10,127.0.0.1,5001 --multiplay=in,10,127.0.0.1,5000 --callsign=Test2\n", 90 | "```\n", 91 | "\n", 92 | "![FlightGear attribute](images/fgattribute.png)" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "#### Register JSBSimEnv in Gym" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 2, 105 | "metadata": { 106 | "vscode": { 107 | "languageId": "powershell" 108 | } 109 | }, 110 | "outputs": [ 111 | { 112 | "name": "stdout", 113 | "output_type": "stream", 114 | "text": [ 115 | "Name: gym\n", 116 | "Version: 0.21.0\n", 117 | "Summary: Gym: A universal API for reinforcement learning environments.\n", 118 | "Home-page: https://github.com/openai/gym\n", 119 | "Author: OpenAI\n", 120 | "Author-email: jkterry@umd.edu\n", 121 | "License: \n", 122 | "Location: c:\\users\\mrwan\\.conda\\envs\\jsbsim\\lib\\site-packages\n", 123 | "Requires: cloudpickle, numpy\n", 124 | "Required-by: stable-baselines3\n", 125 | "Note: you may need to restart the kernel to use updated packages.\n" 126 | ] 127 | } 128 | ], 129 | "source": [ 130 | "pip show gym" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": null, 136 | "metadata": { 137 | "vscode": { 138 | "languageId": "powershell" 139 | } 140 | }, 141 | "outputs": [], 142 | "source": [ 143 | "# Replace the path with your own Gym directory\n", 144 | "cp ./src/environments/jsbsimEnv/ c:/users/mrwan/.conda/envs/jsbsim/lib/site-packages/gym/envs/ -r" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "Add the following code to `{GYM}/envs/__init__.py`.\n", 152 | "\n", 153 | "```python\n", 154 | "register(\n", 155 | " id=\"DBRLJsbsim-v0\",\n", 156 | " entry_point=\"gym.envs.jsbsimEnv:JsbsimEnv\",\n", 157 | ")\n", 158 | "```" 159 | ] 160 | }, 161 | { 162 | "cell_type": "markdown", 163 | "metadata": {}, 164 | "source": [ 165 | "#### Start to train or test the dogfighting algorithm" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "metadata": { 172 | "vscode": { 173 | "languageId": "powershell" 174 | } 175 | }, 176 | "outputs": [], 177 | "source": [ 178 | "python ./src/models/sac_jsbsim.py --train --test --timesteps 1000000" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "---" 186 | ] 187 | }, 188 | { 189 | "cell_type": "markdown", 190 | "metadata": {}, 191 | "source": [ 192 | "### Tutorial of missile-evading algorithm training and testing" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "metadata": {}, 198 | "source": [ 199 | "#### Install dependencies." 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "metadata": { 206 | "vscode": { 207 | "languageId": "powershell" 208 | } 209 | }, 210 | "outputs": [], 211 | "source": [ 212 | "cd jsbsim\n", 213 | "\n", 214 | "# Skip the next line of code if you've already cloned DBRL in JSBSim environment\n", 215 | "git clone https://github.com/mrwangyou/DBRL.git\n", 216 | "\n", 217 | "cd DBRL\n", 218 | "\n", 219 | "wget -Uri https://github.com/harfang3d/dogfight-sandbox-hg2/releases/download/1.0.2/dogfight-sandbox-hg2-win64.7z -OutFile \"df2.7z\"\n", 220 | "7z x -odf2/ df2.7z\n", 221 | "mv ./df2/dogfight-sandbox-hg2/ ./src/environments/dogfightEnv/dogfight_sandbox_hg2/\n", 222 | "del df2/, df2.7z" 223 | ] 224 | }, 225 | { 226 | "cell_type": "markdown", 227 | "metadata": {}, 228 | "source": [ 229 | "#### Register DogfightEnv in Gym." 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": 3, 235 | "metadata": { 236 | "vscode": { 237 | "languageId": "powershell" 238 | } 239 | }, 240 | "outputs": [ 241 | { 242 | "name": "stdout", 243 | "output_type": "stream", 244 | "text": [ 245 | "Name: gym\n", 246 | "Version: 0.21.0\n", 247 | "Summary: Gym: A universal API for reinforcement learning environments.\n", 248 | "Home-page: https://github.com/openai/gym\n", 249 | "Author: OpenAI\n", 250 | "Author-email: jkterry@umd.edu\n", 251 | "License: \n", 252 | "Location: c:\\users\\mrwan\\.conda\\envs\\jsbsim\\lib\\site-packages\n", 253 | "Requires: cloudpickle, numpy\n", 254 | "Required-by: stable-baselines3\n", 255 | "Note: you may need to restart the kernel to use updated packages.\n" 256 | ] 257 | } 258 | ], 259 | "source": [ 260 | "pip show gym" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": null, 266 | "metadata": { 267 | "vscode": { 268 | "languageId": "powershell" 269 | } 270 | }, 271 | "outputs": [], 272 | "source": [ 273 | "# Replace the path with your own Gym directory\n", 274 | "cp ./src/environments/dogfightEnv/ c:/users/mrwan/.conda/envs/jsbsim/lib/site-packages/gym/envs/ -r" 275 | ] 276 | }, 277 | { 278 | "cell_type": "markdown", 279 | "metadata": {}, 280 | "source": [ 281 | "Add the following code to `{GYM}/envs/__init__.py`.\n", 282 | "\n", 283 | "```python\n", 284 | "register(\n", 285 | " id=\"DBRLDogfight-v0\",\n", 286 | " entry_point=\"gym.envs.dogfightEnv:DogfightEnv\",\n", 287 | ")\n", 288 | "```" 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "metadata": {}, 294 | "source": [ 295 | "#### Run df2" 296 | ] 297 | }, 298 | { 299 | "cell_type": "markdown", 300 | "metadata": {}, 301 | "source": [ 302 | "Run `evade.bat` in `./src/environments/dogfightEnv/` for missile evading mode.\n", 303 | "\n", 304 | "Run `start.bat` in `dogfight_sandbox_hg2/`, choose \"Network Mode\", and press space. The host and port IP are in the upper left corner of the screen.\n", 305 | "\n", 306 | "![Host and port figure](images/host.png)" 307 | ] 308 | }, 309 | { 310 | "cell_type": "markdown", 311 | "metadata": {}, 312 | "source": [ 313 | "#### Start to train or test the evading algorithm." 314 | ] 315 | }, 316 | { 317 | "cell_type": "code", 318 | "execution_count": null, 319 | "metadata": { 320 | "vscode": { 321 | "languageId": "powershell" 322 | } 323 | }, 324 | "outputs": [], 325 | "source": [ 326 | "# Replace the parameters with your own host and port\n", 327 | "python ./df2.py --host 192.168.239.1 --port 50888 --train --test --timesteps 50000 --model SAC --initial-state air --flare-enable --ego-pose-enable --missile-pose-enable" 328 | ] 329 | } 330 | ], 331 | "metadata": { 332 | "kernelspec": { 333 | "display_name": "Python 3.9.7 ('jsbsim')", 334 | "language": "python", 335 | "name": "python3" 336 | }, 337 | "language_info": { 338 | "codemirror_mode": { 339 | "name": "ipython", 340 | "version": 3 341 | }, 342 | "file_extension": ".py", 343 | "mimetype": "text/x-python", 344 | "name": "python", 345 | "nbconvert_exporter": "python", 346 | "pygments_lexer": "ipython3", 347 | "version": "3.9.16" 348 | }, 349 | "orig_nbformat": 4, 350 | "vscode": { 351 | "interpreter": { 352 | "hash": "663e50e37becc1d53f39f342eec15d3c22c0771839e1c61f0c2308195e9fefc0" 353 | } 354 | } 355 | }, 356 | "nbformat": 4, 357 | "nbformat_minor": 2 358 | } 359 | -------------------------------------------------------------------------------- /src/environments/jsbsimEnv/jsbsimEnv.py: -------------------------------------------------------------------------------- 1 | import random 2 | import re 3 | import sys 4 | import time 5 | 6 | import gym 7 | import numpy as np 8 | from gym import Env 9 | from gym.spaces import Box, Discrete 10 | 11 | sys.path.append('./src/environments/jsbsimEnv') 12 | try: 13 | from gym.envs.jsbsimEnv.jsbsimFdm import JsbsimFdm as Fdm 14 | print("Gym") 15 | time.sleep(2) 16 | except: 17 | from jsbsimFdm import JsbsimFdm as Fdm 18 | print("DBRL") 19 | time.sleep(2) 20 | 21 | 22 | class JsbsimEnv(Env): 23 | 24 | def __init__( 25 | self, 26 | fdm1=Fdm( 27 | fdm_id=1, 28 | fdm_fgfs=True 29 | ), 30 | fdm2=Fdm( 31 | fdm_id=2, 32 | fdm_ic_lat=.003, 33 | fdm_ic_psi=0, 34 | fdm_fgfs=True 35 | ), 36 | policy2='Level', 37 | ) -> None: 38 | super(JsbsimEnv, self).__init__() 39 | 40 | self.fdm1 = fdm1 41 | self.fdm2 = fdm2 42 | 43 | self.param1 = self.fdm1.param 44 | self.param2 = self.fdm2.param 45 | 46 | self.policy2 = policy2 47 | 48 | self.action_space = Box( 49 | low=np.array([ 50 | -1, # Aileron 副翼 51 | -1, # Elevator 升降舵 52 | -1, # Rudder 方向舵 53 | 0, # Throttle 油门 54 | 0, # Flap 襟翼 55 | 0, # Speed brake 减速板 56 | 0, # Spoiler 扰流片 57 | ]), 58 | high=np.array([ 59 | 1, 60 | 1, 61 | 1, 62 | 1, 63 | 1, 64 | 1, 65 | 1, 66 | ]), 67 | ) 68 | 69 | self.observation_space = Box( 70 | low=np.array([ 71 | -500, # Latitude 纬度 72 | -500, # Longitude 经度 73 | -500, # Height above sea level 海拔 74 | 0, # Yaw 偏航角 75 | -180, # Pitch 俯仰角 76 | -180, # Roll 翻滚角 77 | ] * 2), 78 | high=np.array([ 79 | 500, 80 | 500, 81 | 500, 82 | 360, 83 | 180, 84 | 180, 85 | ] * 2), 86 | dtype=np.float64 87 | ) 88 | 89 | def getDistanceVector(self, ego): 90 | positionEci1 = self.fdm1.getProperty("positionEci") # A list of size [3] 91 | positionEci2 = self.fdm2.getProperty("positionEci") # A list of size [3] 92 | if ego == 1: 93 | return np.array(positionEci2) - np.array(positionEci1) 94 | elif ego == 2: 95 | return np.array(positionEci1) - np.array(positionEci2) 96 | else: 97 | raise Exception("Plane {} doesn\'t exist".format(ego)) 98 | 99 | def getDistance(self): 100 | assert abs(np.linalg.norm(self.getDistanceVector(1)) - np.linalg.norm(self.getDistanceVector(2))) < 1e-5 101 | return np.linalg.norm(self.getDistanceVector(1)) 102 | 103 | def getDamage(self, ego): 104 | attitude1 = self.fdm1.getProperty("attitudeRad") # A list of size [3] 105 | attitude2 = self.fdm2.getProperty("attitudeRad") # A list of size [3] 106 | 107 | theta_1 = np.pi / 2 - attitude1[1] 108 | psi_1 = np.pi / 2 - attitude1[0] 109 | heading_1 = np.array([ 110 | np.cos(theta_1), 111 | np.sin(theta_1) * np.cos(psi_1), 112 | np.sin(theta_1) * np.sin(psi_1), 113 | ]) 114 | 115 | theta_2 = np.pi / 2 - attitude2[1] 116 | psi_2 = np.pi / 2 - attitude2[0] 117 | heading_2 = np.array([ 118 | np.cos(theta_2), 119 | np.sin(theta_2) * np.cos(psi_2), 120 | np.sin(theta_2) * np.sin(psi_2), 121 | ]) 122 | 123 | if 500 <= self.getDistance() <= 3000: 124 | 125 | angle1 = np.arccos( 126 | np.dot(self.getDistanceVector(ego=1), heading_1) / 127 | (self.getDistance() * np.linalg.norm(heading_1)) 128 | ) 129 | 130 | if -1 <= angle1 / np.pi * 180 <= 1: 131 | if ego == 2: 132 | return (3000 - self.getDistance()) / 2500 / 120 133 | 134 | angle2 = np.arccos( 135 | np.dot(self.getDistanceVector(ego=2), heading_2) / 136 | (self.getDistance() * np.linalg.norm(heading_2)) 137 | ) 138 | 139 | if -1 <= angle2 / np.pi * 180 <= 1: 140 | if ego == 1: 141 | return (3000 - self.getDistance()) / 2500 / 120 142 | 143 | return 0 144 | 145 | def damage(self): 146 | self.fdm1.damage(self.getDamage(1)) 147 | self.fdm2.damage(self.getDamage(2)) 148 | 149 | def reward(self): 150 | 151 | # sparse reward 152 | if self.terminate() == 2: 153 | r = -10 154 | elif self.terminate() == 1: 155 | r = 10 156 | elif self.terminate() == -1: 157 | r = -0.001 158 | else: 159 | r = -0.001 + (self.getDamage(2) - self.getDamage(1)) * 100 160 | 161 | return r 162 | 163 | def terminate(self): 164 | 165 | if self.fdm1.getProperty('position')[2] <= 10000: # Height protection 166 | return 2 167 | 168 | if self.fdm2.getProperty('position')[2] <= 10000: 169 | return 1 170 | 171 | if self.fdm1.nof >= 2000: # Tied 172 | return -1 173 | 174 | if self.fdm1.fdm_hp <= 0 and self.fdm2.fdm_hp > 0: 175 | return 2 176 | elif self.fdm2.fdm_hp <= 0 and self.fdm1.fdm_hp > 0: 177 | return 1 178 | elif self.fdm1.fdm_hp <= 0 and self.fdm2.fdm_hp <= 0: 179 | return -1 180 | else: 181 | return 0 182 | 183 | def step(self, action): 184 | 185 | self.fdm1.sendAction(action) 186 | 187 | if self.policy2 == 'Level': 188 | self.fdm2.sendAction( # level flight 189 | action=-np.tanh((0 - self.fdm2.getProperty('attitudeDeg')[1]) * 1.5), 190 | actionType='fcs/elevator-cmd-norm' 191 | ) 192 | elif self.policy2 == 'Random': 193 | self.fdm2.sendAction( 194 | self.action_space.sample() 195 | ) 196 | 197 | self.damage() 198 | 199 | self.fdm1.step() 200 | self.fdm2.step() 201 | 202 | terminate = True if self.terminate() else False 203 | 204 | ob = np.array([ # ~100 205 | (self.fdm1.getProperty('position')[0] - self.fdm1.param['fdm_ic_lat']) * 1e4, 206 | (self.fdm1.getProperty('position')[1] - self.fdm1.param['fdm_ic_long']) * 1e4, 207 | (self.fdm1.getProperty('position')[2] - self.fdm1.param['fdm_ic_h']) / 10, 208 | (self.fdm1.getProperty('attitudeDeg')[0] - self.fdm1.param['fdm_ic_psi']), 209 | (self.fdm1.getProperty('attitudeDeg')[1] - self.fdm1.param['fdm_ic_theta']), 210 | (self.fdm1.getProperty('attitudeDeg')[2] - self.fdm1.param['fdm_ic_phi']), 211 | (self.fdm2.getProperty('position')[0] - self.fdm2.param['fdm_ic_lat']) * 1e4, 212 | (self.fdm2.getProperty('position')[1] - self.fdm2.param['fdm_ic_long']) * 1e4, 213 | (self.fdm2.getProperty('position')[2] - self.fdm2.param['fdm_ic_h']) / 10, 214 | (self.fdm2.getProperty('attitudeDeg')[0] - self.fdm2.param['fdm_ic_psi']), 215 | (self.fdm2.getProperty('attitudeDeg')[1] - self.fdm2.param['fdm_ic_theta']), 216 | (self.fdm2.getProperty('attitudeDeg')[2] - self.fdm2.param['fdm_ic_phi']), 217 | ]) 218 | 219 | return ob, self.reward(), terminate, {} 220 | 221 | def render(self, id=0): 222 | 223 | if id == 1 or id == 0: 224 | self.fdm1.set_output_directive('./data_output/flightgear1.xml') 225 | 226 | if id == 2 or id == 0: 227 | self.fdm2.set_output_directive('./data_output/flightgear2.xml') 228 | 229 | def reset(self): 230 | 231 | self.fdm1 = Fdm( 232 | fdm_id=self.param1['fdm_id'], 233 | fdm_aircraft=self.param1['fdm_aircraft'], 234 | fdm_ic_v=self.param1['fdm_ic_v'], 235 | fdm_ic_lat=self.param1['fdm_ic_lat'], 236 | fdm_ic_long=self.param1['fdm_ic_long'], 237 | fdm_ic_h=self.param1['fdm_ic_h'], 238 | fdm_ic_psi=self.param1['fdm_ic_psi'], 239 | fdm_ic_theta=self.param1['fdm_ic_theta'], 240 | fdm_ic_phi=self.param1['fdm_ic_phi'], 241 | fdm_hp=self.param1['fdm_hp'], 242 | fdm_fgfs=self.param1['fdm_fgfs'], 243 | flight_mode=self.param1['flight_mode'], 244 | ) 245 | 246 | self.fdm2 = Fdm( 247 | fdm_id=self.param2['fdm_id'], 248 | fdm_aircraft=self.param2['fdm_aircraft'], 249 | fdm_ic_v=self.param2['fdm_ic_v'], 250 | fdm_ic_lat=self.param2['fdm_ic_lat'], 251 | fdm_ic_long=self.param2['fdm_ic_long'], 252 | fdm_ic_h=self.param2['fdm_ic_h'], 253 | fdm_ic_psi=self.param2['fdm_ic_psi'], 254 | fdm_ic_theta=self.param2['fdm_ic_theta'], 255 | fdm_ic_phi=self.param2['fdm_ic_phi'], 256 | fdm_hp=self.param2['fdm_hp'], 257 | fdm_fgfs=self.param2['fdm_fgfs'], 258 | flight_mode=self.param2['flight_mode'], 259 | ) 260 | 261 | ob = np.array([ # ~100 262 | (self.fdm1.getProperty('position')[0] - self.fdm1.param['fdm_ic_lat']) * 1e4, 263 | (self.fdm1.getProperty('position')[1] - self.fdm1.param['fdm_ic_long']) * 1e4, 264 | (self.fdm1.getProperty('position')[2] - self.fdm1.param['fdm_ic_h']) / 10, 265 | (self.fdm1.getProperty('attitudeDeg')[0] - self.fdm1.param['fdm_ic_psi']), 266 | (self.fdm1.getProperty('attitudeDeg')[1] - self.fdm1.param['fdm_ic_theta']), 267 | (self.fdm1.getProperty('attitudeDeg')[2] - self.fdm1.param['fdm_ic_phi']), 268 | (self.fdm2.getProperty('position')[0] - self.fdm2.param['fdm_ic_lat']) * 1e4, 269 | (self.fdm2.getProperty('position')[1] - self.fdm2.param['fdm_ic_long']) * 1e4, 270 | (self.fdm2.getProperty('position')[2] - self.fdm2.param['fdm_ic_h']) / 10, 271 | (self.fdm2.getProperty('attitudeDeg')[0] - self.fdm2.param['fdm_ic_psi']), 272 | (self.fdm2.getProperty('attitudeDeg')[1] - self.fdm2.param['fdm_ic_theta']), 273 | (self.fdm2.getProperty('attitudeDeg')[2] - self.fdm2.param['fdm_ic_phi']), 274 | ]) 275 | 276 | return ob 277 | 278 | -------------------------------------------------------------------------------- /doc/QuickStart_en.md: -------------------------------------------------------------------------------- 1 |
2 |

DBRL: A Gym Dogfighting Simulation Benchmark for Reinforcement Learning Research

3 | 4 |
5 | 6 | 7 | ##
Quick Start
8 | 9 |
10 | Install 11 | 12 | DBRL is an air combat simulation benchmark based on JSBSim and Dogfight 2. 13 | 14 | If you want to run with JSBSim dogfight environment, please run the code below to install JSBSim. 15 | 16 | ```bash 17 | pip install jsbsim 18 | ``` 19 | 20 | Dogfight 2 environment could only run in Windows OS. If you want to run reinforcement learning methods with Dogfight 2 missile-avoiding environment, please run the code below in Windows PowerShell. 21 | 22 | ```bash 23 | wget -Uri https://github.com/harfang3d/dogfight-sandbox-hg2/releases/download/1.0.2/dogfight-sandbox-hg2-win64.7z -OutFile "df2.7z" 24 | 25 | 7z x -odf2/ df2.7z 26 | 27 | mv ./df2/dogfight-sandbox-hg2/ {DBRL}/src/environments/dogfightEnv/dogfight_sandbox_hg2/ 28 | 29 | del df2/, df2.7z 30 | ``` 31 | Replace `{DBRL}` with the path of this project. 32 | 33 | > If you want to use 7z command in the command line, add your 7zip path to environment variables *Path* on Windows. Alternatively, you can also unzip the folder manually. 34 | 35 | 36 | 37 | If you want to visualize the aircraft model in FlightGear while running the FDM with the JSBSim executable, please download FlightGear following the instructions on the FlightGear website. If you need to visualize the two aircrafts in an engagement simultaneously, please copy two `{JSBSim}/data_output/flightgear.xml`, name them `flightgear{1/2}.xml`, and replace `5550` in line 18 of `flightgear2.xml` to `5551`. `{JSBSim}` represents the path of Python JSBSim. You could run the code below to get the default JSBSim path. 38 | 39 | ```python 40 | import jsbsim 41 | 42 | print(jsbsim.get_default_root_dir()) 43 | ``` 44 | 45 | Run FlightGear with the attributes below. 46 | 47 | ```bash 48 | --fdm=null --native-fdm=socket,in,60,,5550,udp 49 | ``` 50 | 51 | If you want to visualize two aircraft at the same time, use the attributes below in two FlightGear separately. 52 | 53 | ```bash 54 | --fdm=null --native-fdm=socket,in,60,,5550,udp --multiplay=out,10,127.0.0.1,5000 --multiplay=in,10,127.0.0.1,5001 --callsign=Test1 55 | ``` 56 | 57 | ```bash 58 | --fdm=null --native-fdm=socket,in,60,,5550,udp --multiplay=out,10,127.0.0.1,5001 --multiplay=in,10,127.0.0.1,5000 --callsign=Test2 59 | ``` 60 | 61 | If you want to run the reinforcement learning methods in `src/models/*.py`, please put this project in `JSBSim/`. 62 | 63 | ``` 64 | JSBSim/ 65 | └── DBRL/ 66 | ├── doc/ 67 | ├── log/ 68 | ├── src/ 69 | ├── test/ 70 | └── ... 71 | ``` 72 | 73 |
74 | 75 | 76 |
77 | Adopting 78 | 79 | DBRL builds the reinforcement learning environment in OpenAI Gym form. You could use `gym.make` to build the environment with the following register. 80 | 81 | ```python 82 | register( 83 | id="DBRL{Jsbsim/Dogfight}-v0", 84 | entry_point="gym.envs.{jsbsim/dogfight}Env:{Jsbsim/Dogfight}Env", 85 | ) 86 | ``` 87 | 88 | Use the code below to get the path of Gym. 89 | 90 | ```bash 91 | pip show gym 92 | ``` 93 | 94 | After that, you could use DBRL environments with Gym in the way like following. 95 | 96 | ```python 97 | import gym 98 | 99 | env = gym.make('DBRL{Jsbsim/Dogfight}-v0') 100 | ``` 101 | 102 | 109 | 110 |
111 | 112 | 113 | 114 |
115 | Properties 116 | 117 | The action space of DBRL-JSBSim is: 118 | 119 | ```python 120 | gym.spaces.Box( 121 | low=np.array([-1, -1, -1, 0, 0, 0, 0]), 122 | high=np.array([1, 1, 1, 1, 1, 1, 1]) 123 | ) 124 | ``` 125 | which represents the control of aileron, elevator, rudder, throttle, flap, speed brake and spoiler. 126 | 127 | The observation space is: 128 | 129 | ```python 130 | gym.spaces.Box( 131 | low=np.array([-360, -360, 0, -360, -360, -360] * 2), 132 | high=np.array([360, 360, 60000, 360, 360, 360] * 2) 133 | ) 134 | ``` 135 | which represents the latitude(degree), longitude(degree), height above sea level(feet), yaw(degree), pitch(degree) and roll(degree) of the plane. 136 | 137 | Two dimensions of the action space and the observation space represents the two aircraft in an engagement. 138 | 139 | JSBSimEnv class's constructor function takes two JSBSimFdm class as input. 140 | 141 | ```python 142 | class JsbsimEnv(Env): 143 | 144 | def __init__( 145 | self, 146 | fdm1=Fdm(fdm_id=1), 147 | fdm2=Fdm(fdm_id=2), 148 | ) -> None: 149 | ``` 150 | 151 | You could import JSBSimFdm class from `DBRL/src/environments/jsbsimEnv/jsbsimFdm.py`. It takes the arguments below as input. 152 | 153 | ```python 154 | class JsbsimFdm(): 155 | 156 | def __init__( 157 | self, 158 | fdm_id = 1, 159 | fdm_aircraft = 'f16', 160 | fdm_ic_v = 500, 161 | fdm_ic_lat = 0, 162 | fdm_ic_long = 0, 163 | fdm_ic_h = 20005.5, 164 | fdm_ic_psi = 0, 165 | fdm_ic_theta = 0, 166 | fdm_ic_phi = 0, 167 | fdm_hp = 1, 168 | fdm_fgfs = False, 169 | flight_mode = 1 170 | ) -> None: 171 | ``` 172 | `fdm_id` represents the number of aircraft, which needs to be set to `1` or `2`. `fdm_aircraft` represents the aircraft used in the engagement. `fdm_ic_v` represents the initial calibrated velocity. `fdm_ic_lat` represents the initial latitude. `fdm_ic_long` represents the initial longitude. `fdm_ic_h` represents the initial height. `fdm_ic_psi`, `fdm_ic_theta` and `fdm_ic_phi` represents the initial yaw, pitch and roll angle of the aircraft. `fdm_hp` represents the initial health point. `fdm_fgfs` denotes whether the aircraft will be visualized in FlightGear. 173 | 174 | The reward function of DBRL are set to be the damage ego plane takes to the opposite minus the opposite takes to the ego. Various reward function could be customized by `getProperty` function of class JSBSimFdm. 175 | 176 | ```python 177 | def getProperty( 178 | self, 179 | prop, 180 | ) -> list: 181 | ``` 182 | 183 | --- 184 | --- 185 | 186 | DBRL-Dogfight simulate the situation when the plane is confronted with a missile. The agent needs to take actions to avoid the attack of the missile. 187 | 188 | The action space of DBRL-Dogfight is: 189 | 190 | ```python 191 | if pitch_enable: 192 | action_infimum = np.append(action_infimum, -1) 193 | action_supermum = np.append(action_supermum, 1) 194 | if roll_enable: 195 | action_infimum = np.append(action_infimum, -1) 196 | action_supermum = np.append(action_supermum, 1) 197 | if yaw_enable: 198 | action_infimum = np.append(action_infimum, -1) 199 | action_supermum = np.append(action_supermum, 1) 200 | if flaps_enable: 201 | action_infimum = np.append(action_infimum, 0) 202 | action_supermum = np.append(action_supermum, 1) 203 | if throttle_enable: 204 | action_infimum = np.append(action_infimum, 0) 205 | action_supermum = np.append(action_supermum, 1) 206 | if flare_enable: 207 | action_infimum = np.append(action_infimum, 0) 208 | action_supermum = np.append(action_supermum, 1) 209 | action = Box( 210 | low=action_infimum, 211 | high=action_supermum, 212 | ) 213 | ``` 214 | which is the control of pitch, roll, yaw, flaps, throttle and flare. 215 | 216 | The observation space is: 217 | 218 | ```python 219 | if ego_plane_position: 220 | observation_infimum = np.append(observation_infimum, [-300, -300, -1]) 221 | observation_supermum = np.append(observation_supermum, [300, 300, 200]) 222 | if ego_plane_attitude: 223 | observation_infimum = np.append(observation_infimum, [0, -360, -360]) 224 | observation_supermum = np.append(observation_supermum, [360, 360, 360]) 225 | if oppo_plane_position: 226 | observation_infimum = np.append(observation_infimum, [-300, -300, -1]) 227 | observation_supermum = np.append(observation_supermum, [300, 300, 200]) 228 | if oppo_plane_attitude: 229 | observation_infimum = np.append(observation_infimum, [0, -360, -360]) 230 | observation_supermum = np.append(observation_supermum, [360, 360, 360]) 231 | if missile_position: 232 | observation_infimum = np.append(observation_infimum, [-300, -300, -1]) 233 | observation_supermum = np.append(observation_supermum, [300, 300, 200]) 234 | if missile_attitude: 235 | observation_infimum = np.append(observation_infimum, [-315, -315, -315]) 236 | observation_supermum = np.append(observation_supermum, [315, 315, 315]) 237 | if missile_relative_azimuth: 238 | observation_infimum = np.append(observation_infimum, [-1, -1, -1]) 239 | observation_supermum = np.append(observation_supermum, [1, 1, 1]) 240 | observation = Box( 241 | low=observation_infimum, 242 | high=observation_supermum, 243 | ) 244 | ``` 245 | which represents the X-coordinate(÷100), Y-coordinate(÷100), Z-coordinate(÷50), heading, pitch(×4) and roll(×4) of the airplane, and the X-coordinate(÷100), Y-coordinate(÷100), Z-coordinate(÷50), heading(×100), pitch(×100) and roll(×100) of the missile, as well as the azimuth of the missil relative to the airplane. 246 | 247 | DogfightEnv needs to be connected to Dogfight 2 while simulating. Its constructor function takes host and port as input. 248 | 249 | ```python 250 | class DogfightEnv(Env): 251 | 252 | def __init__( 253 | self, 254 | host='10.184.0.0', 255 | port='50888', 256 | plane_slot=1, 257 | enemy_slot=3, 258 | missile_slot=1, 259 | rendering=False, 260 | record_status=0, 261 | initial_state='carrier', 262 | throttle_enable=False, 263 | flare_enable=False, 264 | ego_pose_enable=True, 265 | oppo_pose_enable=False, 266 | missile_pose_enable=True, 267 | missile_relative_azimuth_enable=False, 268 | msg=None, 269 | ) -> None: 270 | ``` 271 | 272 | You need to start the Dogfight 2 at first and choose the Network mode mission. The host and port IP are in the upper left corner of the screen. 273 | 274 | 275 |
276 | 277 | 278 | 285 | 286 | 287 | ##
Welcome PR
288 | 289 | If you find any mistakes while using, or have any suggestions and advices, please point it out in Github Issues. We're looking forward to your contributions, such as your air combat reinforcement learning models, to this dogfight benchmark. If you are interesting in this project, feel free to contact `mrwangyou@stu.xjtu.edu.cn`. 290 | -------------------------------------------------------------------------------- /src/environments/dfEnv/dfEnv.py: -------------------------------------------------------------------------------- 1 | import random 2 | import re 3 | import sys 4 | import time 5 | import warnings 6 | 7 | import gym 8 | import numpy as np 9 | from gym import Env 10 | from gym.spaces import Box, Discrete 11 | 12 | sys.path.append('./src/') 13 | sys.path.append('./src/environments/dfEnv/') 14 | sys.path.append('./src/environments/dfEnv/dogfight_sandbox_hg2/network_client_example/') 15 | sys.path.append('gym.envs.dfEnv.dogfight_sandbox_hg2.network_client_example/') 16 | 17 | 18 | try: 19 | from gym.envs.dogfightEnv.dogfight_sandbox_hg2.network_client_example import \ 20 | dogfight_client as df 21 | print("Gym") 22 | time.sleep(1) 23 | except: 24 | from dogfightEnv.dogfight_sandbox_hg2.network_client_example import \ 25 | dogfight_client as df 26 | print("DBRL") 27 | time.sleep(1) 28 | 29 | 30 | class DfEnv(Env): 31 | 32 | def __init__( 33 | self, 34 | host='10.184.0.0', 35 | port='50888', 36 | plane_slot=1, 37 | enemy_slot=3, 38 | rendering=False, 39 | ) -> None: 40 | 41 | self.host = host 42 | self.port = port 43 | self.nof = 0 44 | self.rendering = rendering 45 | self.plane_slot = plane_slot 46 | self.enemy_slot = enemy_slot 47 | 48 | 49 | try: 50 | planes = df.get_planes_list() 51 | except AttributeError: 52 | print('Run for the first time') 53 | df.connect(host, int(port)) 54 | time.sleep(2) 55 | planes = df.get_planes_list() 56 | 57 | df.disable_log() 58 | 59 | self.planeID = planes[self.plane_slot] 60 | self.enemyID = planes[self.enemy_slot] 61 | 62 | for i in planes: 63 | df.reset_machine(i) 64 | 65 | 66 | df.set_plane_thrust(self.enemyID, 1) 67 | df.set_plane_thrust(self.planeID, 1) 68 | df.set_client_update_mode(True) 69 | 70 | df.set_renderless_mode(True) 71 | 72 | t = 0 73 | while t < 1: 74 | plane_state = df.get_plane_state(self.enemyID) 75 | df.update_scene() 76 | t = plane_state["thrust_level"] 77 | 78 | df.activate_post_combustion(self.enemyID) 79 | df.activate_post_combustion(self.planeID) 80 | 81 | df.set_plane_pitch(self.enemyID, -0.5) 82 | df.set_plane_pitch(self.planeID, -0.5) 83 | 84 | p = 0 85 | while p < 15: 86 | plane_state = df.get_plane_state(self.enemyID) 87 | df.update_scene() 88 | p = plane_state["pitch_attitude"] 89 | 90 | df.stabilize_plane(self.enemyID) 91 | df.stabilize_plane(self.planeID) 92 | 93 | df.retract_gear(self.enemyID) 94 | df.retract_gear(self.planeID) 95 | 96 | s = 0 97 | while s < 1000: 98 | plane_state = df.get_plane_state(self.enemyID) 99 | df.update_scene() 100 | s = plane_state["altitude"] 101 | 102 | df.set_target_id(self.enemyID, self.planeID) 103 | df.activate_IA(self.enemyID) 104 | 105 | self.action_space = Box( # Same as Evade mode 106 | low=np.array([ 107 | 0, # Flaps 襟翼 108 | -1, # Pitch 俯仰角 109 | -1, # Roll 翻滚角 110 | -1, # Yaw 偏航角 111 | ]), 112 | high=np.array([ 113 | 1, 114 | 1, 115 | 1, 116 | 1, 117 | ]), 118 | ) 119 | 120 | self.observation_space = Box( 121 | low=np.array([ # simple normalized 122 | # ego 123 | -300, # x / 100 124 | -300, # y / 100 125 | -1, # z / 50 126 | 0, # heading 127 | -360, # pitch_attitude * 4 128 | -360, # roll_attitude * 4 129 | # oppo 130 | -300, # x / 100 131 | -300, # y / 100 132 | -1, # z / 50 133 | 0, # heading 134 | -360, # pitch_attitude * 4 135 | -360, # roll_attitude * 4 136 | ]), 137 | high=np.array([ 138 | 300, 139 | 300, 140 | 200, 141 | 360, 142 | 360, 143 | 360, 144 | 300, 145 | 300, 146 | 200, 147 | 360, 148 | 360, 149 | 360, 150 | ]), 151 | dtype=np.float64 152 | ) 153 | 154 | if self.rendering: 155 | df.set_renderless_mode(False) 156 | 157 | def getProperty( 158 | self, 159 | prop 160 | ): 161 | if prop == 'position': 162 | return [ 163 | df.get_plane_state(self.planeID)['position'][0], 164 | df.get_plane_state(self.planeID)['position'][2], 165 | df.get_plane_state(self.planeID)['position'][1], 166 | ] 167 | elif prop == 'positionEnemy': 168 | return [ 169 | df.get_plane_state(self.enemyID)['position'][0], 170 | df.get_plane_state(self.enemyID)['position'][2], 171 | df.get_plane_state(self.enemyID)['position'][1], 172 | ] 173 | elif prop == 'attitudeRad': 174 | return [ 175 | df.get_plane_state(self.planeID)['heading'] / 180 * np.pi, 176 | df.get_plane_state(self.planeID)['pitch_attitude'] / 180 * np.pi, 177 | df.get_plane_state(self.planeID)['roll_attitude'] / 180 * np.pi, 178 | ] 179 | elif prop == 'attitudeRadEnemy': 180 | return [ 181 | df.get_plane_state(self.enemyID)['heading'] / 180 * np.pi, 182 | df.get_plane_state(self.enemyID)['pitch_attitude'] / 180 * np.pi, 183 | df.get_plane_state(self.enemyID)['roll_attitude'] / 180 * np.pi, 184 | ] 185 | elif prop == 'attitudeDeg': 186 | return [ 187 | df.get_plane_state(self.planeID)['heading'], 188 | df.get_plane_state(self.planeID)['pitch_attitude'], 189 | df.get_plane_state(self.planeID)['roll_attitude'], 190 | ] 191 | elif prop == 'attitudeDegEnemy': 192 | return [ 193 | df.get_plane_state(self.enemyID)['heading'], 194 | df.get_plane_state(self.enemyID)['pitch_attitude'], 195 | df.get_plane_state(self.enemyID)['roll_attitude'], 196 | ] 197 | elif prop == 'pose': 198 | return [ 199 | df.get_plane_state(self.planeID)['position'][0], 200 | df.get_plane_state(self.planeID)['position'][2], 201 | df.get_plane_state(self.planeID)['position'][1], 202 | df.get_plane_state(self.planeID)['heading'], 203 | df.get_plane_state(self.planeID)['pitch_attitude'], 204 | df.get_plane_state(self.planeID)['roll_attitude'], 205 | ] 206 | elif prop == 'poseEnemy': 207 | return [ 208 | df.get_plane_state(self.enemyID)['position'][0], 209 | df.get_plane_state(self.enemyID)['position'][2], 210 | df.get_plane_state(self.enemyID)['position'][1], 211 | df.get_plane_state(self.enemyID)['heading'], 212 | df.get_plane_state(self.enemyID)['pitch_attitude'], 213 | df.get_plane_state(self.enemyID)['roll_attitude'], 214 | ] 215 | elif prop == 'velocity': 216 | warnings.warn('三个值为速度在欧拉角上的分量, 与JSBSim中的速度不同') 217 | return [ 218 | df.get_plane_state(self.planeID)['horizontal_speed'], 219 | df.get_plane_state(self.planeID)['linear_speed'], 220 | -df.get_plane_state(self.planeID)['vertical_speed'], 221 | ] 222 | elif prop == 'velocityEnemy': 223 | warnings.warn('三个值为速度在欧拉角上的分量, 与JSBSim中的速度不同') 224 | return [ 225 | df.get_plane_state(self.enemyID)['horizontal_speed'], 226 | df.get_plane_state(self.enemyID)['linear_speed'], 227 | -df.get_plane_state(self.enemyID)['vertical_speed'], 228 | ] 229 | else: 230 | raise Exception("Property {} doesn't exist!".format(prop)) 231 | 232 | def getDistanceVector(self, ego): 233 | 234 | positionEci1 = self.getProperty("position") 235 | positionEci2 = self.getProperty("positionEnemy") 236 | 237 | if ego == 1: 238 | return np.array(positionEci2) - np.array(positionEci1) 239 | elif ego == 2: 240 | return np.array(positionEci1) - np.array(positionEci2) 241 | else: 242 | raise Exception("Plane {} doesn\'t exist".format(ego)) 243 | 244 | def getDistance(self): 245 | plane_state = df.get_plane_state(self.planeID) 246 | enemy_state = df.get_plane_state(self.enemyID) 247 | 248 | tmp1 = ((plane_state['position'][0] - enemy_state['position'][0]) ** 2 +\ 249 | (plane_state['position'][1] - enemy_state['position'][1]) ** 2 +\ 250 | (plane_state['position'][2] - enemy_state['position'][2]) ** 2) ** .5 251 | 252 | tmp2 = np.linalg.norm(self.getDistanceVector(1)) 253 | tmp3 = np.linalg.norm(self.getDistanceVector(2)) 254 | try: 255 | assert abs(tmp1 - tmp2) < 1, [tmp1, tmp2, abs(tmp1 - tmp2), abs(tmp1 - tmp2) < 1] 256 | assert abs(tmp3 - tmp2) < 1, [tmp3, tmp2, abs(tmp3 - tmp2), abs(tmp3 - tmp2) < 1] 257 | except: 258 | warnings.warn('飞机之间距离计算误差较大!') 259 | 260 | return tmp2 261 | 262 | def getDamage(self, ego): 263 | attitude1 = self.getProperty("attitudeRad") 264 | attitude2 = self.getProperty("attitudeRadEnemy") 265 | 266 | theta_1 = np.pi / 2 - attitude1[1] 267 | psi_1 = np.pi * 2 - ((attitude1[0] - np.pi / 2) % (np.pi * 2)) 268 | heading_1 = np.array([ 269 | np.sin(theta_1) * np.cos(psi_1), 270 | np.sin(theta_1) * np.sin(psi_1), 271 | np.cos(theta_1) 272 | ]) 273 | 274 | theta_2 = np.pi / 2 - attitude2[1] 275 | psi_2 = np.pi * 2 - (attitude2[0] - np.pi / 2) % (np.pi * 2) 276 | heading_2 = np.array([ 277 | np.sin(theta_2) * np.cos(psi_2), 278 | np.sin(theta_2) * np.sin(psi_2), 279 | np.cos(theta_2) 280 | ]) 281 | 282 | if 500 <= self.getDistance() <= 3000: 283 | 284 | angle1 = np.arccos( 285 | np.dot(self.getDistanceVector(ego=1), heading_1) / 286 | (self.getDistance() * np.linalg.norm(heading_1)) 287 | ) 288 | 289 | if -1 <= angle1 / np.pi * 180 <= 1: 290 | if ego == 2: 291 | return (3000 - self.getDistance()) / 2500 / 120 292 | 293 | angle2 = np.arccos( 294 | np.dot(self.getDistanceVector(ego=2), heading_2) / 295 | (self.getDistance() * np.linalg.norm(heading_2)) 296 | ) 297 | 298 | if -1 <= angle2 / np.pi * 180 <= 1: 299 | if ego == 1: 300 | return (3000 - self.getDistance()) / 2500 / 120 301 | 302 | # print("angle1 {}\tangle2 {}".format(angle1 / np.pi * 180, angle2 / np.pi * 180)) 303 | 304 | return 0 305 | 306 | def damage(self, ego): 307 | if ego == 1: 308 | df.set_health(self.planeID, df.get_health(self.planeID)['health_level'] - self.getDamage(1)) 309 | if ego == 2: 310 | df.set_health(self.enemyID, df.get_health(self.enemyID)['health_level'] - self.getDamage(2)) 311 | 312 | def getHP(self): 313 | return df.get_health(self.planeID)['health_level'] 314 | 315 | def getHPEnemy(self): 316 | return df.get_health(self.enemyID)['health_level'] 317 | 318 | def terminate(self): 319 | if self.getHP() <= 0.0 and self.getHPEnemy() > 0.0: # Oppo 320 | return -1 321 | elif self.getHP() > 0.0 and self.getHPEnemy() <= 0.0: # Ego 322 | return 1 323 | elif self.getHP() <= 0.0 and self.getHPEnemy() <= 0.0: # Tie 324 | return 2 325 | else: 326 | return 0 327 | 328 | def sendAction( 329 | self, 330 | action, 331 | actionType=None, 332 | ): 333 | if actionType == None: 334 | df.set_plane_flaps(self.planeID, float(action[0])) 335 | df.set_plane_pitch(self.planeID, float(action[1])) 336 | df.set_plane_roll(self.planeID, float(action[2])) 337 | df.set_plane_yaw(self.planeID, float(action[3])) 338 | elif actionType == 'Flaps' or actionType == 'flaps': 339 | df.set_plane_flaps(self.planeID, action) 340 | elif actionType == 'Pitch' or actionType == 'pitch': 341 | df.set_plane_pitch(self.planeID, action) 342 | elif actionType == 'Roll' or actionType == 'roll': 343 | df.set_plane_roll(self.planeID, action) 344 | elif actionType == 'Yaw' or actionType == 'yaw': 345 | df.set_plane_yaw(self.planeID, action) 346 | 347 | def step(self, action): 348 | 349 | t_begin = time.time() 350 | 351 | self.sendAction(action) 352 | 353 | df.update_scene() 354 | self.nof += 1 355 | 356 | # self.damage(1) 357 | self.damage(2) 358 | 359 | terminate = self.terminate() 360 | 361 | if terminate == 1: 362 | reward = 50 363 | elif terminate == -1: 364 | reward = 0 365 | elif terminate == 2: 366 | reward = 0 367 | else: 368 | reward = 0 369 | 370 | terminate = True if terminate else False 371 | 372 | ob = np.array([ # normalized 373 | df.get_plane_state(self.planeID)['position'][0] / 100, 374 | df.get_plane_state(self.planeID)['position'][2] / 100, 375 | df.get_plane_state(self.planeID)['position'][1] / 50, 376 | df.get_plane_state(self.planeID)['heading'], 377 | df.get_plane_state(self.planeID)['pitch_attitude'] * 4, 378 | df.get_plane_state(self.planeID)['roll_attitude'] * 4, 379 | 380 | df.get_plane_state(self.enemyID)['position'][0] / 100, 381 | df.get_plane_state(self.enemyID)['position'][2] / 100, 382 | df.get_plane_state(self.enemyID)['position'][1] / 50, 383 | df.get_plane_state(self.enemyID)['heading'], 384 | df.get_plane_state(self.enemyID)['pitch_attitude'] * 4, 385 | df.get_plane_state(self.enemyID)['roll_attitude'] * 4, 386 | 387 | ]) 388 | 389 | if self.rendering: 390 | time.sleep( 391 | max(0, df.get_timestep()['timestep'] - (time.time() - t_begin)) 392 | ) 393 | 394 | return ob, reward, terminate, {} 395 | 396 | def render(self, id=0): 397 | 398 | df.set_renderless_mode(False) 399 | 400 | 401 | def reset( 402 | self, 403 | ): 404 | self.__init__( 405 | host=self.host, 406 | port=self.port, 407 | plane_slot=self.plane_slot, 408 | enemy_slot=self.enemy_slot, 409 | rendering=self.rendering, 410 | ) 411 | 412 | ob = np.array([ # normalized 413 | df.get_plane_state(self.planeID)['position'][0] / 100, 414 | df.get_plane_state(self.planeID)['position'][2] / 100, 415 | df.get_plane_state(self.planeID)['position'][1] / 50, 416 | df.get_plane_state(self.planeID)['heading'], 417 | df.get_plane_state(self.planeID)['pitch_attitude'] * 4, 418 | df.get_plane_state(self.planeID)['roll_attitude'] * 4, 419 | 420 | df.get_plane_state(self.enemyID)['position'][0] / 100, 421 | df.get_plane_state(self.enemyID)['position'][2] / 100, 422 | df.get_plane_state(self.enemyID)['position'][1] / 50, 423 | df.get_plane_state(self.enemyID)['heading'], 424 | df.get_plane_state(self.enemyID)['pitch_attitude'] * 4, 425 | df.get_plane_state(self.enemyID)['roll_attitude'] * 4, 426 | ]) 427 | 428 | return ob 429 | 430 | -------------------------------------------------------------------------------- /src/environments/dogfightEnv/dogfightEnv.py: -------------------------------------------------------------------------------- 1 | import random 2 | import os 3 | import re 4 | import sys 5 | import time 6 | import warnings 7 | import math 8 | 9 | import gym 10 | import numpy as np 11 | from gym import Env 12 | from gym.spaces import Box, Discrete 13 | from pathlib import Path 14 | 15 | 16 | if re.findall('dogfightEnv\.py$', str(Path(__file__).resolve())) and \ 17 | re.findall('dogfightEnv$', str(Path(__file__).resolve().parents[0])) and \ 18 | re.findall('envs$', str(Path(__file__).resolve().parents[1])) and \ 19 | re.findall('gym$', str(Path(__file__).resolve().parents[2])): 20 | print("Using Gym Version") 21 | time.sleep(1) 22 | 23 | elif re.findall('dogfightEnv\.py$', str(Path(__file__).resolve())) and \ 24 | re.findall('dogfightEnv$', str(Path(__file__).resolve().parents[0])) and \ 25 | re.findall('environments$', str(Path(__file__).resolve().parents[1])) and \ 26 | re.findall('src$', str(Path(__file__).resolve().parents[2])) and \ 27 | re.findall('DBRL$', str(Path(__file__).resolve().parents[3])): 28 | print("Using DBRL Version") 29 | time.sleep(1) 30 | 31 | sys.path.append(str(Path(__file__).resolve().parents[2]) + '/envs/dogfightEnv') 32 | # You can also replace `import socket_lib` in `dogfight_sandbox_hg2\network_client_example\dogfight_client.py` with `from . import socket_lib` to achieve the same function as the following line of code 33 | sys.path.append(str(Path(__file__).resolve().parents[2]) + '/envs/dogfightEnv/dogfight_sandbox_hg2/network_client_example/') 34 | 35 | from initialization import * 36 | from models import * 37 | 38 | try: 39 | from .dogfight_sandbox_hg2.network_client_example import \ 40 | dogfight_client as df 41 | print("Gym") 42 | time.sleep(1) 43 | except: 44 | from .dogfight_sandbox_hg2.network_client_example import \ 45 | dogfight_client as df 46 | print("DBRL") 47 | time.sleep(1) 48 | 49 | 50 | class DogfightEnv(Env): 51 | 52 | def __init__( 53 | self, 54 | host='10.184.0.0', 55 | port='50888', 56 | plane_slot=1, 57 | enemy_slot=3, 58 | missile_slot=1, 59 | rendering=False, 60 | record_status=0, 61 | initial_state='carrier', 62 | throttle_enable=False, 63 | flare_enable=False, 64 | ego_pose_enable=True, 65 | oppo_pose_enable=False, 66 | missile_pose_enable=True, 67 | missile_relative_azimuth_enable=False, 68 | msg=None, 69 | ) -> None: 70 | 71 | self.host = host 72 | self.port = port 73 | self.nof = 0 74 | self.rendering = rendering 75 | self.plane_slot = plane_slot 76 | self.enemy_slot = enemy_slot 77 | self.missile_slot = missile_slot 78 | self.record_status = record_status 79 | self.initial_state = initial_state 80 | self.throttle_enable = throttle_enable 81 | self.flare_enable = flare_enable 82 | self.flare_active = False 83 | self.ego_pose_enable = ego_pose_enable 84 | self.oppo_pose_enable = oppo_pose_enable 85 | self.missile_pose_enable = missile_pose_enable 86 | self.missile_relative_azimuth_enable = missile_relative_azimuth_enable 87 | self.msg = msg 88 | 89 | try: 90 | planes = df.get_planes_list() 91 | except: 92 | print('Connecting...') 93 | df.connect(host, int(port)) 94 | time.sleep(2) 95 | planes = df.get_planes_list() 96 | 97 | df.disable_log() 98 | 99 | for i in planes: 100 | df.reset_machine(i) 101 | 102 | self.planeID = planes[self.plane_slot] 103 | self.enemyID = planes[self.enemy_slot] 104 | 105 | missiles = df.get_machine_missiles_list(self.enemyID) 106 | self.missileID = missiles[self.missile_slot] 107 | 108 | df.set_plane_thrust(self.enemyID, 1) 109 | df.set_plane_thrust(self.planeID, 1) 110 | 111 | df.set_client_update_mode(True) 112 | 113 | df.set_renderless_mode(True) 114 | 115 | if self.initial_state == 'carrier': 116 | start_on_carrier( 117 | df=df, 118 | planeID=self.planeID, 119 | enemyID=self.enemyID, 120 | missile_slot=self.missile_slot, 121 | missileID=self.missileID, 122 | ) 123 | elif self.initial_state == 'air': 124 | start_in_sky( 125 | df=df, 126 | planeID=self.planeID, 127 | enemyID=self.enemyID, 128 | missile_slot=self.missile_slot, 129 | missileID=self.missileID, 130 | ) 131 | else: 132 | raise Exception('Invalid initial state {}!'.format(self.initial_state)) 133 | 134 | self.action_space, self.action_type = action_space( 135 | pitch_enable=True, 136 | roll_enable=True, 137 | yaw_enable=True, 138 | flaps_enable=True, 139 | throttle_enable=throttle_enable, 140 | flare_enable=flare_enable 141 | ) 142 | 143 | self.observation_space = observation_space( 144 | ego_plane_position=ego_pose_enable, 145 | ego_plane_attitude=ego_pose_enable, 146 | oppo_plane_position=oppo_pose_enable, 147 | oppo_plane_attitude=oppo_pose_enable, 148 | missile_position=missile_pose_enable, 149 | missile_attitude=missile_pose_enable, 150 | missile_relative_azimuth=missile_relative_azimuth_enable, 151 | ) 152 | 153 | 154 | if self.rendering: 155 | df.set_renderless_mode(False) 156 | 157 | def getProperty( 158 | self, 159 | prop 160 | ): 161 | if prop == 'position': 162 | plane_state = df.get_plane_state(self.planeID) 163 | return [ 164 | plane_state['position'][0], 165 | plane_state['position'][2], 166 | plane_state['position'][1], 167 | ] 168 | elif prop == 'positionEci': 169 | warnings.warn('Dogfight simulation environments have no global data!') 170 | plane_state = df.get_plane_state(self.planeID) 171 | return [ 172 | plane_state['position'][0], 173 | plane_state['position'][2], 174 | plane_state['position'][1], 175 | ] 176 | elif prop == 'positionEcef': 177 | warnings.warn('Dogfight simulation environments have no global data!') 178 | plane_state = df.get_plane_state(self.planeID) 179 | return [ 180 | plane_state['position'][0], 181 | plane_state['position'][2], 182 | plane_state['position'][1], 183 | ] 184 | elif prop == 'attitudeRad': 185 | plane_state = df.get_plane_state(self.planeID) 186 | return [ 187 | plane_state['heading'] / 180 * np.pi, 188 | plane_state['pitch_attitude'] / 180 * np.pi, 189 | plane_state['roll_attitude'] / 180 * np.pi, 190 | ] 191 | elif prop == 'attitudeDeg': 192 | plane_state = df.get_plane_state(self.planeID) 193 | return [ 194 | plane_state['heading'], 195 | plane_state['pitch_attitude'], 196 | plane_state['roll_attitude'], 197 | ] 198 | elif prop == 'pose': 199 | plane_state = df.get_plane_state(self.planeID) 200 | return [ 201 | plane_state['position'][0], 202 | plane_state['position'][2], 203 | plane_state['position'][1], 204 | plane_state['heading'], 205 | plane_state['pitch_attitude'], 206 | plane_state['roll_attitude'], 207 | ] 208 | elif prop == 'poseNorm': 209 | plane_state = df.get_plane_state(self.planeID) 210 | return [ 211 | plane_state['position'][0] / 100, 212 | plane_state['position'][2] / 100, 213 | plane_state['position'][1] / 50, 214 | plane_state['heading'], 215 | plane_state['pitch_attitude'] * 4, 216 | plane_state['roll_attitude'] * 4, 217 | ] 218 | elif prop == 'velocity': 219 | warnings.warn('三个值为速度在欧拉角上的分量, 与JSBSim中的速度不同') 220 | plane_state = df.get_plane_state(self.planeID) 221 | return [ 222 | plane_state['horizontal_speed'], 223 | plane_state['linear_speed'], 224 | -plane_state['vertical_speed'], 225 | ] 226 | elif prop == 'poseMissile': 227 | missile_state = df.get_missile_state(self.missileID) 228 | return [ 229 | missile_state['position'][0], 230 | missile_state['position'][2], 231 | missile_state['position'][1], 232 | missile_state['Euler_angles'][0], 233 | missile_state['Euler_angles'][1], 234 | missile_state['Euler_angles'][2], 235 | ] 236 | elif prop == 'poseMissleNorm': 237 | missile_state = df.get_missile_state(self.missileID) 238 | return [ 239 | missile_state['position'][0] / 100, 240 | missile_state['position'][2] / 100, 241 | missile_state['position'][1] / 50, 242 | missile_state['Euler_angles'][0] * 100, 243 | missile_state['Euler_angles'][1] * 100, 244 | missile_state['Euler_angles'][2] * 100, 245 | ] 246 | elif prop == 'azimuthRel': 247 | plane_state = df.get_plane_state(self.planeID) 248 | missile_state = df.get_missile_state(self.missileID) 249 | azimuth = [ 250 | missile_state['position'][0] - plane_state['position'][0], 251 | missile_state['position'][2] - plane_state['position'][2], 252 | missile_state['position'][1] - plane_state['position'][1], 253 | ] 254 | azimuthRel = [0, 0, 0] 255 | for idx, value in enumerate(azimuth): 256 | azimuthRel[idx] = value / np.linalg.norm(azimuth) 257 | return azimuthRel 258 | 259 | else: 260 | raise Exception("Property {} doesn't exist!".format(prop)) 261 | 262 | def getDistance(self): 263 | plane_state = df.get_plane_state(self.planeID) 264 | missile_state = df.get_missile_state(self.missileID) 265 | 266 | return ((plane_state['position'][0] - missile_state['position'][0]) ** 2 +\ 267 | (plane_state['position'][1] - missile_state['position'][1]) ** 2 +\ 268 | (plane_state['position'][2] - missile_state['position'][2]) ** 2) ** .5 269 | 270 | def getHP(self): 271 | return df.get_health(self.planeID)['health_level'] 272 | 273 | def terminate(self): 274 | if not df.get_missile_state(self.missileID)['active']: 275 | if self.getHP() >= .9: 276 | return 1 277 | else: 278 | return -1 279 | else: 280 | return 0 281 | 282 | def setProperty( 283 | self, 284 | prop, 285 | value, 286 | ): 287 | if prop == 'plane': 288 | self.plane_slot = value 289 | elif prop == 'enemy': 290 | self.enemy_slot = value 291 | elif prop == 'missile': 292 | self.missile_slot = value 293 | else: 294 | raise Exception("Property {} doesn't exist!".format(prop)) 295 | 296 | def sendAction( 297 | self, 298 | action, 299 | actionType=None, 300 | ): 301 | if actionType == None: 302 | for idx, value in enumerate(self.action_type): 303 | if value[0] == '_': 304 | eval(value[1:])(self.planeID, float(action[idx])) 305 | 306 | elif value == 'Flare' and not self.flare_active: 307 | if float(action[idx]) >= 0.99: 308 | self.flare_active = True 309 | self.setFlare() 310 | 311 | elif actionType == 'Flaps' or actionType == 'flaps': 312 | df.set_plane_flaps(self.planeID, action) 313 | elif actionType == 'Pitch' or actionType == 'pitch': 314 | df.set_plane_pitch(self.planeID, action) 315 | elif actionType == 'Roll' or actionType == 'roll': 316 | df.set_plane_roll(self.planeID, action) 317 | elif actionType == 'Yaw' or actionType == 'yaw': 318 | df.set_plane_yaw(self.planeID, action) 319 | 320 | def setFlare( 321 | self, 322 | ): 323 | planes = df.get_planes_list() 324 | plane_id = planes[self.plane_slot] 325 | 326 | missiles = df.get_machine_missiles_list(plane_id) 327 | 328 | self.flare_slot = 0 329 | self.flare_id = missiles[self.flare_slot] 330 | 331 | df.fire_missile(plane_id, self.flare_slot) 332 | 333 | df.set_machine_custom_physics_mode(self.flare_id, True) 334 | 335 | df.set_missile_life_delay(self.flare_id, 5) 336 | 337 | df.update_scene() 338 | 339 | 340 | flare_state = df.get_missile_state(self.flare_id) 341 | self.x, self.y, self.z = flare_state["position"][0], flare_state["position"][1], flare_state["position"][2] 342 | self.y_init = self.y 343 | self.z_init = self.z 344 | self.v_init = df.get_plane_state(planes[self.plane_slot])['linear_speed'] 345 | self.w_init = df.get_plane_state(planes[self.plane_slot])['vertical_speed'] 346 | 347 | self.flare_matrix = [ 348 | 1, 0, 0, 349 | 0, 1, 0, 350 | 0, 0, 1, 351 | self.x, self.y, self.z, 352 | ] 353 | 354 | # Linear displacement vector in m.s-1 355 | self.flare_speed_vector = [1, 1, 1] 356 | 357 | # Custom missile movements 358 | self.flare_active_time = 0 359 | 360 | def flare_step( 361 | self, 362 | ): 363 | frame_time_step = 1/60 364 | 365 | flare_state = df.get_missile_state(self.flare_id) 366 | if not flare_state["wreck"]: 367 | self.flare_matrix[9] = self.x 368 | self.flare_matrix[10] = self.y 369 | self.flare_matrix[11] = self.z 370 | 371 | df.update_machine_kinetics(self.flare_id, self.flare_matrix, self.flare_speed_vector) 372 | df.update_scene() 373 | self.x = self.x 374 | self.y = self.y_init + self.w_init * self.flare_active_time - 0.5 * 10 * self.flare_active_time * self.flare_active_time 375 | self.z = self.z_init + self.v_init * self.flare_active_time 376 | 377 | # Compute speed vector, used by missile engine smoke 378 | self.flare_speed_vector = [(self.x-self.flare_matrix[9]) / frame_time_step, (self.y - self.flare_matrix[10]) / frame_time_step, (self.z - self.flare_matrix[11]) / frame_time_step] 379 | 380 | self.flare_active_time += frame_time_step 381 | 382 | if df.get_missile_state(self.flare_id)['active']: 383 | 384 | if random.random() < .5: 385 | df.set_missile_target(self.missileID, self.planeID) 386 | else: 387 | df.set_missile_target(self.missileID, self.flare_id) 388 | 389 | else: 390 | df.set_missile_target(self.missileID, self.planeID) 391 | 392 | def XYtoGPS(self, x, y, ref_lat=0, ref_lon=0): 393 | CONSTANTS_RADIUS_OF_EARTH = 6378137. # meters (m) 394 | x_rad = float(x) / CONSTANTS_RADIUS_OF_EARTH 395 | y_rad = float(y) / CONSTANTS_RADIUS_OF_EARTH 396 | c = math.sqrt(x_rad * x_rad + y_rad * y_rad) 397 | 398 | ref_lat_rad = math.radians(ref_lat) 399 | ref_lon_rad = math.radians(ref_lon) 400 | 401 | ref_sin_lat = math.sin(ref_lat_rad) 402 | ref_cos_lat = math.cos(ref_lat_rad) 403 | 404 | if abs(c) > 0: 405 | sin_c = math.sin(c) 406 | cos_c = math.cos(c) 407 | 408 | lat_rad = math.asin(cos_c * ref_sin_lat + (x_rad * sin_c * ref_cos_lat) / c) 409 | lon_rad = (ref_lon_rad + math.atan2(y_rad * sin_c, c * ref_cos_lat * cos_c - x_rad * ref_sin_lat * sin_c)) 410 | 411 | lat = math.degrees(lat_rad) 412 | lon = math.degrees(lon_rad) 413 | 414 | else: 415 | lat = math.degrees(ref_lat) 416 | lon = math.degrees(ref_lon) 417 | 418 | return lat, lon 419 | 420 | def getObservation(self): 421 | ob = np.array([]) 422 | if self.ego_pose_enable: 423 | ob = np.append(ob, self.getProperty('poseNorm')) 424 | if self.oppo_pose_enable: 425 | pass 426 | if self.missile_pose_enable: 427 | ob = np.append(ob, self.getProperty('poseMissleNorm')) 428 | if self.missile_relative_azimuth_enable: 429 | ob = np.append(ob, self.getProperty('azimuthRel')) 430 | 431 | return ob 432 | 433 | def step(self, action): 434 | 435 | t_begin = time.time() 436 | 437 | self.sendAction(action) 438 | 439 | if self.flare_enable and self.flare_active: 440 | self.flare_step() 441 | 442 | df.update_scene() 443 | self.nof += 1 444 | 445 | if self.record_status: 446 | if self.nof == 1: 447 | path = r'./log/' 448 | if not os.path.exists(path): 449 | os.mkdir(path) 450 | file = open('./log/{}_status_record.txt'.format(self.msg), 'w') 451 | file.write('FileType=text/acmi/tacview\nFileVersion=2.1\n0,ReferenceTime=2022-10-01T00:00:00Z\n0,Title = test simple aircraft\n1000000,T=160.123456|24.8976763|0, Type=Ground+Static+Building, Name=Competition, EngagementRange=30000\n') 452 | else: 453 | file = open('./log/{}_status_record.txt'.format(self.msg), 'a') 454 | file.write('#{}\n'.format(self.nof * df.get_timestep()['timestep'])) 455 | if self.nof == 1: 456 | initMsg = ',Name=F16,Type=Air+FixedWing,Coalition=Enemies,Color=Blue,Mach=0.800,ShortName=F16,RadarMode=1,RadarRange=40000,RadarHorizontalBeamwidth=60,RadarVerticalBeamwidth=60' 457 | else: 458 | initMsg = '' 459 | file.write("1,T={0[0]}|{0[1]}|{1}{2}\n".format( 460 | self.XYtoGPS(self.getProperty('position')[0], self.getProperty('position')[1]), 461 | self.getProperty('position')[2], 462 | initMsg 463 | ) 464 | ) 465 | file.close() 466 | 467 | terminate = self.terminate() 468 | 469 | if terminate == 1: 470 | reward = 50 471 | elif terminate == -1: 472 | reward = -50 473 | else: 474 | reward = .1 475 | if self.getHP() <= .1: 476 | reward = -1 477 | 478 | terminate = True if terminate else False 479 | 480 | # plane_state = df.get_plane_state(self.planeID) 481 | # missile_state = df.get_missile_state(self.missileID) 482 | 483 | ob = self.getObservation() 484 | 485 | if self.rendering: 486 | time.sleep( 487 | max(0, df.get_timestep()['timestep'] - (time.time() - t_begin)) 488 | ) 489 | 490 | return ob, reward, terminate, {} 491 | 492 | def render(self, id=0): 493 | 494 | df.set_renderless_mode(False) 495 | 496 | 497 | def reset( 498 | self, 499 | ): 500 | 501 | self.__init__( 502 | host=self.host, 503 | port=self.port, 504 | plane_slot=self.plane_slot, 505 | enemy_slot=self.enemy_slot, 506 | missile_slot=self.missile_slot, 507 | rendering=self.rendering, 508 | record_status=self.record_status, 509 | initial_state=self.initial_state, 510 | throttle_enable=self.throttle_enable, 511 | flare_enable=self.flare_enable, 512 | ego_pose_enable = self.ego_pose_enable, 513 | oppo_pose_enable = self.oppo_pose_enable, 514 | missile_pose_enable = self.missile_pose_enable, 515 | missile_relative_azimuth_enable = self.missile_relative_azimuth_enable, 516 | msg=self.msg 517 | ) 518 | 519 | ob = self.getObservation() 520 | 521 | return ob 522 | 523 | -------------------------------------------------------------------------------- /bak/Missions.bak: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018-2021 Eric Kernin, NWNC HARFANG. 2 | 3 | import harfang as hg 4 | from Animations import * 5 | import tools 6 | from random import uniform 7 | from math import radians 8 | import json 9 | import network_server as netws 10 | from overlays import * 11 | from MachineDevice import * 12 | 13 | class Mission: 14 | 15 | def __init__(self, title, num_ennemies, num_allies, num_carriers_ennemies, num_carriers_allies, setup_players, end_test, end_phase_update): 16 | self.title = title 17 | self.ennemies = num_ennemies 18 | self.allies = num_allies 19 | self.allies_carriers = num_carriers_allies 20 | self.ennemies_carriers = num_carriers_ennemies 21 | self.setup_players_f = setup_players 22 | self.end_test_f = end_test 23 | self.update_end_phase_f = end_phase_update 24 | self.failed = False 25 | self.aborted = False 26 | 27 | def reset(self): 28 | self.failed = False 29 | self.aborted = False 30 | 31 | def setup_players(self, main): 32 | self.setup_players_f(main) 33 | 34 | def end_test(self, main): 35 | return self.end_test_f(main) 36 | 37 | def update_end_phase(self, main, dts): 38 | self.update_end_phase_f(main, dts) 39 | 40 | class Missions: 41 | beep_ref = None 42 | beep_state = None 43 | beep_source = None 44 | 45 | validation_ref = None 46 | validation_state = None 47 | validation_source = None 48 | 49 | # animations mission: 50 | am_start = False 51 | am_anim_x_prec = None 52 | am_anim_a_prec = None 53 | am_anim_x_new = None 54 | am_anim_a_new = None 55 | am_mission_id_prec = 0 56 | am_t = 0 57 | 58 | missions = [] 59 | mission_id = 1 60 | 61 | @classmethod 62 | def display_mission_title(cls, main, fade_lvl, dts, yof7): 63 | mcr = 1 64 | mcg = 0.6 65 | mcb = 0.1 66 | 67 | if not cls.am_start: 68 | mx = 0.5 69 | mc = hg.Color(mcr, mcg, mcb, 1) * fade_lvl 70 | mid = cls.mission_id 71 | 72 | if cls.mission_id < len(cls.missions) - 1 and main.keyboard.Pressed(hg.K_Right): 73 | cls.am_start = True 74 | mcpt = 1 75 | xpd = 0 76 | xns = 1 77 | 78 | elif cls.mission_id > 0 and main.keyboard.Pressed(hg.K_Left): 79 | cls.am_start = True 80 | mcpt = -1 81 | xpd = 1 82 | xns = 0 83 | 84 | if cls.am_start: 85 | cls.beep_source = hg.PlayStereo(cls.beep_ref, cls.beep_state) 86 | hg.SetSourceVolume(cls.beep_source, 0.05) 87 | cls.am_mission_id_prec = cls.mission_id 88 | cls.mission_id += mcpt 89 | cls.am_t = 0 90 | cls.am_anim_x_prec = Animation(0, 0.5, 0.5, xpd) 91 | cls.am_anim_a_prec = Animation(0, 0.5, 1, 0) 92 | cls.am_anim_x_new = Animation(0, 0.2, xns, 0.5) 93 | cls.am_anim_a_new = Animation(0, 0.2, 0, 1) 94 | mid = cls.am_mission_id_prec 95 | 96 | else: 97 | cls.am_t += dts 98 | cls.am_anim_x_prec.update(cls.am_t) 99 | cls.am_anim_x_new.update(cls.am_t) 100 | cls.am_anim_a_prec.update(cls.am_t) 101 | cls.am_anim_a_new.update(cls.am_t) 102 | 103 | mx = cls.am_anim_x_new.v 104 | mc = hg.Color(mcr, mcg, mcb, cls.am_anim_a_new.v) * fade_lvl 105 | 106 | mxprec = cls.am_anim_x_prec.v 107 | mcprec = hg.Color(mcr, mcg, mcb, cls.am_anim_a_prec.v) * fade_lvl 108 | 109 | Overlays.add_text2D( Missions.missions[cls.am_mission_id_prec].title, hg.Vec2(mxprec, 671 / 900 + yof7), 0.02, mcprec, main.hud_font, hg.DTHA_Center) 110 | mid = cls.mission_id 111 | 112 | if cls.am_anim_a_new.flag_end and cls.am_anim_x_new.flag_end and cls.am_anim_x_prec.flag_end and cls.am_anim_a_prec: 113 | cls.am_start = False 114 | 115 | Overlays.add_text2D(cls.missions[mid].title, hg.Vec2(mx, 671 / 900 + yof7), 0.02, mc, main.hud_font, hg.DTHA_Center) 116 | Overlays.add_text2D( "<- choose your mission ->", hg.Vec2(0.5, 701 / 900 + yof7), 0.012, hg.Color(1, 0.9, 0.3, fade_lvl * 0.8), main.hud_font, hg.DTHA_Center) 117 | 118 | if main.keyboard.Pressed(hg.K_Space): 119 | cls.validation_source = hg.PlayStereo(cls.validation_ref, cls.validation_state) 120 | hg.SetSourceVolume(cls.validation_source, 1) 121 | 122 | @classmethod 123 | def get_current_mission(cls): 124 | return cls.missions[cls.mission_id] 125 | 126 | @classmethod 127 | def aircrafts_starts_on_carrier(cls, aircrafts, carrier, start, y_orientation, distance, liftoff_delay=0, liftoff_offset=1): 128 | p, r = carrier.get_aircraft_start_point(0) 129 | carrier_alt = p.y 130 | carrier_mat = hg.TransformationMat4(carrier.get_parent_node().GetTransform().GetPos(), carrier.get_parent_node().GetTransform().GetRot()) 131 | for i, aircraft in enumerate(aircrafts): 132 | ia_ctrl = aircraft.get_device("IAControlDevice") 133 | if ia_ctrl is not None: 134 | ia_ctrl.IA_liftoff_delay = liftoff_delay 135 | aircraft.flag_landed = True 136 | liftoff_delay += liftoff_offset 137 | start += distance 138 | gear = aircraft.get_device("Gear") 139 | if gear is not None: 140 | bottom_height = gear.gear_height 141 | gear.record_start_state(True) 142 | gear.reset() 143 | else: 144 | bottom_height = aircraft.bottom_height 145 | start.y = carrier_alt + bottom_height 146 | mat = carrier_mat * hg.TransformationMat4(start, hg.Vec3(0, y_orientation, 0)) 147 | aircraft.reset_matrix(hg.GetT(mat), hg.GetR(mat)) 148 | aircraft.record_start_state() 149 | 150 | @classmethod 151 | def setup_aircrafts_on_carrier(cls, players, aircraft_carriers, start_time): 152 | na = len(players) 153 | nc = len(aircraft_carriers) 154 | na_row = na // (2 * nc) 155 | 156 | for i in range(nc): 157 | n0 = na_row * (2 * i) 158 | n1 = na_row * (2 * i + 1) 159 | n2 = na_row * (2 * i + 2) 160 | if na - n2 <= (na_row+1): 161 | n2 = na 162 | cls.aircrafts_starts_on_carrier(players[n0:n1], aircraft_carriers[i], hg.Vec3(10, 19.5, 80), 0, hg.Vec3(0, 0, -18), start_time + na_row * 2 * i, 2) 163 | cls.aircrafts_starts_on_carrier(players[n1:n2], aircraft_carriers[i], hg.Vec3(-10, 19.5, 62), 0, hg.Vec3(0, 0, -18), start_time + 1 + na_row * 2 * i, 2) 164 | 165 | @classmethod 166 | def aircrafts_starts_in_sky(cls, aircrafts, center: hg.Vec3, range: hg.Vec3, y_orientations_range: hg.Vec2, speed_range: hg.Vec2): 167 | for i, ac in enumerate(aircrafts): 168 | # ac.reset_matrix(hg.Vec3(uniform(center.x-range.x/2, center.x+range.x/2), uniform(center.y-range.y/2, center.y+range.y/2), uniform(center.z-range.z/2, center.z+range.z/2)), hg.Vec3(0, radians(uniform(y_orientations_range.x, y_orientations_range.y)), 0)) 169 | 170 | ac.reset_matrix(hg.Vec3(uniform(center.x - range.x / 2, center.x + range.x / 2), uniform(center.y - range.y / 2, center.y + range.y / 2), uniform(center.z - range.z / 2, center.z + range.z / 2)), hg.Vec3(0, radians(uniform(y_orientations_range.x, y_orientations_range.y)), 0)) 171 | gear = ac.get_device("Gear") 172 | if gear is not None: 173 | gear.record_start_state(False) 174 | gear.reset() 175 | ac.set_linear_speed(uniform(speed_range.x, speed_range.y)) 176 | ac.flag_landed = False 177 | ac.record_start_state() 178 | 179 | @classmethod 180 | def setup_carriers(cls, carriers, start_pos, dist, y_orientation): 181 | for carrier in carriers: 182 | carrier.reset_matrix(start_pos, hg.Vec3(0, y_orientation, 0)) 183 | start_pos += dist 184 | 185 | 186 | # ============================= Training 187 | 188 | @classmethod 189 | def mission_setup_training(cls, main): 190 | 191 | mission = cls.get_current_mission() 192 | main.create_aircraft_carriers(mission.allies_carriers, mission.ennemies_carriers) 193 | main.create_players(mission.allies, mission.ennemies) 194 | 195 | cls.setup_carriers(main.aircraft_carrier_allies, hg.Vec3(0, 0, 0), hg.Vec3(500, 0, 100), 0) 196 | 197 | n = len(main.players_allies) 198 | # if n == 1: 199 | 200 | cls.aircrafts_starts_on_carrier(main.players_allies, main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 201 | 202 | # cls.aircrafts_starts_in_sky(main.players_allies, hg.Vec3(1000, 2000, 0), hg.Vec3(1000, 1000, 20000), hg.Vec2(-180, 180), hg.Vec2(600 / 3.6, 800 / 3.6)) 203 | 204 | # elif n > 1: 205 | # cls.aircrafts_starts_on_carrier(main.players_allies[0:n // 2], main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 206 | # cls.aircrafts_starts_on_carrier(main.players_allies[n // 2:n], main.aircraft_carrier_allies[0], hg.Vec3(-10, 19.5, 60), 0, hg.Vec3(0, 0, -20)) 207 | 208 | fps_start_matrix = main.aircraft_carrier_allies[0].fps_start_point.GetTransform().GetWorld() 209 | main.camera_fps.GetTransform().SetWorld(fps_start_matrix) 210 | 211 | lt = [] 212 | for carrier in main.aircraft_carrier_allies: 213 | lt += carrier.landing_targets 214 | for ac in main.players_allies: 215 | ac.set_landing_targets(lt) 216 | ia = ac.get_device("IAControlDevice") 217 | if ia is not None: 218 | ia.activate() 219 | 220 | # ------- Missile Launcher: 221 | main.create_missile_launchers(0, 1) 222 | 223 | plateform = main.scene.GetNode("platform.S400") 224 | ml = main.missile_launchers_ennemies[0] 225 | ml.set_platform(plateform) 226 | 227 | # --------- Views setup 228 | main.setup_views_carousel(True) 229 | main.set_view_carousel("Aircraft_ally_" + str(main.num_players_allies)) 230 | main.set_track_view("back") 231 | 232 | main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) 233 | main.user_aircraft.set_focus() 234 | 235 | """ 236 | ia = main.user_aircraft.get_device("IAControlDevice") 237 | if ia is not None: 238 | ia.set_IA_landing_target(main.aircraft_carrier_allies[0].landing_targets[1]) 239 | ia.deactivate() 240 | uctrl = main.user_aircraft.get_device("UserControlDevice") 241 | if uctrl is not None: 242 | uctrl.activate() 243 | """ 244 | # Deactivate IA: 245 | for i, ac in enumerate(main.players_allies): 246 | ia = ac.get_device("IAControlDevice") 247 | if ia is not None: 248 | ia.deactivate() 249 | 250 | for i, ac in enumerate(main.players_ennemies): 251 | ia = ac.get_device("IAControlDevice") 252 | if ia is not None: 253 | ia.deactivate() 254 | 255 | uctrl = main.user_aircraft.get_device("UserControlDevice") 256 | if uctrl is not None: 257 | uctrl.activate() 258 | 259 | main.init_playground() 260 | 261 | # if main.num_players_allies < 4: 262 | # main.user_aircraft.set_thrust_level(1) 263 | # main.user_aircraft.activate_post_combustion() 264 | 265 | @classmethod 266 | def mission_training_end_test(cls, main): 267 | mission = cls.get_current_mission() 268 | allies_wreck = 0 269 | for ally in main.players_allies: 270 | if ally.wreck: allies_wreck += 1 271 | 272 | if main.num_players_allies == allies_wreck: 273 | mission.failed = True 274 | print("MISSION FAILED !") 275 | return True 276 | 277 | return False 278 | 279 | @classmethod 280 | def mission_training_end_phase_update(cls, main, dts): 281 | mission = cls.get_current_mission() 282 | if mission.failed: 283 | msg_title = "Mission Failed !" 284 | msg_debriefing = " You need more lessons commander..." 285 | elif mission.aborted: 286 | msg_title = "Training aborted !" 287 | msg_debriefing = "Ready for fight ?" 288 | else: 289 | msg_title = "Training successful !" 290 | msg_debriefing = "Congratulation commander !" 291 | Overlays.add_text2D(msg_title, hg.Vec2(0.5, 771 / 900 - 0.15), 0.04, hg.Color(1, 0.9, 0.3, 1), main.title_font, hg.DTHA_Center) 292 | Overlays.add_text2D(msg_debriefing, hg.Vec2(0.5, 701 / 900 - 0.15), 0.02, hg.Color(1, 0.9, 0.8, 1), main.hud_font, hg.DTHA_Center) 293 | 294 | # ============================= Basic fight 295 | 296 | @classmethod 297 | def mission_setup_players(cls, main): 298 | mission = cls.get_current_mission() 299 | main.create_aircraft_carriers(mission.allies_carriers, mission.ennemies_carriers) 300 | main.create_players(mission.allies, mission.ennemies) 301 | 302 | cls.setup_carriers(main.aircraft_carrier_allies, hg.Vec3(0, 0, 0), hg.Vec3(500, 0, 100), 0) 303 | cls.setup_carriers(main.aircraft_carrier_ennemies, hg.Vec3(-17000, 0, 0), hg.Vec3(500, 0, -150), radians(90)) 304 | 305 | main.init_playground() 306 | 307 | n = len(main.players_allies) 308 | if n == 1: 309 | cls.aircrafts_starts_on_carrier(main.players_allies, main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 310 | elif n > 1: 311 | cls.aircrafts_starts_on_carrier(main.players_allies[0:n // 2], main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20), 2, 2) 312 | cls.aircrafts_starts_on_carrier(main.players_allies[n // 2:n], main.aircraft_carrier_allies[0], hg.Vec3(-10, 19.5, 60), 0, hg.Vec3(0, 0, -20), 3, 2) 313 | 314 | n = len(main.players_ennemies) 315 | if n < 3: 316 | cls.aircrafts_starts_in_sky(main.players_ennemies, hg.Vec3(-5000, 1000, 0), hg.Vec3(1000, 500, 2000), hg.Vec2(-180, 180), hg.Vec2(800 / 3.6, 600 / 3.6)) 317 | else: 318 | cls.aircrafts_starts_in_sky(main.players_ennemies[0:2], hg.Vec3(-5000, 1000, 0), hg.Vec3(1000, 500, 2000), hg.Vec2(-180, 180), hg.Vec2(800 / 3.6, 600 / 3.6)) 319 | cls.aircrafts_starts_on_carrier(main.players_ennemies[2:n], main.aircraft_carrier_ennemies[0], hg.Vec3(-10, 19.5, 60), 0, hg.Vec3(0, 0, -20), 2, 1) 320 | 321 | for i, ac in enumerate(main.players_allies): 322 | ia = ac.get_device("IAControlDevice") 323 | if ia is not None: 324 | ia.activate() 325 | 326 | for i, ac in enumerate(main.players_ennemies): 327 | ia = ac.get_device("IAControlDevice") 328 | if ia is not None: 329 | ia.activate() 330 | 331 | main.setup_views_carousel(False) 332 | main.set_view_carousel("Aircraft_ally_" + str(main.num_players_allies)) 333 | main.set_track_view("back") 334 | 335 | main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) 336 | main.user_aircraft.set_focus() 337 | 338 | if main.user_aircraft is not None: 339 | ia = main.user_aircraft.get_device("IAControlDevice") 340 | if ia is not None: 341 | ia.deactivate() 342 | uctrl = main.user_aircraft.get_device("UserControlDevice") 343 | if uctrl is not None: 344 | uctrl.activate() 345 | if main.num_players_allies < 3: 346 | main.user_aircraft.reset_thrust_level(1) 347 | main.user_aircraft.activate_post_combustion() 348 | 349 | @classmethod 350 | def mission_one_against_x_end_test(cls, main): 351 | mission = cls.get_current_mission() 352 | ennemies_wreck = 0 353 | allies_wreck = 0 354 | for ennemy in main.players_ennemies: 355 | if ennemy.wreck: ennemies_wreck += 1 356 | for ally in main.players_allies: 357 | if ally.wreck: allies_wreck += 1 358 | 359 | if main.num_players_ennemies == ennemies_wreck: 360 | mission.failed = False 361 | return True 362 | if main.num_players_allies == allies_wreck: 363 | mission.failed = True 364 | return True 365 | 366 | return False 367 | 368 | @classmethod 369 | def mission_one_against_x_end_phase_update(cls, main, dts): 370 | mission = cls.get_current_mission() 371 | if mission.failed: 372 | msg_title = "Mission Failed !" 373 | msg_debriefing = " R.I.P. Commander..." 374 | elif mission.aborted: 375 | msg_title = "Mission aborted !" 376 | msg_debriefing = "We hope you do better next time !" 377 | else: 378 | msg_title = "Mission successful !" 379 | msg_debriefing = "Congratulation commander !" 380 | Overlays.add_text2D(msg_title, hg.Vec2(0.5, 771 / 900 - 0.15), 0.04, hg.Color(1, 0.9, 0.3, 1), main.title_font, hg.DTHA_Center) 381 | Overlays.add_text2D(msg_debriefing, hg.Vec2(0.5, 701 / 900 - 0.15), 0.02, hg.Color(1, 0.9, 0.8, 1), main.hud_font, hg.DTHA_Center) 382 | 383 | # ============================ War fight 384 | @classmethod 385 | def mission_total_war_setup_players(cls, main): 386 | 387 | mission = cls.get_current_mission() 388 | main.create_aircraft_carriers(mission.allies_carriers, mission.ennemies_carriers) 389 | main.create_players(mission.allies, mission.ennemies) 390 | 391 | cls.setup_carriers(main.aircraft_carrier_allies, hg.Vec3(0, 0, 0), hg.Vec3(300, 0, 25), 0) 392 | cls.setup_carriers(main.aircraft_carrier_ennemies, hg.Vec3(-20000, 0, 0), hg.Vec3(50, 0, -350), radians(90)) 393 | 394 | 395 | cls.setup_aircrafts_on_carrier(main.players_allies, main.aircraft_carrier_allies, 0) 396 | 397 | n = len(main.players_ennemies) 398 | cls.aircrafts_starts_in_sky(main.players_ennemies[0:n // 2], hg.Vec3(-8000, 1000, 0), hg.Vec3(2000, 500, 2000), hg.Vec2(-180, -175), hg.Vec2(800 / 3.6, 600 / 3.6)) 399 | 400 | cls.setup_aircrafts_on_carrier(main.players_ennemies[n//2:n], main.aircraft_carrier_ennemies, 60) 401 | 402 | main.init_playground() 403 | 404 | for i, ac in enumerate(main.players_allies): 405 | ia = ac.get_device("IAControlDevice") 406 | if ia is not None: 407 | ia.activate() 408 | 409 | for i, ac in enumerate(main.players_ennemies): 410 | ia = ac.get_device("IAControlDevice") 411 | if ia is not None: 412 | ia.activate() 413 | 414 | main.setup_views_carousel() 415 | main.set_view_carousel("Aircraft_ally_" + str(main.num_players_allies)) 416 | main.set_track_view("back") 417 | 418 | main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) 419 | main.user_aircraft.set_focus() 420 | 421 | if main.user_aircraft is not None: 422 | ia = main.user_aircraft.get_device("IAControlDevice") 423 | if ia is not None: 424 | ia.deactivate() 425 | uctrl = main.user_aircraft.get_device("UserControlDevice") 426 | if uctrl is not None: 427 | uctrl.activate() 428 | if main.num_players_allies < 4: 429 | main.user_aircraft.reset_thrust_level(1) 430 | main.user_aircraft.activate_post_combustion() 431 | 432 | @classmethod 433 | def mission_war_end_test(cls, main): 434 | mission = cls.get_current_mission() 435 | if main.keyboard.Pressed(hg.K_F6): 436 | for pl in main.players_allies: 437 | pl.flag_IA_start_liftoff = True 438 | for pl in main.players_ennemies: 439 | pl.flag_IA_start_liftoff = True 440 | # Pour le moment, même test de fin de mission 441 | return cls.mission_one_against_x_end_test(main) 442 | 443 | @classmethod 444 | def mission_war_end_phase_update(cls, main, dts): 445 | mission = cls.get_current_mission() 446 | if mission.failed: 447 | msg_title = "Mission Failed !" 448 | msg_debriefing = " R.I.P. Commander..." 449 | elif mission.aborted: 450 | msg_title = "Mission aborted !" 451 | msg_debriefing = "We hope you do better next time !" 452 | else: 453 | msg_title = "Mission successful !" 454 | msg_debriefing = "Congratulation commander !" 455 | 456 | Overlays.add_text2D(msg_title, hg.Vec2(0.5, 771 / 900 - 0.15), 0.04, hg.Color(1, 0.9, 0.3, 1), main.title_font, hg.DTHA_Center) 457 | Overlays.add_text2D(msg_debriefing, hg.Vec2(0.5, 701 / 900 - 0.15), 0.02, hg.Color(1, 0.9, 0.8, 1), main.hud_font, hg.DTHA_Center) 458 | 459 | # ============================ Client / Server mode 460 | @classmethod 461 | def network_mode_setup(cls, main): 462 | mission = cls.get_current_mission() 463 | main.flag_network_mode = True 464 | 465 | file_name = "scripts/network_mission_config.json" 466 | file = hg.OpenText(file_name) 467 | if not file: 468 | print("Can't open mission configuration json file : " + file_name) 469 | else: 470 | json_script = hg.ReadString(file) 471 | hg.Close(file) 472 | if json_script != "": 473 | script_parameters = json.loads(json_script) 474 | mission.allies = script_parameters["aircrafts_allies"] 475 | mission.ennemies = script_parameters["aircrafts_ennemies"] 476 | mission.allies_carriers = script_parameters["aircraft_carriers_allies_count"] 477 | mission.ennemies_carriers = script_parameters["aircraft_carriers_enemies_count"] 478 | else: 479 | print("Mission configuration json file empty : " + file_name) 480 | 481 | main.create_aircraft_carriers(mission.allies_carriers, mission.ennemies_carriers) 482 | main.create_players(mission.allies, mission.ennemies) 483 | 484 | cls.setup_carriers(main.aircraft_carrier_allies, hg.Vec3(0, 0, 0), hg.Vec3(500, 0, 100), 0) 485 | cls.setup_carriers(main.aircraft_carrier_ennemies, hg.Vec3(-17000, 0, 0), hg.Vec3(500, 0, -150), radians(90)) 486 | 487 | main.init_playground() 488 | 489 | # Sets aircrafts landed on carriers: 490 | 491 | n = len(main.players_allies) 492 | if n == 1: 493 | cls.aircrafts_starts_on_carrier(main.players_allies, main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 494 | elif n > 1: 495 | cls.aircrafts_starts_on_carrier(main.players_allies[0:n // 2], main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20), 2, 2) 496 | cls.aircrafts_starts_on_carrier(main.players_allies[n // 2:n], main.aircraft_carrier_allies[0], hg.Vec3(-10, 19.5, 60), 0, hg.Vec3(0, 0, -20), 3, 2) 497 | 498 | n = len(main.players_ennemies) 499 | if n == 1: 500 | cls.aircrafts_starts_on_carrier(main.players_ennemies, main.aircraft_carrier_ennemies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 501 | elif n > 1: 502 | cls.aircrafts_starts_on_carrier(main.players_ennemies[0:n // 2], main.aircraft_carrier_ennemies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20), 2, 2) 503 | cls.aircrafts_starts_on_carrier(main.players_ennemies[n // 2:n], main.aircraft_carrier_ennemies[0], hg.Vec3(-10, 19.5, 60), 0, hg.Vec3(0, 0, -20), 3, 2) 504 | 505 | # Deactivate IA: 506 | for i, ac in enumerate(main.players_allies): 507 | ia = ac.get_device("IAControlDevice") 508 | if ia is not None: 509 | ia.deactivate() 510 | 511 | for i, ac in enumerate(main.players_ennemies): 512 | ia = ac.get_device("IAControlDevice") 513 | if ia is not None: 514 | ia.deactivate() 515 | 516 | # ------- Missile Launcher: 517 | main.create_missile_launchers(0, 1) 518 | 519 | plateform = main.scene.GetNode("platform.S400") 520 | ml = main.missile_launchers_ennemies[0] 521 | ml.set_platform(plateform) 522 | 523 | # --------- Views setup 524 | main.setup_views_carousel(True) 525 | main.set_view_carousel("Aircraft_ally_1")# + str(main.num_players_allies)) 526 | main.set_track_view("back") 527 | 528 | main.init_playground() 529 | 530 | main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) 531 | main.user_aircraft.set_focus() 532 | 533 | uctrl = main.user_aircraft.get_device("UserControlDevice") 534 | if uctrl is not None: 535 | uctrl.activate() 536 | 537 | netws.init_server(main) 538 | netws.start_server() 539 | 540 | @classmethod 541 | def network_mode_end_test(cls, main): 542 | """ 543 | mission = cls.get_current_mission() 544 | 545 | allies_wreck = 0 546 | for ally in main.players_allies: 547 | if ally.wreck: 548 | allies_wreck += 1 549 | 550 | if main.num_players_allies == allies_wreck: 551 | mission.failed = True 552 | return True 553 | """ 554 | return False 555 | 556 | @classmethod 557 | def network_mode_end_phase_update(cls, main, dts): 558 | if main.flag_network_mode: 559 | main.flag_network_mode = False 560 | netws.stop_server() 561 | mission = cls.get_current_mission() 562 | if mission.failed: 563 | msg_title = "Network aborted !" 564 | msg_debriefing = " Aircraft destroyed, TAB to restart" 565 | elif mission.aborted: 566 | msg_title = "Network aborted !" 567 | msg_debriefing = "TAB to restart" 568 | else: 569 | msg_title = "Successful !" 570 | msg_debriefing = "Congratulation commander !" 571 | Overlays.add_text2D(msg_title, hg.Vec2(0.5, 771 / 900 - 0.15), 0.04, hg.Color(1, 0.9, 0.3, 1), main.title_font, hg.DTHA_Center) 572 | Overlays.add_text2D(msg_debriefing, hg.Vec2(0.5, 701 / 900 - 0.15), 0.02, hg.Color(1, 0.9, 0.8, 1), main.hud_font, hg.DTHA_Center) 573 | 574 | @classmethod 575 | def init(cls): 576 | cls.beep_ref = hg.LoadWAVSoundAsset("sfx/blip.wav") 577 | cls.beep_state = tools.create_stereo_sound_state(hg.SR_Once) 578 | cls.beep_state.volume = 0.25 579 | 580 | cls.validation_ref = hg.LoadWAVSoundAsset("sfx/blip2.wav") 581 | cls.validation_state = tools.create_stereo_sound_state(hg.SR_Once) 582 | cls.validation_state.volume = 0.5 583 | 584 | 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)) 585 | 586 | 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)) 587 | 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)) 588 | cls.missions.append(Mission("Training with TFX", [], ["TFX"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 589 | cls.missions.append(Mission("Training with F16", [], ["F16"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 590 | cls.missions.append(Mission("Training with Miuss", [], ["Miuss"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 591 | #cls.missions.append(Mission("Training with F14", [], ["F14"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 592 | #cls.missions.append(Mission("Training with F14 2", [], ["F14_2"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 593 | 594 | cls.missions.append(Mission("One on one", ["Rafale"], ["Eurofighter"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 595 | cls.missions.append(Mission("Fight against 2 ennemies", ["Rafale"] * 2, ["Eurofighter"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 596 | cls.missions.append(Mission("Fight against 3 ennemies", ["Rafale"] * 1 + ["F16"] * 2, ["Eurofighter"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 597 | cls.missions.append(Mission("Fight against 4 ennemies", ["Rafale"] * 2 + ["F16"] * 2, ["TFX", "Eurofighter"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 598 | cls.missions.append(Mission("Fight against 5 ennemies", ["Rafale"] * 5, ["TFX", "Eurofighter", "F16"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 599 | 600 | cls.missions.append(Mission("War: 5 allies against 5 ennemies", ["Rafale"] * 3 + ["Eurofighter"] * 2, ["TFX"] * 2 + ["F16"] * 2 + ["Eurofighter"] * 1, 1, 1, Missions.mission_total_war_setup_players, Missions.mission_war_end_test, Missions.mission_war_end_phase_update)) 601 | #cls.missions.append(Mission("Total War: 12 allies against 12 ennemies", ["Rafale"] * 12, ["TFX"] * 4 + ["Eurofighter"] * 4 + ["F16"] * 4 + ["Eurofighter"] * 4, 2, 2, Missions.mission_total_war_setup_players, Missions.mission_war_end_test, Missions.mission_war_end_phase_update)) 602 | #cls.missions.append(Mission("Crash test: 60 allies against 60 ennemies", ["Rafale"] * 30 + ["Eurofighter"] * 30, ["TFX"] * 30 + ["Eurofighter"] * 20 + ["F16"] * 10, 5, 5, Missions.mission_total_war_setup_players, Missions.mission_war_end_test, Missions.mission_war_end_phase_update)) 603 | -------------------------------------------------------------------------------- /bak/Missions_evade.bak: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018-2021 Eric Kernin, NWNC HARFANG. 2 | 3 | import harfang as hg 4 | from Animations import * 5 | import tools 6 | from random import uniform 7 | from math import radians 8 | import json 9 | import network_server as netws 10 | from overlays import * 11 | from MachineDevice import * 12 | 13 | class Mission: 14 | 15 | def __init__(self, title, num_ennemies, num_allies, num_carriers_ennemies, num_carriers_allies, setup_players, end_test, end_phase_update): 16 | self.title = title 17 | self.ennemies = num_ennemies 18 | self.allies = num_allies 19 | self.allies_carriers = num_carriers_allies 20 | self.ennemies_carriers = num_carriers_ennemies 21 | self.setup_players_f = setup_players 22 | self.end_test_f = end_test 23 | self.update_end_phase_f = end_phase_update 24 | self.failed = False 25 | self.aborted = False 26 | 27 | def reset(self): 28 | self.failed = False 29 | self.aborted = False 30 | 31 | def setup_players(self, main): 32 | self.setup_players_f(main) 33 | 34 | def end_test(self, main): 35 | return self.end_test_f(main) 36 | 37 | def update_end_phase(self, main, dts): 38 | self.update_end_phase_f(main, dts) 39 | 40 | class Missions: 41 | beep_ref = None 42 | beep_state = None 43 | beep_source = None 44 | 45 | validation_ref = None 46 | validation_state = None 47 | validation_source = None 48 | 49 | # animations mission: 50 | am_start = False 51 | am_anim_x_prec = None 52 | am_anim_a_prec = None 53 | am_anim_x_new = None 54 | am_anim_a_new = None 55 | am_mission_id_prec = 0 56 | am_t = 0 57 | 58 | missions = [] 59 | mission_id = 1 60 | 61 | @classmethod 62 | def display_mission_title(cls, main, fade_lvl, dts, yof7): 63 | mcr = 1 64 | mcg = 0.6 65 | mcb = 0.1 66 | 67 | if not cls.am_start: 68 | mx = 0.5 69 | mc = hg.Color(mcr, mcg, mcb, 1) * fade_lvl 70 | mid = cls.mission_id 71 | 72 | if cls.mission_id < len(cls.missions) - 1 and main.keyboard.Pressed(hg.K_Right): 73 | cls.am_start = True 74 | mcpt = 1 75 | xpd = 0 76 | xns = 1 77 | 78 | elif cls.mission_id > 0 and main.keyboard.Pressed(hg.K_Left): 79 | cls.am_start = True 80 | mcpt = -1 81 | xpd = 1 82 | xns = 0 83 | 84 | if cls.am_start: 85 | cls.beep_source = hg.PlayStereo(cls.beep_ref, cls.beep_state) 86 | hg.SetSourceVolume(cls.beep_source, 0.05) 87 | cls.am_mission_id_prec = cls.mission_id 88 | cls.mission_id += mcpt 89 | cls.am_t = 0 90 | cls.am_anim_x_prec = Animation(0, 0.5, 0.5, xpd) 91 | cls.am_anim_a_prec = Animation(0, 0.5, 1, 0) 92 | cls.am_anim_x_new = Animation(0, 0.2, xns, 0.5) 93 | cls.am_anim_a_new = Animation(0, 0.2, 0, 1) 94 | mid = cls.am_mission_id_prec 95 | 96 | else: 97 | cls.am_t += dts 98 | cls.am_anim_x_prec.update(cls.am_t) 99 | cls.am_anim_x_new.update(cls.am_t) 100 | cls.am_anim_a_prec.update(cls.am_t) 101 | cls.am_anim_a_new.update(cls.am_t) 102 | 103 | mx = cls.am_anim_x_new.v 104 | mc = hg.Color(mcr, mcg, mcb, cls.am_anim_a_new.v) * fade_lvl 105 | 106 | mxprec = cls.am_anim_x_prec.v 107 | mcprec = hg.Color(mcr, mcg, mcb, cls.am_anim_a_prec.v) * fade_lvl 108 | 109 | Overlays.add_text2D( Missions.missions[cls.am_mission_id_prec].title, hg.Vec2(mxprec, 671 / 900 + yof7), 0.02, mcprec, main.hud_font, hg.DTHA_Center) 110 | mid = cls.mission_id 111 | 112 | if cls.am_anim_a_new.flag_end and cls.am_anim_x_new.flag_end and cls.am_anim_x_prec.flag_end and cls.am_anim_a_prec: 113 | cls.am_start = False 114 | 115 | Overlays.add_text2D(cls.missions[mid].title, hg.Vec2(mx, 671 / 900 + yof7), 0.02, mc, main.hud_font, hg.DTHA_Center) 116 | Overlays.add_text2D( "<- choose your mission ->", hg.Vec2(0.5, 701 / 900 + yof7), 0.012, hg.Color(1, 0.9, 0.3, fade_lvl * 0.8), main.hud_font, hg.DTHA_Center) 117 | 118 | if main.keyboard.Pressed(hg.K_Space): 119 | cls.validation_source = hg.PlayStereo(cls.validation_ref, cls.validation_state) 120 | hg.SetSourceVolume(cls.validation_source, 1) 121 | 122 | @classmethod 123 | def get_current_mission(cls): 124 | return cls.missions[cls.mission_id] 125 | 126 | @classmethod 127 | def aircrafts_starts_on_carrier(cls, aircrafts, carrier, start, y_orientation, distance, liftoff_delay=0, liftoff_offset=1): 128 | p, r = carrier.get_aircraft_start_point(0) 129 | carrier_alt = p.y 130 | carrier_mat = hg.TransformationMat4(carrier.get_parent_node().GetTransform().GetPos(), carrier.get_parent_node().GetTransform().GetRot()) 131 | for i, aircraft in enumerate(aircrafts): 132 | ia_ctrl = aircraft.get_device("IAControlDevice") 133 | if ia_ctrl is not None: 134 | ia_ctrl.IA_liftoff_delay = liftoff_delay 135 | aircraft.flag_landed = True 136 | liftoff_delay += liftoff_offset 137 | start += distance 138 | gear = aircraft.get_device("Gear") 139 | if gear is not None: 140 | bottom_height = gear.gear_height 141 | gear.record_start_state(True) 142 | gear.reset() 143 | else: 144 | bottom_height = aircraft.bottom_height 145 | start.y = carrier_alt + bottom_height 146 | mat = carrier_mat * hg.TransformationMat4(start, hg.Vec3(0, y_orientation, 0)) 147 | aircraft.reset_matrix(hg.GetT(mat), hg.GetR(mat)) 148 | aircraft.record_start_state() 149 | 150 | @classmethod 151 | def setup_aircrafts_on_carrier(cls, players, aircraft_carriers, start_time): 152 | na = len(players) 153 | nc = len(aircraft_carriers) 154 | na_row = na // (2 * nc) 155 | 156 | for i in range(nc): 157 | n0 = na_row * (2 * i) 158 | n1 = na_row * (2 * i + 1) 159 | n2 = na_row * (2 * i + 2) 160 | if na - n2 <= (na_row+1): 161 | n2 = na 162 | cls.aircrafts_starts_on_carrier(players[n0:n1], aircraft_carriers[i], hg.Vec3(10, 19.5, 80), 0, hg.Vec3(0, 0, -18), start_time + na_row * 2 * i, 2) 163 | cls.aircrafts_starts_on_carrier(players[n1:n2], aircraft_carriers[i], hg.Vec3(-10, 19.5, 62), 0, hg.Vec3(0, 0, -18), start_time + 1 + na_row * 2 * i, 2) 164 | 165 | @classmethod 166 | def aircrafts_starts_in_sky(cls, aircrafts, center: hg.Vec3, range: hg.Vec3, y_orientations_range: hg.Vec2, speed_range: hg.Vec2): 167 | for i, ac in enumerate(aircrafts): 168 | # ac.reset_matrix(hg.Vec3(uniform(center.x-range.x/2, center.x+range.x/2), uniform(center.y-range.y/2, center.y+range.y/2), uniform(center.z-range.z/2, center.z+range.z/2)), hg.Vec3(0, radians(uniform(y_orientations_range.x, y_orientations_range.y)), 0)) 169 | 170 | ac.reset_matrix( 171 | hg.Vec3( 172 | uniform(center.x - range.x / 2, center.x + range.x / 2), 173 | uniform(center.y - range.y / 2, center.y + range.y / 2), 174 | uniform(center.z - range.z / 2, center.z + range.z / 2) 175 | ), 176 | hg.Vec3( 177 | 0, 178 | radians( 179 | uniform(y_orientations_range.x, y_orientations_range.y) 180 | ), 181 | 0 182 | ) 183 | ) 184 | gear = ac.get_device("Gear") 185 | if gear is not None: 186 | gear.record_start_state(False) 187 | gear.reset() 188 | ac.set_linear_speed(uniform(speed_range.x, speed_range.y)) 189 | ac.flag_landed = False 190 | ac.record_start_state() 191 | 192 | @classmethod 193 | def setup_carriers(cls, carriers, start_pos, dist, y_orientation): 194 | for carrier in carriers: 195 | carrier.reset_matrix(start_pos, hg.Vec3(0, y_orientation, 0)) 196 | start_pos += dist 197 | 198 | 199 | # ============================= Training 200 | 201 | @classmethod 202 | def mission_setup_training(cls, main): 203 | 204 | mission = cls.get_current_mission() 205 | main.create_aircraft_carriers(mission.allies_carriers, mission.ennemies_carriers) 206 | main.create_players(mission.allies, mission.ennemies) 207 | 208 | cls.setup_carriers(main.aircraft_carrier_allies, hg.Vec3(0, 0, 0), hg.Vec3(500, 0, 100), 0) 209 | 210 | n = len(main.players_allies) 211 | # if n == 1: 212 | 213 | cls.aircrafts_starts_on_carrier(main.players_allies, main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 214 | 215 | # cls.aircrafts_starts_in_sky(main.players_allies, hg.Vec3(1000, 2000, 0), hg.Vec3(1000, 1000, 20000), hg.Vec2(-180, 180), hg.Vec2(600 / 3.6, 800 / 3.6)) 216 | 217 | # elif n > 1: 218 | # cls.aircrafts_starts_on_carrier(main.players_allies[0:n // 2], main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 219 | # cls.aircrafts_starts_on_carrier(main.players_allies[n // 2:n], main.aircraft_carrier_allies[0], hg.Vec3(-10, 19.5, 60), 0, hg.Vec3(0, 0, -20)) 220 | 221 | fps_start_matrix = main.aircraft_carrier_allies[0].fps_start_point.GetTransform().GetWorld() 222 | main.camera_fps.GetTransform().SetWorld(fps_start_matrix) 223 | 224 | lt = [] 225 | for carrier in main.aircraft_carrier_allies: 226 | lt += carrier.landing_targets 227 | for ac in main.players_allies: 228 | ac.set_landing_targets(lt) 229 | ia = ac.get_device("IAControlDevice") 230 | if ia is not None: 231 | ia.activate() 232 | 233 | # ------- Missile Launcher: 234 | main.create_missile_launchers(0, 1) 235 | 236 | plateform = main.scene.GetNode("platform.S400") 237 | ml = main.missile_launchers_ennemies[0] 238 | ml.set_platform(plateform) 239 | 240 | # --------- Views setup 241 | main.setup_views_carousel(True) 242 | main.set_view_carousel("Aircraft_ally_" + str(main.num_players_allies)) 243 | main.set_track_view("back") 244 | 245 | main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) 246 | main.user_aircraft.set_focus() 247 | 248 | """ 249 | ia = main.user_aircraft.get_device("IAControlDevice") 250 | if ia is not None: 251 | ia.set_IA_landing_target(main.aircraft_carrier_allies[0].landing_targets[1]) 252 | ia.deactivate() 253 | uctrl = main.user_aircraft.get_device("UserControlDevice") 254 | if uctrl is not None: 255 | uctrl.activate() 256 | """ 257 | # Deactivate IA: 258 | for i, ac in enumerate(main.players_allies): 259 | ia = ac.get_device("IAControlDevice") 260 | if ia is not None: 261 | ia.deactivate() 262 | 263 | for i, ac in enumerate(main.players_ennemies): 264 | ia = ac.get_device("IAControlDevice") 265 | if ia is not None: 266 | ia.deactivate() 267 | 268 | uctrl = main.user_aircraft.get_device("UserControlDevice") 269 | if uctrl is not None: 270 | uctrl.activate() 271 | 272 | main.init_playground() 273 | 274 | # if main.num_players_allies < 4: 275 | # main.user_aircraft.set_thrust_level(1) 276 | # main.user_aircraft.activate_post_combustion() 277 | 278 | @classmethod 279 | def mission_training_end_test(cls, main): 280 | mission = cls.get_current_mission() 281 | allies_wreck = 0 282 | for ally in main.players_allies: 283 | if ally.wreck: allies_wreck += 1 284 | 285 | if main.num_players_allies == allies_wreck: 286 | mission.failed = True 287 | print("MISSION FAILED !") 288 | return True 289 | 290 | return False 291 | 292 | @classmethod 293 | def mission_training_end_phase_update(cls, main, dts): 294 | mission = cls.get_current_mission() 295 | if mission.failed: 296 | msg_title = "Mission Failed !" 297 | msg_debriefing = " You need more lessons commander..." 298 | elif mission.aborted: 299 | msg_title = "Training aborted !" 300 | msg_debriefing = "Ready for fight ?" 301 | else: 302 | msg_title = "Training successful !" 303 | msg_debriefing = "Congratulation commander !" 304 | Overlays.add_text2D(msg_title, hg.Vec2(0.5, 771 / 900 - 0.15), 0.04, hg.Color(1, 0.9, 0.3, 1), main.title_font, hg.DTHA_Center) 305 | Overlays.add_text2D(msg_debriefing, hg.Vec2(0.5, 701 / 900 - 0.15), 0.02, hg.Color(1, 0.9, 0.8, 1), main.hud_font, hg.DTHA_Center) 306 | 307 | # ============================= Basic fight 308 | 309 | @classmethod 310 | def mission_setup_players(cls, main): 311 | mission = cls.get_current_mission() 312 | main.create_aircraft_carriers(mission.allies_carriers, mission.ennemies_carriers) 313 | main.create_players(mission.allies, mission.ennemies) 314 | 315 | cls.setup_carriers(main.aircraft_carrier_allies, hg.Vec3(0, 0, 0), hg.Vec3(500, 0, 100), 0) 316 | cls.setup_carriers(main.aircraft_carrier_ennemies, hg.Vec3(-17000, 0, 0), hg.Vec3(500, 0, -150), radians(90)) 317 | 318 | main.init_playground() 319 | 320 | n = len(main.players_allies) 321 | if n == 1: 322 | cls.aircrafts_starts_on_carrier(main.players_allies, main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 323 | elif n > 1: 324 | cls.aircrafts_starts_on_carrier(main.players_allies[0:n // 2], main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20), 2, 2) 325 | cls.aircrafts_starts_on_carrier(main.players_allies[n // 2:n], main.aircraft_carrier_allies[0], hg.Vec3(-10, 19.5, 60), 0, hg.Vec3(0, 0, -20), 3, 2) 326 | 327 | n = len(main.players_ennemies) 328 | if n < 3: 329 | cls.aircrafts_starts_in_sky(main.players_ennemies, hg.Vec3(-5000, 1000, 0), hg.Vec3(1000, 500, 2000), hg.Vec2(-180, 180), hg.Vec2(800 / 3.6, 600 / 3.6)) 330 | else: 331 | cls.aircrafts_starts_in_sky(main.players_ennemies[0:2], hg.Vec3(-5000, 1000, 0), hg.Vec3(1000, 500, 2000), hg.Vec2(-180, 180), hg.Vec2(800 / 3.6, 600 / 3.6)) 332 | cls.aircrafts_starts_on_carrier(main.players_ennemies[2:n], main.aircraft_carrier_ennemies[0], hg.Vec3(-10, 19.5, 60), 0, hg.Vec3(0, 0, -20), 2, 1) 333 | 334 | for i, ac in enumerate(main.players_allies): 335 | ia = ac.get_device("IAControlDevice") 336 | if ia is not None: 337 | ia.activate() 338 | 339 | for i, ac in enumerate(main.players_ennemies): 340 | ia = ac.get_device("IAControlDevice") 341 | if ia is not None: 342 | ia.activate() 343 | 344 | main.setup_views_carousel(False) 345 | main.set_view_carousel("Aircraft_ally_" + str(main.num_players_allies)) 346 | main.set_track_view("back") 347 | 348 | main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) 349 | main.user_aircraft.set_focus() 350 | 351 | if main.user_aircraft is not None: 352 | ia = main.user_aircraft.get_device("IAControlDevice") 353 | if ia is not None: 354 | ia.deactivate() 355 | uctrl = main.user_aircraft.get_device("UserControlDevice") 356 | if uctrl is not None: 357 | uctrl.activate() 358 | if main.num_players_allies < 3: 359 | main.user_aircraft.reset_thrust_level(1) 360 | main.user_aircraft.activate_post_combustion() 361 | 362 | @classmethod 363 | def mission_one_against_x_end_test(cls, main): 364 | mission = cls.get_current_mission() 365 | ennemies_wreck = 0 366 | allies_wreck = 0 367 | for ennemy in main.players_ennemies: 368 | if ennemy.wreck: ennemies_wreck += 1 369 | for ally in main.players_allies: 370 | if ally.wreck: allies_wreck += 1 371 | 372 | if main.num_players_ennemies == ennemies_wreck: 373 | mission.failed = False 374 | return True 375 | if main.num_players_allies == allies_wreck: 376 | mission.failed = True 377 | return True 378 | 379 | return False 380 | 381 | @classmethod 382 | def mission_one_against_x_end_phase_update(cls, main, dts): 383 | mission = cls.get_current_mission() 384 | if mission.failed: 385 | msg_title = "Mission Failed !" 386 | msg_debriefing = " R.I.P. Commander..." 387 | elif mission.aborted: 388 | msg_title = "Mission aborted !" 389 | msg_debriefing = "We hope you do better next time !" 390 | else: 391 | msg_title = "Mission successful !" 392 | msg_debriefing = "Congratulation commander !" 393 | Overlays.add_text2D(msg_title, hg.Vec2(0.5, 771 / 900 - 0.15), 0.04, hg.Color(1, 0.9, 0.3, 1), main.title_font, hg.DTHA_Center) 394 | Overlays.add_text2D(msg_debriefing, hg.Vec2(0.5, 701 / 900 - 0.15), 0.02, hg.Color(1, 0.9, 0.8, 1), main.hud_font, hg.DTHA_Center) 395 | 396 | # ============================ War fight 397 | @classmethod 398 | def mission_total_war_setup_players(cls, main): 399 | 400 | mission = cls.get_current_mission() 401 | main.create_aircraft_carriers(mission.allies_carriers, mission.ennemies_carriers) 402 | main.create_players(mission.allies, mission.ennemies) 403 | 404 | cls.setup_carriers(main.aircraft_carrier_allies, hg.Vec3(0, 0, 0), hg.Vec3(300, 0, 25), 0) 405 | cls.setup_carriers(main.aircraft_carrier_ennemies, hg.Vec3(-20000, 0, 0), hg.Vec3(50, 0, -350), radians(90)) 406 | 407 | 408 | cls.setup_aircrafts_on_carrier(main.players_allies, main.aircraft_carrier_allies, 0) 409 | 410 | n = len(main.players_ennemies) 411 | cls.aircrafts_starts_in_sky(main.players_ennemies[0:n // 2], hg.Vec3(-8000, 1000, 0), hg.Vec3(2000, 500, 2000), hg.Vec2(-180, -175), hg.Vec2(800 / 3.6, 600 / 3.6)) 412 | 413 | cls.setup_aircrafts_on_carrier(main.players_ennemies[n//2:n], main.aircraft_carrier_ennemies, 60) 414 | 415 | main.init_playground() 416 | 417 | for i, ac in enumerate(main.players_allies): 418 | ia = ac.get_device("IAControlDevice") 419 | if ia is not None: 420 | ia.activate() 421 | 422 | for i, ac in enumerate(main.players_ennemies): 423 | ia = ac.get_device("IAControlDevice") 424 | if ia is not None: 425 | ia.activate() 426 | 427 | main.setup_views_carousel() 428 | main.set_view_carousel("Aircraft_ally_" + str(main.num_players_allies)) 429 | main.set_track_view("back") 430 | 431 | main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) 432 | main.user_aircraft.set_focus() 433 | 434 | if main.user_aircraft is not None: 435 | ia = main.user_aircraft.get_device("IAControlDevice") 436 | if ia is not None: 437 | ia.deactivate() 438 | uctrl = main.user_aircraft.get_device("UserControlDevice") 439 | if uctrl is not None: 440 | uctrl.activate() 441 | if main.num_players_allies < 4: 442 | main.user_aircraft.reset_thrust_level(1) 443 | main.user_aircraft.activate_post_combustion() 444 | 445 | @classmethod 446 | def mission_war_end_test(cls, main): 447 | mission = cls.get_current_mission() 448 | if main.keyboard.Pressed(hg.K_F6): 449 | for pl in main.players_allies: 450 | pl.flag_IA_start_liftoff = True 451 | for pl in main.players_ennemies: 452 | pl.flag_IA_start_liftoff = True 453 | # Pour le moment, même test de fin de mission 454 | return cls.mission_one_against_x_end_test(main) 455 | 456 | @classmethod 457 | def mission_war_end_phase_update(cls, main, dts): 458 | mission = cls.get_current_mission() 459 | if mission.failed: 460 | msg_title = "Mission Failed !" 461 | msg_debriefing = " R.I.P. Commander..." 462 | elif mission.aborted: 463 | msg_title = "Mission aborted !" 464 | msg_debriefing = "We hope you do better next time !" 465 | else: 466 | msg_title = "Mission successful !" 467 | msg_debriefing = "Congratulation commander !" 468 | 469 | Overlays.add_text2D(msg_title, hg.Vec2(0.5, 771 / 900 - 0.15), 0.04, hg.Color(1, 0.9, 0.3, 1), main.title_font, hg.DTHA_Center) 470 | Overlays.add_text2D(msg_debriefing, hg.Vec2(0.5, 701 / 900 - 0.15), 0.02, hg.Color(1, 0.9, 0.8, 1), main.hud_font, hg.DTHA_Center) 471 | 472 | # ============================ Client / Server mode 473 | @classmethod 474 | def network_mode_setup(cls, main): 475 | mission = cls.get_current_mission() 476 | main.flag_network_mode = True 477 | 478 | file_name = "scripts/network_mission_config.json" 479 | file = hg.OpenText(file_name) 480 | if not file: 481 | print("Can't open mission configuration json file : " + file_name) 482 | else: 483 | json_script = hg.ReadString(file) 484 | hg.Close(file) 485 | if json_script != "": 486 | script_parameters = json.loads(json_script) 487 | mission.allies = script_parameters["aircrafts_allies"] 488 | mission.ennemies = script_parameters["aircrafts_ennemies"] 489 | mission.allies_carriers = script_parameters["aircraft_carriers_allies_count"] 490 | mission.ennemies_carriers = script_parameters["aircraft_carriers_enemies_count"] 491 | else: 492 | print("Mission configuration json file empty : " + file_name) 493 | 494 | main.create_aircraft_carriers(mission.allies_carriers, mission.ennemies_carriers) 495 | main.create_players(mission.allies, mission.ennemies) 496 | 497 | cls.setup_carriers(main.aircraft_carrier_allies, hg.Vec3(0, 0, 0), hg.Vec3(500, 0, 100), 0) 498 | cls.setup_carriers(main.aircraft_carrier_ennemies, hg.Vec3(-17000, 0, 0), hg.Vec3(500, 0, -150), radians(90)) 499 | 500 | main.init_playground() 501 | 502 | # Sets aircrafts landed on carriers: 503 | 504 | n = len(main.players_allies) 505 | if n == 1: 506 | cls.aircrafts_starts_on_carrier(main.players_allies, main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 507 | elif n > 1: 508 | cls.aircrafts_starts_on_carrier(main.players_allies[0:n // 2], main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20), 2, 2) 509 | cls.aircrafts_starts_in_sky( 510 | aircrafts=main.players_allies[n // 2:n], 511 | center=hg.Vec3(-10, 1500, 60), # (x, z, y), recommend height 1500 512 | # range=hg.Vec3(-10, 19.5, 60), 513 | range=hg.Vec3(0, 0, 0), 514 | y_orientations_range=hg.Vec2(270, 270), # heading 515 | speed_range=hg.Vec2(400, 400) # m/s, recommend 400 516 | ) 517 | 518 | n = len(main.players_ennemies) 519 | if n == 1: 520 | cls.aircrafts_starts_on_carrier(main.players_ennemies, main.aircraft_carrier_ennemies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 521 | elif n > 1: 522 | cls.aircrafts_starts_on_carrier(main.players_ennemies[0:n // 2], main.aircraft_carrier_ennemies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20), 2, 2) 523 | cls.aircrafts_starts_in_sky( 524 | aircrafts=main.players_ennemies[n // 2:n], 525 | center=hg.Vec3(4000, 1500, 60), # (x, z, y), recommend height 1500 526 | # range=hg.Vec3(-10, 19.5, 60), 527 | range=hg.Vec3(0, 0, 0), 528 | y_orientations_range=hg.Vec2(270, 270), # heading 529 | speed_range=hg.Vec2(400, 400) # m/s, recommend 400 530 | ) 531 | 532 | # Deactivate IA: 533 | for i, ac in enumerate(main.players_allies): 534 | ia = ac.get_device("IAControlDevice") 535 | if ia is not None: 536 | ia.deactivate() 537 | 538 | for i, ac in enumerate(main.players_ennemies): 539 | ia = ac.get_device("IAControlDevice") 540 | if ia is not None: 541 | ia.deactivate() 542 | 543 | # ------- Missile Launcher: 544 | main.create_missile_launchers(0, 1) 545 | 546 | plateform = main.scene.GetNode("platform.S400") 547 | ml = main.missile_launchers_ennemies[0] 548 | ml.set_platform(plateform) 549 | 550 | # --------- Views setup 551 | main.setup_views_carousel(True) 552 | main.set_view_carousel("Aircraft_ally_1")# + str(main.num_players_allies)) 553 | main.set_track_view("back") 554 | 555 | main.init_playground() 556 | 557 | main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) 558 | main.user_aircraft.set_focus() 559 | 560 | uctrl = main.user_aircraft.get_device("UserControlDevice") 561 | if uctrl is not None: 562 | uctrl.activate() 563 | 564 | netws.init_server(main) 565 | netws.start_server() 566 | 567 | @classmethod 568 | def network_mode_end_test(cls, main): 569 | """ 570 | mission = cls.get_current_mission() 571 | 572 | allies_wreck = 0 573 | for ally in main.players_allies: 574 | if ally.wreck: 575 | allies_wreck += 1 576 | 577 | if main.num_players_allies == allies_wreck: 578 | mission.failed = True 579 | return True 580 | """ 581 | return False 582 | 583 | @classmethod 584 | def network_mode_end_phase_update(cls, main, dts): 585 | if main.flag_network_mode: 586 | main.flag_network_mode = False 587 | netws.stop_server() 588 | mission = cls.get_current_mission() 589 | if mission.failed: 590 | msg_title = "Network aborted !" 591 | msg_debriefing = " Aircraft destroyed, TAB to restart" 592 | elif mission.aborted: 593 | msg_title = "Network aborted !" 594 | msg_debriefing = "TAB to restart" 595 | else: 596 | msg_title = "Successful !" 597 | msg_debriefing = "Congratulation commander !" 598 | Overlays.add_text2D(msg_title, hg.Vec2(0.5, 771 / 900 - 0.15), 0.04, hg.Color(1, 0.9, 0.3, 1), main.title_font, hg.DTHA_Center) 599 | Overlays.add_text2D(msg_debriefing, hg.Vec2(0.5, 701 / 900 - 0.15), 0.02, hg.Color(1, 0.9, 0.8, 1), main.hud_font, hg.DTHA_Center) 600 | 601 | @classmethod 602 | def init(cls): 603 | cls.beep_ref = hg.LoadWAVSoundAsset("sfx/blip.wav") 604 | cls.beep_state = tools.create_stereo_sound_state(hg.SR_Once) 605 | cls.beep_state.volume = 0.25 606 | 607 | cls.validation_ref = hg.LoadWAVSoundAsset("sfx/blip2.wav") 608 | cls.validation_state = tools.create_stereo_sound_state(hg.SR_Once) 609 | cls.validation_state.volume = 0.5 610 | 611 | 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)) 612 | 613 | 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)) 614 | 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)) 615 | cls.missions.append(Mission("Training with TFX", [], ["TFX"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 616 | cls.missions.append(Mission("Training with F16", [], ["F16"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 617 | cls.missions.append(Mission("Training with Miuss", [], ["Miuss"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 618 | #cls.missions.append(Mission("Training with F14", [], ["F14"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 619 | #cls.missions.append(Mission("Training with F14 2", [], ["F14_2"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 620 | 621 | cls.missions.append(Mission("One on one", ["Rafale"], ["Eurofighter"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 622 | cls.missions.append(Mission("Fight against 2 ennemies", ["Rafale"] * 2, ["Eurofighter"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 623 | cls.missions.append(Mission("Fight against 3 ennemies", ["Rafale"] * 1 + ["F16"] * 2, ["Eurofighter"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 624 | cls.missions.append(Mission("Fight against 4 ennemies", ["Rafale"] * 2 + ["F16"] * 2, ["TFX", "Eurofighter"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 625 | cls.missions.append(Mission("Fight against 5 ennemies", ["Rafale"] * 5, ["TFX", "Eurofighter", "F16"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 626 | 627 | cls.missions.append(Mission("War: 5 allies against 5 ennemies", ["Rafale"] * 3 + ["Eurofighter"] * 2, ["TFX"] * 2 + ["F16"] * 2 + ["Eurofighter"] * 1, 1, 1, Missions.mission_total_war_setup_players, Missions.mission_war_end_test, Missions.mission_war_end_phase_update)) 628 | #cls.missions.append(Mission("Total War: 12 allies against 12 ennemies", ["Rafale"] * 12, ["TFX"] * 4 + ["Eurofighter"] * 4 + ["F16"] * 4 + ["Eurofighter"] * 4, 2, 2, Missions.mission_total_war_setup_players, Missions.mission_war_end_test, Missions.mission_war_end_phase_update)) 629 | #cls.missions.append(Mission("Crash test: 60 allies against 60 ennemies", ["Rafale"] * 30 + ["Eurofighter"] * 30, ["TFX"] * 30 + ["Eurofighter"] * 20 + ["F16"] * 10, 5, 5, Missions.mission_total_war_setup_players, Missions.mission_war_end_test, Missions.mission_war_end_phase_update)) 630 | -------------------------------------------------------------------------------- /bak/Missions_dogfight.bak: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018-2021 Eric Kernin, NWNC HARFANG. 2 | 3 | import harfang as hg 4 | from Animations import * 5 | import tools 6 | from random import uniform 7 | from math import radians 8 | import json 9 | import network_server as netws 10 | from overlays import * 11 | from MachineDevice import * 12 | 13 | class Mission: 14 | 15 | def __init__(self, title, num_ennemies, num_allies, num_carriers_ennemies, num_carriers_allies, setup_players, end_test, end_phase_update): 16 | self.title = title 17 | self.ennemies = num_ennemies 18 | self.allies = num_allies 19 | self.allies_carriers = num_carriers_allies 20 | self.ennemies_carriers = num_carriers_ennemies 21 | self.setup_players_f = setup_players 22 | self.end_test_f = end_test 23 | self.update_end_phase_f = end_phase_update 24 | self.failed = False 25 | self.aborted = False 26 | 27 | def reset(self): 28 | self.failed = False 29 | self.aborted = False 30 | 31 | def setup_players(self, main): 32 | self.setup_players_f(main) 33 | 34 | def end_test(self, main): 35 | return self.end_test_f(main) 36 | 37 | def update_end_phase(self, main, dts): 38 | self.update_end_phase_f(main, dts) 39 | 40 | class Missions: 41 | beep_ref = None 42 | beep_state = None 43 | beep_source = None 44 | 45 | validation_ref = None 46 | validation_state = None 47 | validation_source = None 48 | 49 | # animations mission: 50 | am_start = False 51 | am_anim_x_prec = None 52 | am_anim_a_prec = None 53 | am_anim_x_new = None 54 | am_anim_a_new = None 55 | am_mission_id_prec = 0 56 | am_t = 0 57 | 58 | missions = [] 59 | mission_id = 1 60 | 61 | @classmethod 62 | def display_mission_title(cls, main, fade_lvl, dts, yof7): 63 | mcr = 1 64 | mcg = 0.6 65 | mcb = 0.1 66 | 67 | if not cls.am_start: 68 | mx = 0.5 69 | mc = hg.Color(mcr, mcg, mcb, 1) * fade_lvl 70 | mid = cls.mission_id 71 | 72 | if cls.mission_id < len(cls.missions) - 1 and main.keyboard.Pressed(hg.K_Right): 73 | cls.am_start = True 74 | mcpt = 1 75 | xpd = 0 76 | xns = 1 77 | 78 | elif cls.mission_id > 0 and main.keyboard.Pressed(hg.K_Left): 79 | cls.am_start = True 80 | mcpt = -1 81 | xpd = 1 82 | xns = 0 83 | 84 | if cls.am_start: 85 | cls.beep_source = hg.PlayStereo(cls.beep_ref, cls.beep_state) 86 | hg.SetSourceVolume(cls.beep_source, 0.05) 87 | cls.am_mission_id_prec = cls.mission_id 88 | cls.mission_id += mcpt 89 | cls.am_t = 0 90 | cls.am_anim_x_prec = Animation(0, 0.5, 0.5, xpd) 91 | cls.am_anim_a_prec = Animation(0, 0.5, 1, 0) 92 | cls.am_anim_x_new = Animation(0, 0.2, xns, 0.5) 93 | cls.am_anim_a_new = Animation(0, 0.2, 0, 1) 94 | mid = cls.am_mission_id_prec 95 | 96 | else: 97 | cls.am_t += dts 98 | cls.am_anim_x_prec.update(cls.am_t) 99 | cls.am_anim_x_new.update(cls.am_t) 100 | cls.am_anim_a_prec.update(cls.am_t) 101 | cls.am_anim_a_new.update(cls.am_t) 102 | 103 | mx = cls.am_anim_x_new.v 104 | mc = hg.Color(mcr, mcg, mcb, cls.am_anim_a_new.v) * fade_lvl 105 | 106 | mxprec = cls.am_anim_x_prec.v 107 | mcprec = hg.Color(mcr, mcg, mcb, cls.am_anim_a_prec.v) * fade_lvl 108 | 109 | Overlays.add_text2D( Missions.missions[cls.am_mission_id_prec].title, hg.Vec2(mxprec, 671 / 900 + yof7), 0.02, mcprec, main.hud_font, hg.DTHA_Center) 110 | mid = cls.mission_id 111 | 112 | if cls.am_anim_a_new.flag_end and cls.am_anim_x_new.flag_end and cls.am_anim_x_prec.flag_end and cls.am_anim_a_prec: 113 | cls.am_start = False 114 | 115 | Overlays.add_text2D(cls.missions[mid].title, hg.Vec2(mx, 671 / 900 + yof7), 0.02, mc, main.hud_font, hg.DTHA_Center) 116 | Overlays.add_text2D( "<- choose your mission ->", hg.Vec2(0.5, 701 / 900 + yof7), 0.012, hg.Color(1, 0.9, 0.3, fade_lvl * 0.8), main.hud_font, hg.DTHA_Center) 117 | 118 | if main.keyboard.Pressed(hg.K_Space): 119 | cls.validation_source = hg.PlayStereo(cls.validation_ref, cls.validation_state) 120 | hg.SetSourceVolume(cls.validation_source, 1) 121 | 122 | @classmethod 123 | def get_current_mission(cls): 124 | return cls.missions[cls.mission_id] 125 | 126 | @classmethod 127 | def aircrafts_starts_on_carrier(cls, aircrafts, carrier, start, y_orientation, distance, liftoff_delay=0, liftoff_offset=1): 128 | p, r = carrier.get_aircraft_start_point(0) 129 | carrier_alt = p.y 130 | carrier_mat = hg.TransformationMat4(carrier.get_parent_node().GetTransform().GetPos(), carrier.get_parent_node().GetTransform().GetRot()) 131 | for i, aircraft in enumerate(aircrafts): 132 | ia_ctrl = aircraft.get_device("IAControlDevice") 133 | if ia_ctrl is not None: 134 | ia_ctrl.IA_liftoff_delay = liftoff_delay 135 | aircraft.flag_landed = True 136 | liftoff_delay += liftoff_offset 137 | start += distance 138 | gear = aircraft.get_device("Gear") 139 | if gear is not None: 140 | bottom_height = gear.gear_height 141 | gear.record_start_state(True) 142 | gear.reset() 143 | else: 144 | bottom_height = aircraft.bottom_height 145 | start.y = carrier_alt + bottom_height 146 | mat = carrier_mat * hg.TransformationMat4(start, hg.Vec3(0, y_orientation, 0)) 147 | aircraft.reset_matrix(hg.GetT(mat), hg.GetR(mat)) 148 | aircraft.record_start_state() 149 | 150 | @classmethod 151 | def setup_aircrafts_on_carrier(cls, players, aircraft_carriers, start_time): 152 | na = len(players) 153 | nc = len(aircraft_carriers) 154 | na_row = na // (2 * nc) 155 | 156 | for i in range(nc): 157 | n0 = na_row * (2 * i) 158 | n1 = na_row * (2 * i + 1) 159 | n2 = na_row * (2 * i + 2) 160 | if na - n2 <= (na_row+1): 161 | n2 = na 162 | cls.aircrafts_starts_on_carrier(players[n0:n1], aircraft_carriers[i], hg.Vec3(10, 19.5, 80), 0, hg.Vec3(0, 0, -18), start_time + na_row * 2 * i, 2) 163 | cls.aircrafts_starts_on_carrier(players[n1:n2], aircraft_carriers[i], hg.Vec3(-10, 19.5, 62), 0, hg.Vec3(0, 0, -18), start_time + 1 + na_row * 2 * i, 2) 164 | 165 | @classmethod 166 | def aircrafts_starts_in_sky(cls, aircrafts, center: hg.Vec3, range: hg.Vec3, y_orientations_range: hg.Vec2, speed_range: hg.Vec2): 167 | for i, ac in enumerate(aircrafts): 168 | # ac.reset_matrix(hg.Vec3(uniform(center.x-range.x/2, center.x+range.x/2), uniform(center.y-range.y/2, center.y+range.y/2), uniform(center.z-range.z/2, center.z+range.z/2)), hg.Vec3(0, radians(uniform(y_orientations_range.x, y_orientations_range.y)), 0)) 169 | 170 | ac.reset_matrix( 171 | hg.Vec3( 172 | uniform(center.x - range.x / 2, center.x + range.x / 2), 173 | uniform(center.y - range.y / 2, center.y + range.y / 2), 174 | uniform(center.z - range.z / 2, center.z + range.z / 2) 175 | ), 176 | hg.Vec3( 177 | 0, 178 | radians( 179 | uniform(y_orientations_range.x, y_orientations_range.y) 180 | ), 181 | 0 182 | ) 183 | ) 184 | gear = ac.get_device("Gear") 185 | if gear is not None: 186 | gear.record_start_state(False) 187 | gear.reset() 188 | ac.set_linear_speed(uniform(speed_range.x, speed_range.y)) 189 | ac.flag_landed = False 190 | ac.record_start_state() 191 | 192 | @classmethod 193 | def setup_carriers(cls, carriers, start_pos, dist, y_orientation): 194 | for carrier in carriers: 195 | carrier.reset_matrix(start_pos, hg.Vec3(0, y_orientation, 0)) 196 | start_pos += dist 197 | 198 | 199 | # ============================= Training 200 | 201 | @classmethod 202 | def mission_setup_training(cls, main): 203 | 204 | mission = cls.get_current_mission() 205 | main.create_aircraft_carriers(mission.allies_carriers, mission.ennemies_carriers) 206 | main.create_players(mission.allies, mission.ennemies) 207 | 208 | cls.setup_carriers(main.aircraft_carrier_allies, hg.Vec3(0, 0, 0), hg.Vec3(500, 0, 100), 0) 209 | 210 | n = len(main.players_allies) 211 | # if n == 1: 212 | 213 | cls.aircrafts_starts_on_carrier(main.players_allies, main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 214 | 215 | # cls.aircrafts_starts_in_sky(main.players_allies, hg.Vec3(1000, 2000, 0), hg.Vec3(1000, 1000, 20000), hg.Vec2(-180, 180), hg.Vec2(600 / 3.6, 800 / 3.6)) 216 | 217 | # elif n > 1: 218 | # cls.aircrafts_starts_on_carrier(main.players_allies[0:n // 2], main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 219 | # cls.aircrafts_starts_on_carrier(main.players_allies[n // 2:n], main.aircraft_carrier_allies[0], hg.Vec3(-10, 19.5, 60), 0, hg.Vec3(0, 0, -20)) 220 | 221 | fps_start_matrix = main.aircraft_carrier_allies[0].fps_start_point.GetTransform().GetWorld() 222 | main.camera_fps.GetTransform().SetWorld(fps_start_matrix) 223 | 224 | lt = [] 225 | for carrier in main.aircraft_carrier_allies: 226 | lt += carrier.landing_targets 227 | for ac in main.players_allies: 228 | ac.set_landing_targets(lt) 229 | ia = ac.get_device("IAControlDevice") 230 | if ia is not None: 231 | ia.activate() 232 | 233 | # ------- Missile Launcher: 234 | main.create_missile_launchers(0, 1) 235 | 236 | plateform = main.scene.GetNode("platform.S400") 237 | ml = main.missile_launchers_ennemies[0] 238 | ml.set_platform(plateform) 239 | 240 | # --------- Views setup 241 | main.setup_views_carousel(True) 242 | main.set_view_carousel("Aircraft_ally_" + str(main.num_players_allies)) 243 | main.set_track_view("back") 244 | 245 | main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) 246 | main.user_aircraft.set_focus() 247 | 248 | """ 249 | ia = main.user_aircraft.get_device("IAControlDevice") 250 | if ia is not None: 251 | ia.set_IA_landing_target(main.aircraft_carrier_allies[0].landing_targets[1]) 252 | ia.deactivate() 253 | uctrl = main.user_aircraft.get_device("UserControlDevice") 254 | if uctrl is not None: 255 | uctrl.activate() 256 | """ 257 | # Deactivate IA: 258 | for i, ac in enumerate(main.players_allies): 259 | ia = ac.get_device("IAControlDevice") 260 | if ia is not None: 261 | ia.deactivate() 262 | 263 | for i, ac in enumerate(main.players_ennemies): 264 | ia = ac.get_device("IAControlDevice") 265 | if ia is not None: 266 | ia.deactivate() 267 | 268 | uctrl = main.user_aircraft.get_device("UserControlDevice") 269 | if uctrl is not None: 270 | uctrl.activate() 271 | 272 | main.init_playground() 273 | 274 | # if main.num_players_allies < 4: 275 | # main.user_aircraft.set_thrust_level(1) 276 | # main.user_aircraft.activate_post_combustion() 277 | 278 | @classmethod 279 | def mission_training_end_test(cls, main): 280 | mission = cls.get_current_mission() 281 | allies_wreck = 0 282 | for ally in main.players_allies: 283 | if ally.wreck: allies_wreck += 1 284 | 285 | if main.num_players_allies == allies_wreck: 286 | mission.failed = True 287 | print("MISSION FAILED !") 288 | return True 289 | 290 | return False 291 | 292 | @classmethod 293 | def mission_training_end_phase_update(cls, main, dts): 294 | mission = cls.get_current_mission() 295 | if mission.failed: 296 | msg_title = "Mission Failed !" 297 | msg_debriefing = " You need more lessons commander..." 298 | elif mission.aborted: 299 | msg_title = "Training aborted !" 300 | msg_debriefing = "Ready for fight ?" 301 | else: 302 | msg_title = "Training successful !" 303 | msg_debriefing = "Congratulation commander !" 304 | Overlays.add_text2D(msg_title, hg.Vec2(0.5, 771 / 900 - 0.15), 0.04, hg.Color(1, 0.9, 0.3, 1), main.title_font, hg.DTHA_Center) 305 | Overlays.add_text2D(msg_debriefing, hg.Vec2(0.5, 701 / 900 - 0.15), 0.02, hg.Color(1, 0.9, 0.8, 1), main.hud_font, hg.DTHA_Center) 306 | 307 | # ============================= Basic fight 308 | 309 | @classmethod 310 | def mission_setup_players(cls, main): 311 | mission = cls.get_current_mission() 312 | main.create_aircraft_carriers(mission.allies_carriers, mission.ennemies_carriers) 313 | main.create_players(mission.allies, mission.ennemies) 314 | 315 | cls.setup_carriers(main.aircraft_carrier_allies, hg.Vec3(0, 0, 0), hg.Vec3(500, 0, 100), 0) 316 | cls.setup_carriers(main.aircraft_carrier_ennemies, hg.Vec3(-17000, 0, 0), hg.Vec3(500, 0, -150), radians(90)) 317 | 318 | main.init_playground() 319 | 320 | n = len(main.players_allies) 321 | if n == 1: 322 | cls.aircrafts_starts_on_carrier(main.players_allies, main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 323 | elif n > 1: 324 | cls.aircrafts_starts_on_carrier(main.players_allies[0:n // 2], main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20), 2, 2) 325 | cls.aircrafts_starts_on_carrier(main.players_allies[n // 2:n], main.aircraft_carrier_allies[0], hg.Vec3(-10, 19.5, 60), 0, hg.Vec3(0, 0, -20), 3, 2) 326 | 327 | n = len(main.players_ennemies) 328 | if n < 3: 329 | cls.aircrafts_starts_in_sky(main.players_ennemies, hg.Vec3(-5000, 1000, 0), hg.Vec3(1000, 500, 2000), hg.Vec2(-180, 180), hg.Vec2(800 / 3.6, 600 / 3.6)) 330 | else: 331 | cls.aircrafts_starts_in_sky(main.players_ennemies[0:2], hg.Vec3(-5000, 1000, 0), hg.Vec3(1000, 500, 2000), hg.Vec2(-180, 180), hg.Vec2(800 / 3.6, 600 / 3.6)) 332 | cls.aircrafts_starts_on_carrier(main.players_ennemies[2:n], main.aircraft_carrier_ennemies[0], hg.Vec3(-10, 19.5, 60), 0, hg.Vec3(0, 0, -20), 2, 1) 333 | 334 | for i, ac in enumerate(main.players_allies): 335 | ia = ac.get_device("IAControlDevice") 336 | if ia is not None: 337 | ia.activate() 338 | 339 | for i, ac in enumerate(main.players_ennemies): 340 | ia = ac.get_device("IAControlDevice") 341 | if ia is not None: 342 | ia.activate() 343 | 344 | main.setup_views_carousel(False) 345 | main.set_view_carousel("Aircraft_ally_" + str(main.num_players_allies)) 346 | main.set_track_view("back") 347 | 348 | main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) 349 | main.user_aircraft.set_focus() 350 | 351 | if main.user_aircraft is not None: 352 | ia = main.user_aircraft.get_device("IAControlDevice") 353 | if ia is not None: 354 | ia.deactivate() 355 | uctrl = main.user_aircraft.get_device("UserControlDevice") 356 | if uctrl is not None: 357 | uctrl.activate() 358 | if main.num_players_allies < 3: 359 | main.user_aircraft.reset_thrust_level(1) 360 | main.user_aircraft.activate_post_combustion() 361 | 362 | @classmethod 363 | def mission_one_against_x_end_test(cls, main): 364 | mission = cls.get_current_mission() 365 | ennemies_wreck = 0 366 | allies_wreck = 0 367 | for ennemy in main.players_ennemies: 368 | if ennemy.wreck: ennemies_wreck += 1 369 | for ally in main.players_allies: 370 | if ally.wreck: allies_wreck += 1 371 | 372 | if main.num_players_ennemies == ennemies_wreck: 373 | mission.failed = False 374 | return True 375 | if main.num_players_allies == allies_wreck: 376 | mission.failed = True 377 | return True 378 | 379 | return False 380 | 381 | @classmethod 382 | def mission_one_against_x_end_phase_update(cls, main, dts): 383 | mission = cls.get_current_mission() 384 | if mission.failed: 385 | msg_title = "Mission Failed !" 386 | msg_debriefing = " R.I.P. Commander..." 387 | elif mission.aborted: 388 | msg_title = "Mission aborted !" 389 | msg_debriefing = "We hope you do better next time !" 390 | else: 391 | msg_title = "Mission successful !" 392 | msg_debriefing = "Congratulation commander !" 393 | Overlays.add_text2D(msg_title, hg.Vec2(0.5, 771 / 900 - 0.15), 0.04, hg.Color(1, 0.9, 0.3, 1), main.title_font, hg.DTHA_Center) 394 | Overlays.add_text2D(msg_debriefing, hg.Vec2(0.5, 701 / 900 - 0.15), 0.02, hg.Color(1, 0.9, 0.8, 1), main.hud_font, hg.DTHA_Center) 395 | 396 | # ============================ War fight 397 | @classmethod 398 | def mission_total_war_setup_players(cls, main): 399 | 400 | mission = cls.get_current_mission() 401 | main.create_aircraft_carriers(mission.allies_carriers, mission.ennemies_carriers) 402 | main.create_players(mission.allies, mission.ennemies) 403 | 404 | cls.setup_carriers(main.aircraft_carrier_allies, hg.Vec3(0, 0, 0), hg.Vec3(300, 0, 25), 0) 405 | cls.setup_carriers(main.aircraft_carrier_ennemies, hg.Vec3(-20000, 0, 0), hg.Vec3(50, 0, -350), radians(90)) 406 | 407 | 408 | cls.setup_aircrafts_on_carrier(main.players_allies, main.aircraft_carrier_allies, 0) 409 | 410 | n = len(main.players_ennemies) 411 | cls.aircrafts_starts_in_sky(main.players_ennemies[0:n // 2], hg.Vec3(-8000, 1000, 0), hg.Vec3(2000, 500, 2000), hg.Vec2(-180, -175), hg.Vec2(800 / 3.6, 600 / 3.6)) 412 | 413 | cls.setup_aircrafts_on_carrier(main.players_ennemies[n//2:n], main.aircraft_carrier_ennemies, 60) 414 | 415 | main.init_playground() 416 | 417 | for i, ac in enumerate(main.players_allies): 418 | ia = ac.get_device("IAControlDevice") 419 | if ia is not None: 420 | ia.activate() 421 | 422 | for i, ac in enumerate(main.players_ennemies): 423 | ia = ac.get_device("IAControlDevice") 424 | if ia is not None: 425 | ia.activate() 426 | 427 | main.setup_views_carousel() 428 | main.set_view_carousel("Aircraft_ally_" + str(main.num_players_allies)) 429 | main.set_track_view("back") 430 | 431 | main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) 432 | main.user_aircraft.set_focus() 433 | 434 | if main.user_aircraft is not None: 435 | ia = main.user_aircraft.get_device("IAControlDevice") 436 | if ia is not None: 437 | ia.deactivate() 438 | uctrl = main.user_aircraft.get_device("UserControlDevice") 439 | if uctrl is not None: 440 | uctrl.activate() 441 | if main.num_players_allies < 4: 442 | main.user_aircraft.reset_thrust_level(1) 443 | main.user_aircraft.activate_post_combustion() 444 | 445 | @classmethod 446 | def mission_war_end_test(cls, main): 447 | mission = cls.get_current_mission() 448 | if main.keyboard.Pressed(hg.K_F6): 449 | for pl in main.players_allies: 450 | pl.flag_IA_start_liftoff = True 451 | for pl in main.players_ennemies: 452 | pl.flag_IA_start_liftoff = True 453 | # Pour le moment, même test de fin de mission 454 | return cls.mission_one_against_x_end_test(main) 455 | 456 | @classmethod 457 | def mission_war_end_phase_update(cls, main, dts): 458 | mission = cls.get_current_mission() 459 | if mission.failed: 460 | msg_title = "Mission Failed !" 461 | msg_debriefing = " R.I.P. Commander..." 462 | elif mission.aborted: 463 | msg_title = "Mission aborted !" 464 | msg_debriefing = "We hope you do better next time !" 465 | else: 466 | msg_title = "Mission successful !" 467 | msg_debriefing = "Congratulation commander !" 468 | 469 | Overlays.add_text2D(msg_title, hg.Vec2(0.5, 771 / 900 - 0.15), 0.04, hg.Color(1, 0.9, 0.3, 1), main.title_font, hg.DTHA_Center) 470 | Overlays.add_text2D(msg_debriefing, hg.Vec2(0.5, 701 / 900 - 0.15), 0.02, hg.Color(1, 0.9, 0.8, 1), main.hud_font, hg.DTHA_Center) 471 | 472 | # ============================ Client / Server mode 473 | @classmethod 474 | def network_mode_setup(cls, main): 475 | mission = cls.get_current_mission() 476 | main.flag_network_mode = True 477 | 478 | file_name = "scripts/network_mission_config.json" 479 | file = hg.OpenText(file_name) 480 | if not file: 481 | print("Can't open mission configuration json file : " + file_name) 482 | else: 483 | json_script = hg.ReadString(file) 484 | hg.Close(file) 485 | if json_script != "": 486 | script_parameters = json.loads(json_script) 487 | mission.allies = script_parameters["aircrafts_allies"] 488 | mission.ennemies = script_parameters["aircrafts_ennemies"] 489 | mission.allies_carriers = script_parameters["aircraft_carriers_allies_count"] 490 | mission.ennemies_carriers = script_parameters["aircraft_carriers_enemies_count"] 491 | else: 492 | print("Mission configuration json file empty : " + file_name) 493 | 494 | main.create_aircraft_carriers(mission.allies_carriers, mission.ennemies_carriers) 495 | main.create_players(mission.allies, mission.ennemies) 496 | 497 | cls.setup_carriers(main.aircraft_carrier_allies, hg.Vec3(0, 0, 0), hg.Vec3(500, 0, 100), 0) 498 | cls.setup_carriers(main.aircraft_carrier_ennemies, hg.Vec3(-17000, 0, 0), hg.Vec3(500, 0, -150), radians(90)) 499 | 500 | main.init_playground() 501 | 502 | # Sets aircrafts landed on carriers: 503 | 504 | n = len(main.players_allies) 505 | if n == 1: 506 | cls.aircrafts_starts_on_carrier(main.players_allies, main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 507 | elif n > 1: 508 | cls.aircrafts_starts_on_carrier(main.players_allies[0:n // 2], main.aircraft_carrier_allies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20), 2, 2) 509 | cls.aircrafts_starts_in_sky( 510 | aircrafts=main.players_allies[n // 2:n], 511 | center=hg.Vec3(-10, 1500, 60), # (x, z, y), recommend height 1500 512 | # range=hg.Vec3(-10, 19.5, 60), 513 | range=hg.Vec3(0, 0, 0), 514 | y_orientations_range=hg.Vec2(270, 270), # heading 515 | speed_range=hg.Vec2(400, 400) # m/s, recommend 400 516 | ) 517 | 518 | n = len(main.players_ennemies) 519 | if n == 1: 520 | cls.aircrafts_starts_on_carrier(main.players_ennemies, main.aircraft_carrier_ennemies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20)) 521 | elif n > 1: 522 | cls.aircrafts_starts_on_carrier(main.players_ennemies[0:n // 2], main.aircraft_carrier_ennemies[0], hg.Vec3(10, 19.5, 40), 0, hg.Vec3(0, 0, -20), 2, 2) 523 | cls.aircrafts_starts_in_sky( 524 | aircrafts=main.players_ennemies[n // 2:n], 525 | center=hg.Vec3(-5000, 1500, 60), # (x, z, y), recommend height 1500 526 | # range=hg.Vec3(-10, 19.5, 60), 527 | range=hg.Vec3(0, 0, 0), 528 | y_orientations_range=hg.Vec2(90, 90), # heading 529 | speed_range=hg.Vec2(400, 400) # m/s, recommend 400 530 | ) 531 | 532 | # Deactivate IA: 533 | for i, ac in enumerate(main.players_allies): 534 | ia = ac.get_device("IAControlDevice") 535 | if ia is not None: 536 | ia.deactivate() 537 | 538 | for i, ac in enumerate(main.players_ennemies): 539 | ia = ac.get_device("IAControlDevice") 540 | if ia is not None: 541 | ia.deactivate() 542 | 543 | # ------- Missile Launcher: 544 | main.create_missile_launchers(0, 1) 545 | 546 | plateform = main.scene.GetNode("platform.S400") 547 | ml = main.missile_launchers_ennemies[0] 548 | ml.set_platform(plateform) 549 | 550 | # --------- Views setup 551 | main.setup_views_carousel(True) 552 | main.set_view_carousel("Aircraft_ally_1")# + str(main.num_players_allies)) 553 | main.set_track_view("back") 554 | 555 | main.init_playground() 556 | 557 | main.user_aircraft = main.get_player_from_caroursel_id(main.views_carousel[main.views_carousel_ptr]) 558 | main.user_aircraft.set_focus() 559 | 560 | uctrl = main.user_aircraft.get_device("UserControlDevice") 561 | if uctrl is not None: 562 | uctrl.activate() 563 | 564 | netws.init_server(main) 565 | netws.start_server() 566 | 567 | @classmethod 568 | def network_mode_end_test(cls, main): 569 | """ 570 | mission = cls.get_current_mission() 571 | 572 | allies_wreck = 0 573 | for ally in main.players_allies: 574 | if ally.wreck: 575 | allies_wreck += 1 576 | 577 | if main.num_players_allies == allies_wreck: 578 | mission.failed = True 579 | return True 580 | """ 581 | return False 582 | 583 | @classmethod 584 | def network_mode_end_phase_update(cls, main, dts): 585 | if main.flag_network_mode: 586 | main.flag_network_mode = False 587 | netws.stop_server() 588 | mission = cls.get_current_mission() 589 | if mission.failed: 590 | msg_title = "Network aborted !" 591 | msg_debriefing = " Aircraft destroyed, TAB to restart" 592 | elif mission.aborted: 593 | msg_title = "Network aborted !" 594 | msg_debriefing = "TAB to restart" 595 | else: 596 | msg_title = "Successful !" 597 | msg_debriefing = "Congratulation commander !" 598 | Overlays.add_text2D(msg_title, hg.Vec2(0.5, 771 / 900 - 0.15), 0.04, hg.Color(1, 0.9, 0.3, 1), main.title_font, hg.DTHA_Center) 599 | Overlays.add_text2D(msg_debriefing, hg.Vec2(0.5, 701 / 900 - 0.15), 0.02, hg.Color(1, 0.9, 0.8, 1), main.hud_font, hg.DTHA_Center) 600 | 601 | @classmethod 602 | def init(cls): 603 | cls.beep_ref = hg.LoadWAVSoundAsset("sfx/blip.wav") 604 | cls.beep_state = tools.create_stereo_sound_state(hg.SR_Once) 605 | cls.beep_state.volume = 0.25 606 | 607 | cls.validation_ref = hg.LoadWAVSoundAsset("sfx/blip2.wav") 608 | cls.validation_state = tools.create_stereo_sound_state(hg.SR_Once) 609 | cls.validation_state.volume = 0.5 610 | 611 | 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)) 612 | 613 | 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)) 614 | 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)) 615 | cls.missions.append(Mission("Training with TFX", [], ["TFX"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 616 | cls.missions.append(Mission("Training with F16", [], ["F16"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 617 | cls.missions.append(Mission("Training with Miuss", [], ["Miuss"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 618 | #cls.missions.append(Mission("Training with F14", [], ["F14"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 619 | #cls.missions.append(Mission("Training with F14 2", [], ["F14_2"], 0, 1, Missions.mission_setup_training, Missions.mission_training_end_test, Missions.mission_training_end_phase_update)) 620 | 621 | cls.missions.append(Mission("One on one", ["Rafale"], ["Eurofighter"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 622 | cls.missions.append(Mission("Fight against 2 ennemies", ["Rafale"] * 2, ["Eurofighter"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 623 | cls.missions.append(Mission("Fight against 3 ennemies", ["Rafale"] * 1 + ["F16"] * 2, ["Eurofighter"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 624 | cls.missions.append(Mission("Fight against 4 ennemies", ["Rafale"] * 2 + ["F16"] * 2, ["TFX", "Eurofighter"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 625 | cls.missions.append(Mission("Fight against 5 ennemies", ["Rafale"] * 5, ["TFX", "Eurofighter", "F16"], 1, 1, Missions.mission_setup_players, Missions.mission_one_against_x_end_test, Missions.mission_one_against_x_end_phase_update)) 626 | 627 | cls.missions.append(Mission("War: 5 allies against 5 ennemies", ["Rafale"] * 3 + ["Eurofighter"] * 2, ["TFX"] * 2 + ["F16"] * 2 + ["Eurofighter"] * 1, 1, 1, Missions.mission_total_war_setup_players, Missions.mission_war_end_test, Missions.mission_war_end_phase_update)) 628 | #cls.missions.append(Mission("Total War: 12 allies against 12 ennemies", ["Rafale"] * 12, ["TFX"] * 4 + ["Eurofighter"] * 4 + ["F16"] * 4 + ["Eurofighter"] * 4, 2, 2, Missions.mission_total_war_setup_players, Missions.mission_war_end_test, Missions.mission_war_end_phase_update)) 629 | #cls.missions.append(Mission("Crash test: 60 allies against 60 ennemies", ["Rafale"] * 30 + ["Eurofighter"] * 30, ["TFX"] * 30 + ["Eurofighter"] * 20 + ["F16"] * 10, 5, 5, Missions.mission_total_war_setup_players, Missions.mission_war_end_test, Missions.mission_war_end_phase_update)) 630 | --------------------------------------------------------------------------------