# _*_ coding: UTF-8 _*_
"""
@Author: nudt-52
@Date: 2025/8/7 16:00
此智能体为较通用适配普通含有的战斗机的想定，训练战斗机的作战的强化学习智能体示例；
接口调用示例；
设置飞机挂载函数：set_aircraft_loadout
特此针对型号：2346的米格-29型“支点A”战斗机，已设置挂载武器
特此针对型号：2232的T-50型战斗机战斗机，已设置挂载武器
动作空间为：
设置航路、开关雷达和设置飞机速度；（战斗机打击目标通过自由开火实现）
"""

from typing import Any, Tuple
import gymnasium as gym
import numpy as np
import tensorflow as tf

from engine.LingYi.entitys.agent_base import RLAgentBase
from engine.LingYi.entitys.global_util import *
from engine.LingYi.entitys import geo

from agent.rl_agent.model import SharedWeightsModel1, FullyConnectedNetworkTorch
from agent.rl_agent.Constant import *
from corekit.utils.typings import ActionExe, ObsArray
from agent.rl_agent.utils import *


class Agent(RLAgentBase):
    """灵弈战斗机智能体"""
    action_dim = [3, 2, 3]  # 动作空间维度  1：航路，2：雷达开关 3：设置速度;  或者一维空间（DQN算法）：设置为:18
    action_space: gym.Space = gym.spaces.MultiDiscrete(action_dim)  # 动作空间维度---必须定义;
    # DQN算法：action_space: gym.Space = gym.spaces.Discrete(18)
    observation_space: gym.Space = gym.spaces.Box(low=-np.inf, high=+np.inf, shape=(316,))  # 状态空间维度---必须定义

    def __init__(self, agent_conf: dict = None):
        """配置状态空间、动作空间"""
        if agent_conf is None:
            agent_conf = {}
        super().__init__(agent_conf)
        self.aircraft_fuel = 8  # 表示已经消耗70%油了
        # self.model = None  # 智能体神经网络模型，客户端调度使用时需加载模型用于决策
        # 用户需重写load_model函数，加载生成模型，训练时在智能体脚本的model目录下会存储模型文件，例如"model_626.h"，代表第626次训练的模型
        self.action_dim = Agent.action_dim  # 动作空间维度
        self.max_missile_num = 6  # 单架炸战斗机导弹最大挂载
        self.teammate_fighter_num = 4  # 友方战斗机最大数量
        self.teammate_bomber_num = 5  # 友方轰炸机最大数量
        self.teammate_aew_num = 2  # 友方预警机最大数量

        self.enemy_aircraft_num = 8  # 计算观测时，敌方战斗机最大数量--9维
        self.enemy_ship_num = 3  # 敌方舰船最大数量--6维
        self.enemy_facility_num = 4  # 敌方舰艇最大数量--6维
        self.enemy_missile_num = 4  # 敌方大概朝向我方飞机的导弹数量--8维

        self.crt_select_airs = set()  # 客户端应用时，当前已编辑的单元
        self.patrol_mission = None
        self.fighter_mission_name = "战斗机智能空战"  # 战斗机巡逻的任务

        # 智能体观测计算归一化相关：
        self.max_lon_move_range = 12  # 战斗机移动最大经度范围，初始位置认为是:0.5,最小值：0，最大值1
        self.max_lat_move_range = 10  # 战斗机移动最大纬度范围，初始位置认为是:0.5,最小值：0，最大值1、
        # 探测到的目标也按此位置范围归一化处理
        self.initial_lon = None  # 初始经度
        self.initial_lat = None  # 初始纬度
        self.lat_bottom = None  # 纬度实际底部
        self.valid_lat_range = self.max_lat_move_range  # 纬度实际可移动范围
        self.lon_min = None  # 最小纬度
        self.speed_range = [300, 1000]  # 战斗机速度范围 海里/H
        self.initial_fuel = None  # 初始油量

        # 在智能体训练平台中，而不是客户端应用中，加载已训练好的神经网络模型，可运行多局，用户可统计分析查看每局决策结果，分析模型效能；
        # 将强化学习智能体配置为规则智能体后（config.py中某项智能体配置从get_rl_agent移到get_rule_dict）
        # 框架自动调用step函数，进行模型推理决策，此时框架中将不会调用step_rl函数
        self.do_ai_apply = False  # 客户端推理应用，和训练模型无需设置为True
        if self.do_ai_apply:
            self.load_model()  # 加载模型，用于决策，而不是使用rllib训练输出的动作决策；
            # 注意：客户端应用智能体中，框架会自动调用函数load_model(), 无需在此处调用
            # 用户可将模型文件放在智能体脚本文件同目录，然后load_model()加载模型。
        else:
            # 创建神经网络模型结构，用于在智能体训练平台中，训练神经网络模型
            framework: str = agent_conf.get('framework', 'tf')
            if framework == 'tf':
                if agent_conf['alg'] == 'PPO' or \
                        agent_conf['alg'] == 'DQN':
                    self.model = SharedWeightsModel1
            elif framework == 'torch':
                self.model = FullyConnectedNetworkTorch
            else:
                raise NotImplemented

    def get_airs_select_center(self, situation):
        """
        获取选择的飞机的中心点, 去除返航、无空空弹的飞机
        :param situation: dict, 态势
        :return: tuple, (lat, lon), 纬度、经度元组
        """
        center_lat = None
        center_lon = None
        air_count = 0
        select_units_guid = list(self.client_info['unit'].keys())  # 客户端选择的单元对象id
        units, contacts = situation
        lat_value_list = []
        lon_value_list = []
        # 飞机起飞可打击检测
        for guid in select_units_guid:
            if guid in units:
                unit_info = units[guid]
                air_ops = unit_info["airops"]
                ops_code = 999
                if 'Con' in air_ops:
                    ops_code = air_ops['Con']
                elif 'Condition' in air_ops:
                    ops_code = air_ops['Condition']

                if ops_code in (1, 2, 4, 8, 9, 18, 23, 24, 26) or ops_code in (0, 13, 14, 15, 16, 19, 20, 21, 22):
                    # 在空中可部署巡逻，进攻，航路规划; 或在基地可出发执行空战打击
                    load_out = unit_info["loadout"]
                    # 检测还有有效对空导弹
                    have_air_missiles = False
                    for _, loadout_info in load_out.items():
                        if not isinstance(loadout_info, dict):
                            continue
                        if "WeaponRecs" in loadout_info:
                            for _, weapon_info in loadout_info["WeaponRecs"].items():
                                if not isinstance(weapon_info, dict):
                                    continue
                                if "AirRangeMax" not in weapon_info:
                                    continue
                                if weapon_info["AirRangeMax"] > 5:
                                    # 对空武器射程大于5海里
                                    if weapon_info["CurrentLoad"] > 0:
                                        have_air_missiles = True
                                        break
                    if have_air_missiles:
                        lat_value_list.append(unit_info["lat"])
                        lon_value_list.append(unit_info["lon"])
                        air_count += 1
        if air_count > 0:
            has_west = any(x < 0 for x in lon_value_list)  # 有西经
            has_east = any(x > 0 for x in lon_value_list)  # 又有东经
            if has_west and has_east:
                # 同时存在东西半球单元
                count_west180_90 = sum(1 for x in lon_value_list if -180 <= x < -90)
                count_west90_0 = sum(1 for x in lon_value_list if -90 <= x < 0)
                count_east180_90 = sum(1 for x in lon_value_list if 90 <= x < 180)
                count_east90_0 = sum(1 for x in lon_value_list if 0 <= x < 90)
                if count_west180_90 + count_east180_90 > count_west90_0 + count_east90_0:
                    # 看哪个半球飞机多，此时西经转数据180到360
                    new_lon_list = [x if x >= 0 else 360 + x for x in lon_value_list]
                    center_lon = sum(new_lon_list) / air_count
                    if center_lon > 180:
                        center_lon = center_lon - 360
                else:
                    center_lon = sum(lon_value_list) / air_count
            else:
                center_lon = sum(lon_value_list) / air_count
            center_lat = sum(lat_value_list) / air_count
        return center_lat, center_lon

    def create_air_fighter_mission(self, situation):
        """
        智能体选择的空中飞机，可获取中心位置，则创建巡逻任务；
        设置巡逻任务
        :param situation: dict, 态势
        :return: tuple, (lat, lon), 纬度、经度元组
        """
        center_pos = self.get_airs_select_center(situation)
        lat, lon = center_pos
        if lat is not None:
            east_lon = lon + 1
            if east_lon > 180:
                # 西经
                east_lon -= 360
            west_lon = lon - 1
            if west_lon < -180:
                # 西经变东经了
                west_lon += 360
            up_lat = lat + 1
            if up_lat > 89.99999:
                up_lat = 89.99999
            down_lat = lat - 1
            if down_lat < -89.99999:
                down_lat = -89.99999
            patrol_area = [(up_lat, west_lon, 'FP_P1'), (up_lat, east_lon, 'FP_P2'),
                           (down_lat, east_lon, 'FP_P3'), (down_lat, west_lon, 'FP_P4')]
            # 创建巡逻任务
            self.patrol_mission = self.create_patrol_mission(self.fighter_mission_name, MissionPatrolType.AIR,
                                                             patrol_area)
            # 任务设置
            self.patrol_mission.patrol_flight_size(FlightSize.One)  # 任务战斗机单机编队
            self.patrol_mission.patrol_checkOPA(True)  # 对巡逻区外的探测目标进行分析
            # 巡逻任务设置条令
            self.patrol_mission.doctrine_ignore_plotted_course(IgnorePlottedCourseWhenAttacking.Yes)  # 攻击时忽略计划航线
            self.patrol_mission.doctrine_weapon_control_status_air(WeaponControlStatus.Hold)  # 对空仅手动决策打击
            self.patrol_mission.set_one_third_rule(False)  # 所有飞机起飞
            self.patrol_mission.doctrine_automatic_evasion(AutomaticEvasion.Yes)  # 自动规避
            self.patrol_mission.doctrine_fuel_state_planned(FuelState.Bingo)  # 到燃油状态预先规划
            self.patrol_mission.doctrine_fuel_state_rtb(FuelStateRTB.YesLeaveGroup)  # 到燃油状态返航
            # 不使用航炮
            self.patrol_mission.doctrine_weapon_state_planned(WeaponStatePlanned.ShotgunOneEngageStrikeNoAirguns)
            # 武器状态返航设置
            self.patrol_mission.doctrine_weapon_state_rtb(WeaponStateRTB.YesLeaveGroup)
            # 空战自由开火
            self.patrol_mission.doctrine_weapon_control_status_air(WeaponControlStatus.Free)

    def set_aircraft_loadout(self, situation):
        """
        战前规划时间设置飞机挂载武器
        :param situation: tuple: units, contacts
        """
        units = situation[0]
        for guid in self.client_info['unit'].keys():
            if guid in units:
                dbid = units[guid]["dbid"]
                if dbid == 2346:
                    # 米格-29型“支点A”战斗机
                    self.set_aircraft_loadout_plan_time(guid, 5299)  # 战前规划时间飞机设置空空战挂载
                elif dbid == 2232:
                    # T-50 战斗机
                    self.set_aircraft_loadout_plan_time(guid, 19951)  # 战前规划时间飞机设置空空战挂载

    def set_player_doctrine(self):
        # 武器控制状态，对空；对水面；对潜；对地。
        self.doctrine_weapon_control_status_air(WeaponControlStatus.Tight)  # 武器控制状态，对空；谨慎开火
        self.doctrine_weapon_control_status_surface(WeaponControlStatus.Tight)  # 武器控制状态，对海，谨慎开火
        self.doctrine_weapon_control_status_subsurface(WeaponControlStatus.Tight)  # 武器控制状态，对潜；谨慎开火
        self.doctrine_weapon_control_status_land(WeaponControlStatus.Tight)  # 武器控制状态，对地：谨慎开火
        # 攻击时忽略计划航线设置 否
        self.doctrine_ignore_plotted_course(IgnorePlottedCourseWhenAttacking.No)
        # 接战模糊位置目标  悲观决策
        self.doctrine_engaging_ambiguous_targets(BehaviorTowardsAmbigousTarget.Pessimistic)
        # 接战临机出现目标  否（只与任务相关目标交战）
        self.doctrine_engage_opportunity_targets(EngageWithContactTarget.No_Only)
        # 燃油状态，预先规划
        self.doctrine_fuel_state_planned(FuelState.Bingo)
        # 设置燃油状态返航
        self.doctrine_fuel_state_rtb(FuelStateRTB.No)
        # 设置武器状态，预先规划
        self.doctrine_weapon_state_planned(WeaponStatePlanned.WinchesterUseAirGuns)
        # 设置武器状态返航
        self.doctrine_weapon_state_rtb(WeaponStateRTB.No)
        # 设置开火条件为：谨慎开火
        self.doctrine_weapon_control_status_air(WeaponControlStatus.Tight)

    def initial(self, situation):
        """
        智能体初始化, 可每局推演初始化变量，可赛前规划，可以用于创建任务，修改任务，设置条令，设置作战单元雷达开关机等
        智能体训练和推理应用（客户端中导入使用）中每局开始运行智能体，都会被调用一次；
        注意客户端调用停止智能体运行后，再次运行，会再次被调用。
        :param situation: tuple: units, contacts
        :return:
        """
        self.log.info("Fighter agents initial")
        self.set_player_doctrine()  # 设置推演方条令
        self.set_aircraft_loadout(situation)  # 战前规划时间，飞机可以设置挂载---------比赛中可实现
        self.patrol_mission = self.get_mission_by_name(self.fighter_mission_name)  # 可能重新导入智能体，或想定已又此任务
        if self.patrol_mission is None:
            # 还没有创建巡逻任务，则创建任务
            self.create_air_fighter_mission(situation)
        if self.patrol_mission is not None:
            # 已成功创建巡逻任务
            select_units_guid = list(self.client_info['unit'].keys())  # 客户端选择的单元对象id
            self.crt_select_airs = set(select_units_guid)
            select_count = len(select_units_guid)
            # 每10架飞机分配到任务，超过10架，则可能通信问题传输异常；
            if select_count < 11:
                self.assign_units_to_mission(select_units_guid, self.fighter_mission_name)  # 分配任务
                self.air_single_out(select_units_guid)  # 飞机起飞
            else:
                air_ten_count = select_count // 10
                if select_count % 10 != 0:
                    air_ten_count += 1
                for i in range(air_ten_count):
                    if i == air_ten_count - 1:
                        start_index = i * 10
                        crt_air_list = select_units_guid[start_index:]
                        self.assign_units_to_mission(crt_air_list, self.fighter_mission_name)  # 分配任务
                        self.air_single_out(crt_air_list)  # 飞机起飞
                    else:
                        end_index = i * 10 + 10
                        start_index = i * 10
                        crt_air_list = select_units_guid[start_index:end_index]
                        self.assign_units_to_mission(crt_air_list, self.fighter_mission_name)  # 分配任务
                        self.air_single_out(crt_air_list)  # 飞机起飞

            # 输出消息到客户端
            self.agent_message_text("Fighter mission %s created, %d air assigned" %
                                    (self.fighter_mission_name, len(select_units_guid)))

    def step(self, time_elapse, situation):
        """
        客户端导入此强化学习智能体后，按推演时间决策间隔（self.agent_decision_time，可赋值修改）会自动调用一次此函数；
        可自动计算当前态势的每个智能体（客户端中智能体脚本的输入单元，每个单元一个智能体）观测空间（用户编写的obs_trans函数），
        通过加载的模型，输入计算的观测，输出动作数据；
        传入模型输出的动作数据，再调用act_trans函数，
        生成推演系统动作指令信息（即action_dict）, 最后调用step_rl函数对推演系统指令调用；
        obs_trans函数、act_trans函数和step_rl函数都是训练智能体时会被调用的函数，客户端应用智能体时可再次被使用；
        :param time_elapse:int, 推演仿真时间，单位：秒，取值0-推演总时间秒数
        :param situation: tuple, 单元字典和探测到的目标字典, units dict, contacts dict
        :return: None
        """
        select_units_guid = list(self.client_info['unit'].keys())  # 客户端选择的单元对象id列表
        # 如果任务还没创建成功，创建巡逻任务
        if self.patrol_mission is None:
            self.create_air_fighter_mission(situation)
        if self.patrol_mission is not None:
            # 如果当前step有新加入单元
            need_take_off = []  # 在基地需起飞的飞机
            for guid in select_units_guid:
                if guid not in self.crt_select_airs:
                    self.crt_select_airs.add(guid)  # 客户端中这步刚编辑的单元
                    self.patrol_mission.assign_unit(guid)  # 飞机分配任务
                    air = self.get_unit(guid)
                    if air is not None:
                        if air.get_status_type() == AirValidStatus.validToFly:
                            # 飞机状态：在基地
                            need_take_off.append(guid)
            if need_take_off:
                self.air_single_out(need_take_off)  # 飞机起飞命令

        # 以下代码段，可作为强化学习智能体通用的推理应用方式
        all_agents_actions = {}  # step_rl函数的动作输入
        units = situation[0]  # 本方所有单元
        for guid in select_units_guid:
            if guid in units:
                # 单元id在收到的态势中
                air = self.get_unit(guid)
                air_status = air.get_status_type()
                if air_status == AirValidStatus.InAir:
                    # 已在空中，可决策; 在机场、返航途中的飞机不决策；
                    self.agent_unit_id = guid
                    unit_obs = self.obs_trans(situation)  # 兵棋观测转换为数据
                    tensorout = self.get_model_out(unit_obs)  # 数据利用模型前向输出
                    action = self.action(tensorout)  # 生成动作数据
                    agent_action_dict = self.act_trans(action, situation)  # 动作数据转换为指令信息
                    all_agents_actions[guid] = agent_action_dict
        if all_agents_actions:
            # self.agent_message_text("Fighter agent step:%d, %d airs action" % (time_elapse, len(all_agents_actions)))
            self.step_rl(time_elapse, situation, all_agents_actions)  # 指令下达
        else:
            pass
            # self.agent_message_text("Fighter agent step:%d, no airs action" % time_elapse)

    def get_model_out(self, obs):
        """
        神经网络前向过程
        :param obs: can be list, 观测, obs_trans函数的输出
        :return: tensorout, action函数的输入
        """
        obs_array = np.array(obs)
        out, value_out = self.model(obs_array[None, :])
        return out

    def action(self, out):
        """
        将神经网络的输出out, 转变成行动策略 action.
        配置多单元的多智能体强化学习算法中，每个单元算一个智能体，每步决策调用一次每个智能体此函数。
        :param out: tensorout
        :return: list, or numpy.array
        """
        # 如果是多维离散动作空间
        if isinstance(self.action_dim, list):
            out = out.numpy()[0]
            slice_tmp = [0 for _ in range(len(self.action_dim))]
            # slice_tmp[i]表示第i维动作在一维输出中对应的索引起点。
            # 用来将一维的输出进行多维离散切片，action_dim=[5,2,2,2,2]，则slice_tmp=[0,5,7,9,11]
            for i in range(1, len(self.action_dim)):
                slice_tmp[i] = slice_tmp[i - 1] + self.action_dim[i - 1]
            action = []
            # 将一维输出转成多维离散的动作输出
            for i in range(len(self.action_dim)):
                if i + 1 < len(self.action_dim):
                    out_tmp = out[slice_tmp[i]:slice_tmp[i + 1]].copy()
                else:
                    out_tmp = out[slice_tmp[i]:].copy()
                action.append(np.argmax(out_tmp))
        # 如果是一维动作空间
        else:
            out = tf.math.argmax(out, 1)
            action = out.numpy()[0]
        return action

    def load_model(self):
        """
        加载神经网络模型, 自动搜寻脚本路径下的'model_best.h5'文件，用户可复写此函数，用于客户端调度时被自动调用
        :return: None
        """
        script_dir = os.path.dirname(os.path.abspath(__file__))  # 获取当前路径
        model_path = os.path.join(script_dir, 'model_best.h5')
        if not os.path.exists(model_path):
            model_path = os.path.join(self.agent_file_path, 'model_best.h5')
            if not os.path.exists(model_path):
                if self.log is not None:
                    self.log.error("路径%s不存在，无法加载模型！" % model_path)
                return

        if os.path.isdir(model_path):
            model_paths = get_file_paths(model_path, fileformat='h5')
            if not model_paths:
                if self.log is not None:
                    self.log.error("路径%s下不存在 h5 模型, 请检查!！" % model_path)
                return
            model_file: str = max(model_paths, key=os.path.getctime)  # 最新的 h5 模型
        elif os.path.isfile(model_path):
            model_file = model_path
        else:
            raise FileNotFoundError
        self.model = tf.keras.models.load_model(model_file)

    def step_rl(self, time_elapse, situation, action_dict):
        """
        强化学习子类实现，进行每步决策对抗推演，接收强化学习输出的指令驱动装备执行指令;
        训练智能体时每个强化学习描述每步决策调用一次，配置多单元的多智能体强化学习算法中，每个单元算一个智能体，
        不是每个智能体被调用一次，请在函数里实现所有多智能体的动作调用。
        推理应用时，此函数可放在step函数调用过程中被调用。
        :param time_elapse:int, 推演仿真时间，单位：秒，取值0-推演总时间秒数
        :param situation: tuple, 单元字典和探测到的目标字典, units dict, contacts dict
        :param action_dict: dict, 智能体的单元id和动作，key:单元guid, value:动作信息字典，从act_trans函数计算而来
        :return:
        """
        # 在进行模型训练，使用rllib最新的策略，或者模型推理应用，根据输出的动作进行决策指令下达
        # 根据智能体控制对象的单元id，获取单元对象，调用接口下发动作指令
        for agent_unit_id, agent_action in action_dict.items():
            agent_unit = self.get_unit(agent_unit_id)  # 获取到智能体对象
            if agent_unit is not None:
                if "course" in agent_action:
                    # 取消上一时刻的航路点，以免影响当前航路点指令下发
                    # agent_unit.clear_plotted_course()
                    # 2.1 机动中执行左飞、右飞,或朝目标飞
                    agent_unit.plotted_course(agent_action["course"])  # 实体航线规划
                # 2.2 传感器指令下发
                if "radar" in agent_action:
                    agent_unit.EMCON_Obey_doctrine(False)  # 智能体单元不用遵循电磁管控条令
                    agent_unit.switch_radar(agent_action["radar"])  # 智能体单元雷达开关
                # 2.3 速度指令下达
                if "speed" in agent_action:
                    if 0 == agent_action["speed"]:
                        agent_unit.set_desired_speed(600)
                    elif 1 == agent_action["speed"]:
                        agent_unit.set_desired_speed(888)
                    elif 2 == agent_action["speed"]:
                        agent_unit.set_desired_speed(1088)
                    else:
                        agent_unit.set_desired_speed(800)

    def obs_trans(self, current_situation: Any) -> ObsArray:
        """
        智能体状态信息转换, 从灵弈系统的可解释态势转换为智能体训练框架可接受的训练数据。
        注意：强化学习智能体需实现此函数, 规则智能体无需实现
        配置多单元的多智能体强化学习算法中，每个单元算一个智能体，每步决策调用一次每个智能体此函数。
        :param current_situation: tuple, 单元字典和探测到的目标字典, units dict, contacts dict
        :return: agent_obs
        """
        agent_obs: list = []

        # n = 316  # 生成316个随机数
        # # 生成NumPy数组（直接操作数组更高效）
        # np_array = np.random.rand(n)
        # # 若需转为Python列表
        # agent_obs = np_array.tolist()
        # return agent_obs

        units = current_situation[0]
        contacts = current_situation[1]
        if self.agent_unit_id in units:
            unit_info = units[self.agent_unit_id]  # 取得智能体单元信息
            # 态势信息主要包括装备自身属性信息、雷达信息、电子干扰信息，导弹信息、相对友方态势信息、相对敌方态势信息
            # 获取智能体的自身属性信息，维度为10
            agent_obs.extend(self._get_agent_attribute_info(aircraft_info=unit_info))
            # 获取智能体的雷达传感器信息，维度为3
            agent_obs.extend(self._get_agent_radar_info(sensors_info=unit_info["sensors"]))
            # 获取智能体电子干扰传感器信息，维度为3
            agent_obs.extend(self._get_agent_jam_info(sensors_info=unit_info["sensors"]))
            # 获取智能体导弹信息，维度为18
            agent_obs.extend(self._get_agent_missile_info(loadouts_info=unit_info["loadout"]))
            # 获取智能体友机信息(包括战斗机和轰炸机)，维度为136
            agent_obs.extend(self._get_agent_teammate_info(units_info=units))
            # 获取智能体敌方目标信息，维度为146
            agent_obs.extend(self._get_agent_enemy_info(agent_unit_info=unit_info,
                                                        contacts_info=contacts))
        else:
            agent_obs = [0.0] * 316
        return agent_obs

    def act_trans(self, action_array, env_info: Any = None) -> ActionExe:
        """
        基于博弈算法框架输出的动作进行动作指令转换，转换为可读的指令信息，辅助下发动作指令给灵弈智能推演系统
        注意：强化学习智能体需实现此函数, 规则智能体无需实现.
        配置多单元的多智能体强化学习算法中，每个单元算一个智能体，每步决策调用一次每个智能体此函数。
        :param action_array: list or ActionArray 训练框架输出的智能体决策动作数据
        :param env_info: 环境信息，tuple, 单元字典和探测到的目标字典, units dict, contacts dict
        :return: dict, agent_act_trans：转换后的动作指令辅助信息
        """
        agent_action_dict: dict = {}
        if isinstance(action_array, list):
            action_len = len(action_array)
        else:
            action_len = action_array.size
        if action_len == 1:  # 解码：（encoded = [0]*3 + [1]*2 + [3]） DQN算法
            manevuer_action = action_array // 6  # 第一对6进行整取
            radar_action = (action_array % 6) // 3
            speed_action = action_array % 3
            print("agent name:{}, input:{}, action:{}".format(self.agent_unit_name, action_array,
                                                              (manevuer_action, radar_action, speed_action)))
        elif action_len == 3:
            manevuer_action = action_array[0]
            radar_action = action_array[1]
            speed_action = action_array[2]
            print("agent name:{}, id:{} action:{}".format(self.agent_unit_name, self.agent_unit_id, action_array))
        else:
            return agent_action_dict
        course_list: list = []
        units = env_info[0]
        if self.agent_unit_id in units:
            unit_info = units[self.agent_unit_id]  # 取得智能体单元信息
            current_lat, current_lon, current_height = unit_info["lat"], unit_info["lon"], unit_info["alt"]
            contact_id, contact_info = self._get_nearest_contact(
                current_course=(current_lat, current_lon, current_height),
                env_obs=env_info
            )

            next_course = self._get_next_course(course_action=manevuer_action,
                                                current_course=(
                                                    current_lat, current_lon,
                                                    current_height),
                                                env_obs=env_info,
                                                agent_info=unit_info)
            course_list.append(next_course)
            # 1、设置航路
            agent_action_dict["course"] = course_list

            # 2、雷达开关
            radar_switch = True if radar_action else False
            agent_action_dict["radar"] = radar_switch
            # 3、速度设置
            agent_action_dict["speed"] = speed_action

        return agent_action_dict

    def _get_next_course(self, course_action, current_course, env_obs, agent_info):
        """
        获取下一时刻的航路点
        Args:
            course_action: 智能体的决策动作
            current_course: 智能体当前航路点
            env_obs: 当前战场态势
            agent_info: 智能体当前态势
        Returns:
            next_course: 智能体下一时刻航路点
        """
        contact_id, contact_info = self._get_nearest_contact(current_course, env_obs)
        if course_action == 0:  # 追击敌机
            if contact_info is not None:
                target_position = contact_info[0], contact_info[1]
                next_course = target_position
            else:
                current_head = agent_info["head"]
                next_course = geo.get_geopoint_from_distance(current_course, current_head, 10000)
        elif course_action == 1:  # 左切飞行
            next_course = self._get_left_tangential(current_course, agent_info)
        else:  # 右切飞行
            next_course = self._get_right_tangential(current_course, agent_info)
        return next_course

    def _get_weapon_mount(self, agent_info, target_distance):
        """
        获取智能体可用武器挂载
        """
        weapon_id, mount_id = None, None
        if agent_info["loadout"] is None:
            return weapon_id, mount_id
        max_range = 0.0
        for loadout_id, loadout_info in agent_info["loadout"].items():
            if not isinstance(loadout_info, dict):
                continue
            mount_id = loadout_info["DBID"]
            if "WeaponRecs" in loadout_info.keys():
                for weapon_name, weapon_info in loadout_info["WeaponRecs"].items():
                    if not isinstance(weapon_info, dict):
                        continue
                    if "AirRangeMax" not in weapon_info:
                        continue
                    crt_range = weapon_info["AirRangeMax"]
                    if crt_range > 2 and crt_range / geo.NM2KM > target_distance - 3 and weapon_info["CurrentLoad"] > 0:
                        # 对空武器射程大于2海里， 大于目标距离公里数减3公里， 并且数量大于0
                        temp_weapon_id = weapon_info["WeaponDBID"]
                        if crt_range > max_range:
                            weapon_id = temp_weapon_id
        return weapon_id, mount_id

    def _get_nearest_contact(self, current_course, env_obs):
        """
        获取距离智能体最近的敌方目标（战斗机）
        """
        nearest_contact_id = None
        nearest_contact_distance = 100000000000
        nearest_contact_info = None

        for contact_id, contact_info in env_obs[1].items():
            if not isinstance(contact_info, dict):
                continue
            if "Type" not in contact_info:
                continue
            if contact_info["Type"] == 0 and "CurrentAltitude" in contact_info:  # 空中目标类型为0,判断是否包含需要信息
                enemy_lat, enemy_lon, enemy_height = contact_info['Lat'], contact_info['Lon'], \
                                                     contact_info['CurrentAltitude']
                distance = self._get_distance(own_position=current_course,
                                              enemy_position=(enemy_lat, enemy_lon, enemy_height))
                if distance < nearest_contact_distance:
                    nearest_contact_id = contact_id
                    nearest_contact_distance = distance
                    nearest_contact_info = (enemy_lat, enemy_lon, enemy_height)

        if not nearest_contact_id:  # 如果没有空中目标
            for contact_id, contact_info in env_obs[1].items():
                if not isinstance(contact_info, dict):
                    continue
                if "Type" not in contact_info.keys():
                    continue
                if contact_info["Type"] == 2:  # 水面目标类型为2
                    enemy_lat, enemy_lon, enemy_height = contact_info['Lat'], contact_info['Lon'], 0
                    distance = self._get_distance(own_position=current_course,
                                                  enemy_position=(enemy_lat, enemy_lon, enemy_height))
                    if distance < nearest_contact_distance:
                        nearest_contact_id = contact_id
                        nearest_contact_distance = distance
                        nearest_contact_info = (enemy_lat, enemy_lon, enemy_height)
        return nearest_contact_id, nearest_contact_info

    def _get_left_tangential(self, current_course, agent_info):
        """
        获取战斗机下一时刻左切航路点
        """
        current_lat, current_lon = agent_info["lat"], agent_info["lon"]
        current_head = agent_info["head"]
        desired_head = current_head - 45
        if desired_head <= 0:
            desired_head = 360 + desired_head

        desired_lat = current_lat + 2 * math.cos(desired_head * math.pi / 180)
        desired_lon = current_lon + 2 * math.sin(desired_head * math.pi / 180)
        if not desired_lat or not desired_lon:
            desired_lat = current_course[0]
            desired_lon = current_course[1]

        return (desired_lat, desired_lon)

    def _get_right_tangential(self, current_course, agent_info):
        """
        获取战斗机下一时刻右切航路点
        """
        current_lat, current_lon = agent_info["lat"], agent_info["lon"]
        current_head = agent_info["head"]
        desired_head = current_head + 45
        if desired_head >= 360:
            desired_head = desired_head - 360
        desired_lat = current_lat + 2 * math.cos(desired_head * math.pi / 180)
        desired_lon = current_lon + 2 * math.sin(desired_head * math.pi / 180)
        if not desired_lat or not desired_lon:
            desired_lat = current_course[0]
            desired_lon = current_course[1]

        return (desired_lat, desired_lon)

    def _get_antihead_course(self, current_course, agent_info):
        """
        获得当前航向角反方向航路点
        """
        current_lat, current_lon = agent_info["lat"], agent_info["lon"]
        current_head = agent_info["head"]
        desired_head = current_head - 180
        if desired_head <= 0:
            desired_head = 360 + desired_head
        desired_lat = current_lat + 2 * math.cos(desired_head * math.pi / 180)
        desired_lon = current_lon + 2 * math.sin(desired_head * math.pi / 180)
        if not desired_lat or not desired_lon:
            desired_lat = current_course[0]
            desired_lon = current_course[1]
        return (desired_lat, desired_lon)

    def _get_distance(self, own_position, enemy_position):
        # 返回距离，公里
        # 地球平均半径，单位为公里
        R = 6371.0
        # 将角度转换为弧度
        lat1_rad = math.radians(own_position[0])
        lon1_rad = math.radians(own_position[1])
        lat2_rad = math.radians(enemy_position[0])
        lon2_rad = math.radians(enemy_position[1])
        # 计算经纬度的差值
        dlat = lat2_rad - lat1_rad
        dlon = lon2_rad - lon1_rad
        # Haversine 公式
        a = math.sin(dlat / 2) ** 2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon / 2) ** 2
        c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
        # 地球表面上两点之间的大圆距离
        distance_on_surface = R * c
        # 计算高度差
        altitude_difference = enemy_position[2] - own_position[2]
        # 计算总距离
        total_distance = math.sqrt(distance_on_surface ** 2 + altitude_difference ** 2)
        return total_distance

    def _get_next_height(self, height_action, initial_height):
        """
        获取智能体下一时刻高度
        Args:
            height_action: 智能体决策动作
            initial_height: 智能体当前时刻高度
        Returns:
            next_height: 智能体下一时刻高度
        """
        if height_action == 0:  # 高度上升
            next_height = initial_height + 500
            if next_height >= 15000:  # 高度限制
                next_height = 15000
        else:  # 高度下降
            next_height = initial_height - 500
            if next_height <= 5000:
                next_height = 5000
        return next_height

    def _init_aircraft_info(self, aircraft_info):
        """
        初始化飞机信息
        """
        if self.initial_lon is None:
            self.initial_lon = aircraft_info['lon']
            self.initial_lat = aircraft_info['lat']
            self.lat_bottom = self.initial_lat - self.max_lat_move_range / 2
            if self.lat_bottom < -90:
                self.lat_bottom = 90.0
            lat_up = self.initial_lat + self.max_lat_move_range / 2
            if lat_up > 90:
                lat_up = 90.0
            self.valid_lat_range = lat_up - self.lat_bottom

            self.lon_min = self.initial_lon - self.max_lon_move_range / 2

        if self.initial_fuel is None:
            # 还没计算飞机燃油量，计算下
            self.initial_fuel = 0.0
            if "fuel" in aircraft_info:
                for fuel_id, info in aircraft_info["fuel"].items():
                    if "CQ" in info:
                        self.initial_fuel += info["CQ"]
            # 考虑副油箱
            if "loadout" in aircraft_info:
                for load_id, info in aircraft_info["loadout"].items():
                    if "WeaponRecs" in info:
                        for recs_id, recd_info in info["WeaponRecs"].items():
                            if "IsTank" in recd_info and "FuelTank" in recd_info and "CurrentLoad" in recd_info:
                                if recd_info["IsTank"]:
                                    # 是挂载副油箱
                                    self.initial_fuel += recd_info["CurrentLoad"] * recd_info["FuelTank"]

    def _get_current_air_fule(self, aircraft_info):
        """
        获取飞机当前油量
        """
        current_fuel = 0.0
        if "fuel" in aircraft_info:
            for fuel_id, info in aircraft_info["fuel"].items():
                if "CQ" in info:
                    current_fuel += info["CQ"]
        # 考虑副油箱
        if "loadout" in aircraft_info:
            for load_id, info in aircraft_info["loadout"].items():
                if "WeaponRecs" in info:
                    for recs_id, recd_info in info["WeaponRecs"].items():
                        if "IsTank" in recd_info and "FuelTank" in recd_info and "CurrentLoad" in recd_info:
                            if recd_info["IsTank"]:
                                # 是挂载副油箱
                                current_fuel += recd_info["CurrentLoad"] * recd_info["FuelTank"]
        return current_fuel

    def _get_agent_attribute_info(self, aircraft_info):
        """
        获取飞机的属性信息
        Args:
            aircraft_info: 飞机的全部态势信息
        Returns:
            attribute_obs: 飞机的属性信息
        """
        attribute_obs: list = []
        self._init_aircraft_info(aircraft_info)

        # 归一化经度
        cal_lon = aircraft_info["lon"]
        if cal_lon < 0 and self.lon_min > 0:
            cal_lon = 360 + cal_lon
        to_one_lon = (cal_lon - self.lon_min) / self.max_lon_move_range
        # 归一化纬度
        to_one_lat = (aircraft_info["lat"] - self.lat_bottom) / self.valid_lat_range
        # 归一化高度
        to_on_alt = aircraft_info["alt"] / Constant.ALT
        # 归一化速度
        to_one_speed = (aircraft_info["speed"] - self.speed_range[0]) / (self.speed_range[1] - self.speed_range[0])
        # 归一化剩余燃油
        if self.initial_fuel > 500.0:
            to_one_fuel = (self._get_current_air_fule(aircraft_info) - 500) / (self.initial_fuel - 500.0)
        else:
            to_one_fuel = self._get_current_air_fule(aircraft_info) / self.initial_fuel
        # 归一化飞行状态
        ops_code = 0
        if "airops" in aircraft_info:
            if 'Con' in aircraft_info["airops"]:
                ops_code = aircraft_info["airops"]['Con']
            elif 'Condition' in aircraft_info["airops"]:
                ops_code = aircraft_info["airops"]['Condition']
        to_one_air_status = ops_code / 26  # 26：最大飞行状态
        to_one_status = aircraft_info["status"] / 22  # 22:最大活动单元状态
        # 单元设置的航线
        to_one_course = 0.5  # 0.5：向前，0：向左；1：向右
        if "course" in aircraft_info and len(aircraft_info["course"]) > 0:
            first_point = aircraft_info["course"][0]
            target_pos = first_point["latitude"], first_point["longitude"]
            crt_pos = aircraft_info["lat"], aircraft_info["lon"]
            target_head = geo.get_azimuth(crt_pos, target_pos)
            delta = target_head - aircraft_info['head']
            # 调整到-180到180度之间
            delta = (delta + 360) % 360
            if delta > 180:
                delta -= 360
            to_one_course = 0.5 + delta / 360

        attribute_obs.append(to_one_lon)  # 智能体经度
        attribute_obs.append(to_one_lat)  # 智能体纬度
        attribute_obs.append(to_on_alt)  # 智能体高度
        attribute_obs.append(to_one_speed)  # 智能体速度
        attribute_obs.append(aircraft_info["head"] / 360.0)  # 智能体航向角
        attribute_obs.append(to_one_fuel)  # 智能体飞机燃油
        attribute_obs.append(to_one_air_status)  # 智能体飞机飞行状态
        attribute_obs.append(to_one_status)  # 智能体单元状态
        attribute_obs.append(to_one_course)  # 智能体飞机下一步航向差值
        attribute_obs.append(aircraft_info["fuelrate"]/5.0)  # 智能体燃油速率

        len_obs = len(attribute_obs)
        for i in range(len_obs):
            if attribute_obs[i] > 1.0:
                attribute_obs[i] = 1.0
            elif attribute_obs[i] < 0.0:
                attribute_obs[i] = 0.0
        return attribute_obs

    def _get_agent_radar_info(self, sensors_info):
        """
        获取飞机的雷达、电子干扰等传感器信息, 最多采集3个雷达开关信息
        """
        sensor_obs: list = [0.0, 0.0, 0.0]
        range_sort = [None, None, None]
        for sensor_id, sensor_info in sensors_info.items():
            if "CanBeActive" in sensor_info and sensor_info["CanBeActive"] and "Component" in sensor_info:
                if "MaxRange" in sensor_info and "IsActive" in sensor_info and "Type" in sensor_info["Component"]:
                    if sensor_info["Component"]["Type"] != 2001:
                        continue
                    for i in range(3):
                        if range_sort[i] is None:
                            range_sort[i] = sensor_info["MaxRange"]
                            if sensor_info["IsActive"]:
                                sensor_obs[i] = 1.0
                            else:
                                sensor_obs[i] = 0.0
                            break
                        else:
                            if sensor_info["MaxRange"] > range_sort[i]:
                                # 仅用探测范围大的
                                if i < 2:
                                    range_sort[i+1] = range_sort[i]
                                    sensor_obs[i+1] = sensor_obs[i]
                                    if sensor_info["IsActive"]:
                                        sensor_obs[i] = 1.0
                                    else:
                                        sensor_obs[i] = 0.0
                                    break
                                elif i == 2:
                                    if sensor_info["IsActive"]:
                                        sensor_obs[i] = 1.0
                                    else:
                                        sensor_obs[i] = 0.0
        return sensor_obs

    def _get_agent_jam_info(self, sensors_info):
        """
        获取飞机的电子干扰信息, 最多采集3个电子干扰开关信息
        Args:
            sensors_info: 飞机的传感器态势信息
        Returns:
            disturb_obs: 飞机的电子干扰信息
        """
        disturb_obs: list = [0.0, 0.0, 0.0]
        set_index = 0
        for sensor_name, sensor_info in sensors_info.items():
            if "CanBeActive" in sensor_info and sensor_info["CanBeActive"] and "Component" in sensor_info:
                if "MaxRange" in sensor_info and "IsActive" in sensor_info and "Type" in sensor_info["Component"]:
                    if sensor_info["Component"]["Type"] != 3002:
                        continue
                    if sensor_info["IsActive"]:
                        disturb_obs[set_index] = 1.0
                    else:
                        disturb_obs[set_index] = 0.0
                    set_index += 1
                    if set_index == 3:
                        break
        return disturb_obs

    def _get_agent_missile_info(self, loadouts_info):
        """
        获取智能体的导弹信息
        Args:
            loadouts_info: 飞机的武器挂载信息
        Returns:
            missile_obs: 飞机的导弹信息
        """
        missile_obs: list = []
        if loadouts_info is not None:
            missiles_num = 0
            for loadout_name, loadout_info in loadouts_info.items():
                if not isinstance(loadout_info, dict):
                    continue
                if "WeaponRecs" in loadout_info.keys():
                    for weapon_name, weapon_info in loadout_info["WeaponRecs"].items():
                        if not isinstance(weapon_info, dict):
                            continue
                        if "AirRangeMax" not in weapon_info or weapon_info["AirRangeMax"] < 5:
                            # 需要有空空弹
                            continue
                        current_load = weapon_info["CurrentLoad"]
                        missiles_num += current_load
                        if current_load > 0:
                            for current_load_index in range(current_load):
                                missile_obs.append(0)  # 导弹状态，0表示未发射，1表示已发射
                                range_one_hot = weapon_info["AirRangeMax"] / Constant.AIR_RANGE_MAX
                                if range_one_hot > 1.0:
                                    range_one_hot = 1.0
                                missile_obs.append(range_one_hot)  # 导弹对空最大攻击距离
                                missile_obs.append(
                                    weapon_info["AirRangeMin"] / Constant.AIR_RANGE_MAX)  # 导弹对空最小攻击距离
        else:
            missile_obs.extend([1, 0, 0] * self.max_missile_num)
            return missile_obs

        if missiles_num < self.max_missile_num:  # 每架战斗机挂载6枚空空导弹，不足的或者已发射的均补零
            missile_obs.extend([1, 0, 0] * (self.max_missile_num - missiles_num))
        elif missiles_num > self.max_missile_num:
            missile_obs = missile_obs[: self.max_missile_num * 3]
        if len(missile_obs) < 18:
            diff = 18 - len(missile_obs)
            missile_obs.extend([0] * diff)
        if len(missile_obs) > 18:
            missile_obs = self.truncate_list(missile_obs, len(missile_obs) - 18)

        return missile_obs

    @staticmethod
    def truncate_list(lst, n):
        valid_n = min(n, len(lst))
        return lst[:-valid_n] if valid_n else lst.copy()

    def get_air_info_one_hot(self, current_position, aircraft_info):
        """
        获取友机相对当前飞机观测
        """
        # 归一化经度
        cal_lon = aircraft_info["lon"]
        if cal_lon < 0 and self.lon_min > 0:
            cal_lon = 360 + cal_lon
        to_one_lon = (cal_lon - self.lon_min) / self.max_lon_move_range
        # 归一化纬度
        to_one_lat = (aircraft_info["lat"] - self.lat_bottom) / self.valid_lat_range
        # 归一化高度
        to_on_alt = aircraft_info["alt"] / Constant.ALT
        # 归一化速度
        to_one_speed = (aircraft_info["speed"] - self.speed_range[0]) / (self.speed_range[1] - self.speed_range[0])
        to_one_status = aircraft_info["status"] / 22  # 22:最大活动单元状态
        # 友机单元相对本单元航向
        target_pos = aircraft_info["lat"], aircraft_info["lon"]
        target_head = geo.get_azimuth(current_position, target_pos) / 360.0
        crt_head = aircraft_info['head'] / 360.0
        distance = geo.get_horizontal_distance(target_pos, current_position) / 500
        friend_obs = [to_one_lon, to_one_lat, to_on_alt, to_one_speed, to_one_status, target_head, crt_head, distance]
        len_obs = len(friend_obs)
        for i in range(len_obs):
            if friend_obs[i] > 1.0:
                friend_obs[i] = 1.0
            elif friend_obs[i] < 0.0:
                friend_obs[i] = 0.0
        return friend_obs

    def _get_agent_teammate_info(self, units_info):
        """
        获取智能体友机信息：包括战斗机/预警机/轰炸机
        Args:
            units_info: 全部单元的态势信息
        Returns:
            teammate_obs: 飞机的友机信息
        """
        teammate_obs_all: list = []
        teammate_fighter_obs: dict = {}  # 友方战斗机态势
        teammate_bomber_obs: dict = {}  # 友方轰炸机态势
        teammate_aew_obs: dict = {}  # 友方轰炸机态势
        teammate_fighter_num: int = 0  # 友方战斗机存活数量
        teammate_bomber_num: int = 0  # 友方轰炸机存活数量
        teammate_aew_num: int = 0  # 友方轰炸机存活数量
        teammate_fighter_id: list = []  # 友方战斗机ID
        teammate_bomber_id: list = []  # 友方轰炸机ID
        teammate_aew_id: list = []  # 友方预警机ID
        teammate_fighter_distance: list = []  # 友方战斗机相对距离
        teammate_bomber_distance: list = []  # 友方轰炸机相对距离
        teammate_aew_distance: list = []  # 友方预警机相对距离
        current_lat, current_lon, current_alt = 0.0, 0.0, 0.0
        if self.agent_unit_id in units_info:
            unit_info = units_info[self.agent_unit_id]
            current_lat, current_lon, current_alt = unit_info["lat"], unit_info["lon"], unit_info["alt"]

        for unit_id, unit_info in units_info.items():
            if unit_id == self.agent_unit_id:
                continue
            if unit_info["category"] != "Aircraft":
                continue
            if unit_info["fuelrate"] < 0.1:
                # 未耗油，认为没有起飞
                continue
            teammate_obs: list = []
            team_pos = unit_info['lat'], unit_info['lon']
            distance = geo.get_horizontal_distance((current_lat, current_lon), team_pos)  # 与友机相对距离
            if distance > 500:
                # 500公里外飞机不考虑
                continue
            teammate_obs_cal = self.get_air_info_one_hot((current_lat, current_lon), unit_info)
            teammate_obs.extend(teammate_obs_cal)
            air_type = unit_info["type"]
            if air_type == 2001 or air_type == 2002:  # 战斗机类型为2001, 多用途：2002
                teammate_fighter_num += 1
                teammate_fighter_obs[unit_id] = teammate_obs
                teammate_fighter_id.append(unit_id)
                teammate_fighter_distance.append(distance)
            elif air_type == 3101:  # 轰炸机类型为3101
                teammate_bomber_num += 1
                teammate_bomber_obs[unit_id] = teammate_obs
                teammate_bomber_id.append(unit_id)
                teammate_bomber_distance.append(distance)
            elif air_type == 4002:  # 预警机类型为4002
                teammate_aew_num += 1
                teammate_aew_obs[unit_id] = teammate_obs
                teammate_aew_id.append(unit_id)
                teammate_aew_distance.append(distance)
            else:
                continue

        # 友方战斗机态势按照距离由远及近排列
        teammate_fighter_sorted_obs: list = []  # 按照距离排序的战斗机态势
        teammate_fighter_sorted_ids = [x for _, x in sorted(zip(teammate_fighter_distance, teammate_fighter_id))]
        for teammate_fighter_sorted_id in teammate_fighter_sorted_ids:
            teammate_fighter_sorted_obs.extend(teammate_fighter_obs[teammate_fighter_sorted_id])
        if teammate_fighter_num < self.teammate_fighter_num:
            teammate_fighter_sorted_obs.extend(
                [0, 0, 0, 0, 0, 0, 0, 0] * (self.teammate_fighter_num - teammate_fighter_num))
        elif teammate_fighter_num > self.teammate_fighter_num:
            teammate_fighter_sorted_obs = teammate_fighter_sorted_obs[: self.teammate_fighter_num * 8]

        # 友方轰炸机态势按照距离由远及近排列
        teammate_bomber_sorted_obs: list = []  # 按照距离排序的轰炸机态势
        teammate_bomber_sorted_ids = [x for _, x in sorted(zip(teammate_bomber_distance, teammate_bomber_id))]
        for teammate_bomber_sorted_id in teammate_bomber_sorted_ids:
            teammate_bomber_sorted_obs.extend(teammate_bomber_obs[teammate_bomber_sorted_id])
        if teammate_bomber_num < self.teammate_bomber_num:
            teammate_bomber_sorted_obs.extend(
                [0, 0, 0, 0, 0, 0, 0, 0] * (self.teammate_bomber_num - teammate_bomber_num))
        elif teammate_bomber_num > self.teammate_bomber_num:
            teammate_bomber_sorted_obs = teammate_bomber_sorted_obs[: self.teammate_bomber_num * 8]

        # 友方预警机态势按照距离由近及远排列
        teammate_aew_sorted_obs: list = []  # 按照距离排序的轰炸机态势
        teammate_aew_sorted_ids = [x for _, x in sorted(zip(teammate_aew_distance, teammate_aew_id))]
        for teammate_aew_sorted_id in teammate_aew_sorted_ids:
            teammate_aew_sorted_obs.extend(teammate_aew_obs[teammate_aew_sorted_id])
        if teammate_aew_num < self.teammate_aew_num:
            teammate_aew_sorted_obs.extend(
                [0, 0, 0, 0, 0, 0, 0, 0] * (self.teammate_aew_num - teammate_aew_num))
        elif teammate_aew_num > self.teammate_aew_num:
            teammate_aew_sorted_obs = teammate_aew_sorted_obs[: self.teammate_aew_num * 8]

        teammate_obs_all.extend(teammate_fighter_sorted_obs)
        teammate_obs_all.extend(teammate_bomber_sorted_obs)
        teammate_obs_all.extend(teammate_aew_sorted_obs)

        if len(teammate_obs_all) < 136:
            diff = 136 - len(teammate_obs_all)
            teammate_obs_all.extend([0] * diff)
        if len(teammate_obs_all) > 136:
            teammate_obs_all = self.truncate_list(teammate_obs_all, len(teammate_obs_all) - 136)
        return teammate_obs_all

    def _get_agent_enemy_info(self, agent_unit_info, contacts_info):
        """
        获取智能体的敌机信息、周围导弹信息
        Args:
            agent_unit_info: 单元信息
            contacts_info: 探测到的敌方目标信息
        Returns:
            enemy_obs: 智能体的敌方目标信息
        """
        air_max_distance = 300  # 考虑最大飞机距离：300千米
        missile_max_distance = 100  # 考虑最大导弹距离：100千米
        ship_max_distance = 200  # 考虑最大舰船距离：200千米
        facility_max_distance = 200  # 考虑最大舰船距离：200千米

        if contacts_info is None:
            return [0, 0, 0, 0, 0, 0, 0, 0] * 16 + [0, 0, 0, 0, 0, 0] * self.enemy_ship_num
        enemy_obs_all: list = []
        enemy_aircraft_obs: dict = {}  # 敌方战斗机态势
        enemy_ship_obs: dict = {}  # 敌方舰艇态势
        enemy_facility_obs: dict = {}  # 敌方舰艇态势
        enemy_missile_obs: dict = {}  # 敌方舰艇态势
        enemy_aircraft_num: int = 0  # 敌方战斗机存活数量
        enemy_ship_num: int = 0  # 敌方舰艇存活数量
        enemy_facility_num: int = 0  # 敌方地面设施存活数量
        enemy_missile_num: int = 0  # 敌方导弹存活数量
        enemy_aircraft_id: list = []  # 敌方战斗机ID
        enemy_ship_id: list = []  # 敌方舰艇ID
        enemy_facility_id: list = []  # 敌方设施ID
        enemy_missile_id: list = []  # 敌方导弹ID
        enemy_aircraft_distance: list = []  # 敌方战斗机相对距离
        enemy_ship_distance: list = []  # 敌方舰艇相对距离
        enemy_facility_distance: list = []  # 敌方设施相对距离
        enemy_missile_distance: list = []  # 敌方导弹相对距离
        current_lat, current_lon = agent_unit_info["lat"], agent_unit_info["lon"]
        current_alt = agent_unit_info["alt"]
        current_air_pos = current_lat, current_lon
        for contact_name, contact_info in contacts_info.items():
            if not isinstance(contact_info, dict):
                continue
            if "Type" not in contact_info.keys():
                continue

            contact_type = contact_info["Type"]
            if contact_type not in (0, 1, 2, 7, 8):
                continue

            if 'Lat' in contact_info and 'Lon' in contact_info:
                target_pos = contact_info["Lat"], contact_info['Lon']
                distance = geo.get_horizontal_distance(target_pos, current_air_pos)
                if contact_type == 0:
                    # 飞机
                    if distance > air_max_distance:
                        continue
                elif contact_type == 1:
                    # 导弹
                    if distance > missile_max_distance:
                        continue
                elif contact_type == 2:
                    # 舰船
                    if distance > ship_max_distance:
                        continue
                elif contact_type in (7, 8):
                    # 舰船
                    if distance > facility_max_distance:
                        continue
            else:
                continue

            enemy_obs: list = []
            # 归一化目标的经度
            to_one_lon = 0.0
            if "Lon" in contact_info:
                cal_lon = contact_info["Lon"]
                if cal_lon < 0 and self.lon_min > 0:
                    cal_lon = 360 + cal_lon
                to_one_lon = (cal_lon - self.lon_min) / self.max_lon_move_range

            # 归一化目标纬度
            to_one_lat = 0.0
            if "Lat" in contact_info:
                to_one_lat = (contact_info["Lat"] - self.lat_bottom) / self.valid_lat_range

            # 归一化高度
            to_on_alt = 0.0
            if "CurrentAltitude" in contact_info:
                to_on_alt = contact_info["CurrentAltitude"] / Constant.ALT

            # 归一化速度
            to_one_speed = 0.0
            if "CurrentSpeed" in contact_info:
                if contact_info["Type"] == 0:  # 空中飞机类型为0
                    to_one_speed = (contact_info["CurrentSpeed"] - self.speed_range[0]) / (
                            self.speed_range[1] - self.speed_range[0])
                elif contact_info["Type"] == 1:  # 导弹
                    to_one_speed = (contact_info["CurrentSpeed"] - self.speed_range[0]) / (
                            self.speed_range[1] - self.speed_range[0])
                elif contact_info["Type"] == 2:  # 舰船
                    to_one_speed = contact_info["CurrentSpeed"] / 30
                elif contact_info["Type"] in (7, 8):  # 地面设施
                    to_one_speed = contact_info["CurrentSpeed"] / 30

            to_one_head = 0.0
            if "CurrentHeading" in contact_info:
                to_one_head = contact_info["CurrentHeading"] / Constant.HEAD

            to_one_pitch = 0.0
            if 'Pitch' in contact_info:
                to_one_pitch = (contact_info["Pitch"] + 90) / 180.0  # 目标俯仰角

            to_one_dis = distance / 500  # 目标相对距离

            to_target_me = 0.0  # 目标指向自己程度
            if contact_type in (0, 1):
                if "CurrentHeading" in contact_info:
                    contact_head = contact_info["CurrentHeading"]
                    target_head = geo.get_azimuth(target_pos, current_air_pos)
                    delta = target_head - contact_head
                    # 调整到-180到180度之间
                    delta = (delta + 360) % 360
                    if delta > 180:
                        delta -= 360
                    if -90 < delta < 90:
                        to_target_me = 1 - abs(delta)/90

            if contact_type == 0:
                # 飞机---9维
                enemy_obs.append(to_one_lon)  # 经度
                enemy_obs.append(to_one_lat)  # 纬度
                enemy_obs.append(to_on_alt)  # 高度
                enemy_obs.append(to_one_speed)  # 速度
                enemy_obs.append(to_one_head)  # 目标航向
                enemy_obs.append(to_one_pitch)  # 目标俯仰角
                enemy_obs.append(to_target_me)  # 目标指向本单元程度
                enemy_obs.append(to_one_dis)  # 目标相对距离
                # 飞机具体类型
                air_type = 0.0
                if "IDStatus" in contact_info and contact_info["IDStatus"] > 2:
                    if "ActType" in contact_info:
                        if contact_info["ActType"] == 2001 or contact_info["ActType"] == 2002:
                            # 战斗机和多用途战机
                            air_type = 1.0
                        elif contact_info["ActType"] in (3101, 3001):
                            # 轰炸机、攻击机
                            air_type = 0.2
                        elif contact_info["ActType"] == 4002:
                            # 预警机
                            air_type = 0.8
                        elif contact_info["ActType"] == 4001:
                            # 电子战机
                            air_type = 0.6
                        elif contact_info["ActType"] in (7001, 7002, 7003, 7004, 7005):
                            # 侦察机、情报收集飞机等
                            air_type = 0.4
                enemy_obs.append(air_type)
            elif contact_type == 1:
                # 导弹---8维
                enemy_obs.append(to_one_lon)  # 经度
                enemy_obs.append(to_one_lat)  # 纬度
                enemy_obs.append(to_on_alt)  # 高度
                enemy_obs.append(to_one_speed)  # 速度
                enemy_obs.append(to_one_head)  # 目标航向
                enemy_obs.append(to_one_pitch)  # 目标俯仰角
                enemy_obs.append(to_target_me)  # 目标指向本单元程度
                enemy_obs.append(to_one_dis)  # 目标相对距离
            elif contact_type == 2:
                # 舰船--6纬
                enemy_obs.append(to_one_lon)  # 经度
                enemy_obs.append(to_one_lat)  # 纬度
                enemy_obs.append(to_on_alt)  # 高度
                enemy_obs.append(to_one_speed)  # 速度
                enemy_obs.append(to_one_dis)  # 目标相对距离
                # 舰船具体类型
                ship_type = 0.0
                if "IDStatus" in contact_info and contact_info["IDStatus"] > 2:
                    if "ActType" in contact_info:
                        unit_type = contact_info["ActType"]
                        if 2000 < unit_type < 2012:
                            # 航母
                            ship_type = 1.0
                        elif 3000 < unit_type < 3008:
                            # 战列舰
                            ship_type = 0.2
                        elif 3100 < unit_type < 3115:
                            # 巡洋舰
                            ship_type = 0.8
                        elif 3200 < unit_type < 3213:
                            # 驱逐舰
                            ship_type = 0.6
                        elif 3300 < unit_type < 3307:
                            # 护卫舰、
                            ship_type = 0.4
                enemy_obs.append(ship_type)
            else:
                # 地面设施---6维度
                enemy_obs.append(to_one_lon)  # 经度
                enemy_obs.append(to_one_lat)  # 纬度
                enemy_obs.append(to_on_alt)  # 高度
                enemy_obs.append(to_one_dis)  # 目标相对距离
                # 地面设施类别
                unit_type = 0.0
                unit_category = 0
                if "IDStatus" in contact_info and contact_info["IDStatus"] > 2:
                    if "ActType" in contact_info:
                        facit_type = contact_info["ActType"]
                        if facit_type < 3007:
                            # 固定建筑
                            unit_type = 0.2
                        elif facit_type == 3007 or facit_type == 5004:
                            # 雷达站或车载雷达
                            unit_type = 0.4
                        elif facit_type == 5001:
                            # 移动车辆
                            unit_type = 1.0
                        elif facit_type == 5003:
                            # 移动车辆
                            unit_type = 0.8
                        else:
                            unit_type = 0.6
                    if "ActCategory" in contact_info:
                        if contact_info["ActCategory"] == 5001:
                            # 是移动车辆
                            unit_category = 1
                enemy_obs.append(unit_type)
                enemy_obs.append(unit_category)
            len_obs = len(enemy_obs)
            for i in range(len_obs):
                if enemy_obs[i] > 1.0:
                    enemy_obs[i] = 1.0
                elif enemy_obs[i] < 0.0:
                    enemy_obs[i] = 0.0

            if contact_info["Type"] == 0:  # 空中目标类型为0
                enemy_aircraft_num += 1
                enemy_aircraft_obs[contact_name] = enemy_obs
                enemy_aircraft_id.append(contact_name)
                enemy_aircraft_distance.append(distance)
            if contact_info["Type"] == 2:  # 水面舰船目标类型为2
                enemy_ship_num += 1
                enemy_ship_obs[contact_name] = enemy_obs
                enemy_ship_id.append(contact_name)
                enemy_ship_distance.append(distance)
            if contact_info["Type"] == 1:  # 导弹目标类型：1
                enemy_missile_num += 1
                enemy_missile_obs[contact_name] = enemy_obs
                enemy_missile_id.append(contact_name)
                enemy_missile_distance.append(distance)
            if contact_info["Type"] in (7, 8):  # 导弹目标类型：1
                enemy_facility_num += 1
                enemy_facility_obs[contact_name] = enemy_obs
                enemy_facility_id.append(contact_name)
                enemy_facility_distance.append(distance)

        # 敌方飞机态势按照距离由近及远排列
        enemy_aircraft_sorted_obs: list = []  # 按照距离排序的敌方飞机态势
        enemy_aircraft_sorted_ids = [x for _, x in sorted(zip(enemy_aircraft_distance, enemy_aircraft_id))]
        if len(enemy_aircraft_sorted_ids) > self.enemy_aircraft_num:
            enemy_aircraft_sorted_ids = enemy_aircraft_sorted_ids[:self.enemy_aircraft_num]
        for enemy_aircraft_sorted_id in enemy_aircraft_sorted_ids:
            enemy_aircraft_sorted_obs.extend(enemy_aircraft_obs[enemy_aircraft_sorted_id])
        if enemy_aircraft_num < self.enemy_aircraft_num:
            enemy_aircraft_sorted_obs.extend([0, 0, 0, 0, 0, 0, 0, 0, 0] * (self.enemy_aircraft_num - enemy_aircraft_num))

        # 敌方导弹态势按照距离由近及远排列
        enemy_missile_sorted_obs: list = []  # 按照距离排序的敌方导弹态势
        enemy_missile_sorted_ids = [x for _, x in sorted(zip(enemy_missile_distance, enemy_missile_id))]
        if len(enemy_missile_sorted_ids) > self.enemy_missile_num:
            enemy_missile_sorted_ids = enemy_missile_sorted_ids[:self.enemy_missile_num]
        for enemy_missile_sorted_id in enemy_missile_sorted_ids:
            enemy_missile_sorted_obs.extend(enemy_missile_obs[enemy_missile_sorted_id])
        if enemy_missile_num < self.enemy_missile_num:
            enemy_missile_sorted_obs.extend(
                [0, 0, 0, 0, 0, 0, 0, 0] * (self.enemy_missile_num - enemy_missile_num))

        # 敌方舰艇态势按照距离由近及远排列
        enemy_ship_sorted_obs: list = []  # 按照距离排序的敌方舰艇态势
        enemy_ship_sorted_ids = [x for _, x in sorted(zip(enemy_ship_distance, enemy_ship_id))]
        if len(enemy_ship_sorted_ids) > self.enemy_ship_num:
            enemy_ship_sorted_ids = enemy_ship_sorted_ids[:self.enemy_ship_num]
        for enemy_ship_sorted_id in enemy_ship_sorted_ids:
            enemy_ship_sorted_obs.extend(enemy_ship_obs[enemy_ship_sorted_id])
        if enemy_ship_num < self.enemy_ship_num:
            enemy_ship_sorted_obs.extend([0, 0, 0, 0, 0, 0] * (self.enemy_ship_num - enemy_ship_num))

        # 敌方地面单元态势按照距离由近及远排列
        enemy_facility_sorted_obs: list = []  # 按照距离排序的敌方舰艇态势
        enemy_facility_sorted_ids = [x for _, x in sorted(zip(enemy_facility_distance, enemy_facility_id))]
        if len(enemy_facility_sorted_ids) > self.enemy_facility_num:
            enemy_facility_sorted_ids = enemy_facility_sorted_ids[:self.enemy_facility_num]
        for enemy_facility_sorted_id in enemy_facility_sorted_ids:
            enemy_facility_sorted_obs.extend(enemy_facility_obs[enemy_facility_sorted_id])
        if enemy_facility_num < self.enemy_facility_num:
            enemy_facility_sorted_obs.extend([0, 0, 0, 0, 0, 0] * (self.enemy_facility_num - enemy_facility_num))

        enemy_obs_all.extend(enemy_aircraft_sorted_obs)
        enemy_obs_all.extend(enemy_missile_sorted_obs)
        enemy_obs_all.extend(enemy_ship_sorted_obs)
        enemy_obs_all.extend(enemy_facility_sorted_obs)

        data_len = 146
        if len(enemy_obs_all) < data_len:
            diff = data_len - len(enemy_obs_all)
            enemy_obs_all.extend([0] * diff)
        if len(enemy_obs_all) > data_len:
            enemy_obs_all = self.truncate_list(enemy_obs_all, len(enemy_obs_all) - data_len)
        return enemy_obs_all

    def get_reward(self, *args, **kwargs) -> float:
        """
        获取智能体的奖励
        Args:
            *args:
            **kwargs:
        Returns:
            reward: 智能体的奖励值
        """
        own_obs, enemy_obs, last_own_obs, last_enemy_obs, action_dict = args  # 红蓝方态势信息
        reward: float = 0.0
        # 1、智能体存活奖励
        crt_units = own_obs[0]
        agent_existence, agent_obs = self._check_unit_existence(unit_ID=self.agent_unit_id,
                                                                units_dict=crt_units)
        last_units = last_own_obs[0]
        last_agent_existence, last_agent_obs = self._check_unit_existence(unit_ID=self.agent_unit_id,
                                                                          units_dict=last_units)
        if last_agent_existence and agent_existence:  # 如果上一时刻和当前时刻智能体均存活，给予正向奖励
            reward += 1
        if last_agent_existence and not agent_existence:  # 如果上一时刻智能体存活，当前时刻智能体被击毁，给予负向奖励
            reward -= 10

        # 6、与敌方最近目标的距离和角度奖励
        if agent_existence:
            agent_contacts_distance_list: list = []  # 当前智能体与敌方目标的距离列表
            last_agent_contacts_distance_list: list = []  # 上一时刻智能体与敌方目标间的距离列表
            agent_contacts_id_list: list = []  # 当前智能体目标ID
            last_contacts = last_own_obs[1]
            agent_unit_position = agent_obs['lat'], agent_obs['lon'], agent_obs["alt"]
            last_agent_unit_position = last_agent_obs['lat'], last_agent_obs['lon'], last_agent_obs['alt']
            for contact_id, contact_info in last_contacts.items():
                if not isinstance(contact_info, dict):
                    continue
                if "Type" not in contact_info:
                    continue
                if contact_info["Type"] != 0:
                    # 仅计算敌飞机
                    continue
                if "CurrentAltitude" not in contact_info or "Lon" not in contact_info or "Lat" not in contact_info:
                    continue
                last_agent_contacts_distance = self._get_distance(
                    own_position=last_agent_unit_position,
                    enemy_position=(contact_info["Lat"], contact_info["Lon"], contact_info["CurrentAltitude"]))
                if last_agent_contacts_distance < 1000:
                    # 仅判断1000公里内飞机
                    last_agent_contacts_distance_list.append(last_agent_contacts_distance)

                agent_contacts_distance = self._get_distance(
                    own_position=agent_unit_position,
                    enemy_position=(contact_info["Lat"], contact_info["Lon"], contact_info["CurrentAltitude"]))
                if agent_contacts_distance < 1000:
                    agent_contacts_distance_list.append(agent_contacts_distance)
                    agent_contacts_id_list.append(contact_id)

            if last_agent_contacts_distance_list and agent_contacts_distance_list:
                # 距离奖励
                # 上一时刻智能体与敌方目标的最小距离
                min_last_agent_contacts_distance = min(last_agent_contacts_distance_list)
                # 当前智能体与敌方目标的最小距离
                min_agent_contacts_distance = min(agent_contacts_distance_list)
                # 决策间隔30秒，如朝敌方飞机飞，大概飞9公里
                if min_last_agent_contacts_distance > min_agent_contacts_distance + 6:
                    reward += 10
                elif min_last_agent_contacts_distance > min_agent_contacts_distance + 3:
                    reward += 5
                elif min_last_agent_contacts_distance < min_agent_contacts_distance - 6:
                    reward -= 10
                elif min_last_agent_contacts_distance < min_agent_contacts_distance - 3:
                    reward -= 5
                # 角度奖励
                min_distance_contact_id = \
                    [x for _, x in sorted(zip(agent_contacts_distance_list, agent_contacts_id_list))][0]
                angle = self._get_relative_angle(own_position=agent_unit_position,
                                                 enemy_position=(last_contacts[min_distance_contact_id]["Lat"],
                                                                 last_contacts[min_distance_contact_id]["Lon"],
                                                                 last_contacts[min_distance_contact_id][
                                                                     "CurrentAltitude"]))
                if abs(agent_obs['head'] - angle) <= 30:  # 在左右30度范围内给予正向奖励
                    reward += 10
                else:
                    reward -= 5

        # reward = random.random()
        return reward

    def _get_relative_angle(self, own_position, enemy_position):
        x = enemy_position[0] - own_position[0]
        y = enemy_position[1] - own_position[1]
        temp = math.atan2(y, x) / math.pi * 180
        angle = (temp + 360) % 360
        return angle

    def _check_unit_existence(self, unit_ID: str, units_dict: dict) -> Tuple[bool, Any]:
        """
        判定单元是否存活
        Args:
            unit_ID: 单元id
            units_dict: 所有单元字典
        Returns:
            unit_existence: 单元存活状态
            unit_info: 单元态势信息
        """
        unit_existence: bool = False
        unit_obs = None
        if unit_ID in units_dict:
            unit_obs = units_dict[unit_ID]
            unit_existence = True
        return unit_existence, unit_obs

    def check_terminated(self, obs) -> bool:
        """
        判定智能体状态是否结束;
        会先
        Args:
            obs: 己方态势信息
        Returns:
            terminated_status: 智能体是否结束
        """
        terminated_status: bool = True
        if self.agent_unit_id in obs[0]:
            # 如果存在则说明该智能体还活着，那么进一步判断是否还有弹药和剩余油量
            unit_info = obs[0][self.agent_unit_id]
            # 飞机已返航，或在机场正装载武器
            air_status = 0
            if "airops" in unit_info:
                if 'Con' in unit_info["airops"]:
                    air_status = unit_info["airops"]['Con']
                elif 'Condition' in unit_info["airops"]:
                    air_status = unit_info["airops"]['Condition']
            if air_status in (5, 6, 7, 10, 11):
                # 飞机已返航在装载武器，或返航中
                terminated_status = True  # 终止训练
            else:
                missiles_num = 0
                for loadout_name, loadout_info in unit_info["loadout"].items():
                    if not isinstance(loadout_info, dict):
                        continue
                    if "WeaponRecs" in loadout_info.keys():
                        for weapon_name, weapon_info in loadout_info["WeaponRecs"].items():
                            if not isinstance(weapon_info, dict):
                                continue
                            if "Name" not in weapon_info.keys():
                                continue
                            if "AirRangeMax" not in weapon_info:
                                continue
                            if weapon_info["AirRangeMax"] > 3:
                                # 对空武器射程大于3海里
                                missiles_num += weapon_info["CurrentLoad"]
                                if missiles_num > 0:
                                    break
                if missiles_num and unit_info["fuelstate"] <= self.aircraft_fuel:  # 若还有剩余弹药且油量充足，则状态为False
                    terminated_status = False
        return terminated_status
