# -*- coding:utf-8 -*-
# author: Jiang

"""
想定：尤卡坦冲突-接口调用示例想定
红方智能体
创建任务、分配单元到任务和设置任务，以及设置各级别条令智能体
备注：此智能体仅为态势处理和接口调用示例，而非有效红方交战策略。
"""

import logging
import random
from collections import Counter

from engine.LingYi.entitys.global_util import *
from engine.LingYi.entitys.agent_base import RuleAgentBase
from engine.LingYi.entitys import geo
from engine.LingYi.entitys.data import query_data as query
from engine.LingYi.entitys.gameenvs import utils as ul

logger = logging.getLogger("UnitAgent")
logger.setLevel(level=logging.INFO)
formatter_play = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                                   datefmt="%Y-%m-%d %H:%M:%S")
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(formatter_play)
logger.propagate = False
logger.addHandler(console)


class Agent(RuleAgentBase):
    def __init__(self, side_name, params):
        RuleAgentBase.__init__(self, side_name)
        logger.info('%s:%s play game' % (side_name, self.__class__.__name__))
        self.step_index = 0
        self.air_alt_set = False  # 某飞机已设置航路点速度和高度、条令
        self.base_info = {}  # key: air base id, value: position
        self.air_control_mission_name = "制空西南"
        self.air_control_mission = None
        self.missile_possibility = {}  # 空空弹：1和舰船的防空弹：2，和地空弹：4；打击敌方命中率统计  4:未知发射
        self.T50_max_height = None  # T50最大飞行高度
        self.T50_max_speed = None  # T50最大飞行速度
        self.T50_min_speed = None  # T50最小飞行速度
        self.weather_raining = None  # 想定天气降雨情况
        self.weather_fur = None  # 想定中天气的云量
        self.anti_sub_name = "反潜-苍龙"  # 反潜任务名
        self.anti_sub_mission = None  # 东部反潜任务
        self.last_sonar_time = -1  # 还上次投送声纳时间
        self.anti_sub_air_status = 0  # 反潜机状态
        self.last_sonar_shadow = True  # 上次投放是浅层
        self.set_car_hold = False  # 已设置车辆保持阵位
        self.no_nave_zone1 = "禁航区N1"  # 禁航区N1
        self.exclusion_zone1 = "封锁区N1"  # 封锁区N1
        self.car_max_height = 0  # 车辆最高位置

    def units_move(self):
        # 舰船、潜艇和空中飞机战前规划时间可以移动单元，限制原位置20千米以内
        sel_ship_name = "“南高”级护卫舰"
        if sel_ship_name in self.units_of_name:
            ship = self.units_of_name[sel_ship_name]
            lon, lat = ship.Lon, ship.Lat
            self.unit_new_position(ship.ID, (lon - 0.18, lat), heading=240)  # 战前规划时间，移动舰船单元位置
            ship.plotted_course([(lat, lon - 1), (lat - 0.5, lon - 1.5)])  # 舰船设置航路
            ship.set_throttle(Throttle.Full)  # 舰船设置油门 全速前进

    def aircraft_course_cargo(self, time_elapse=0):
        # 飞机设置航路和投送货物
        air_name = "伊尔-76MD型”耿直“B运输机A"
        if air_name in self.units_of_name:
            air = self.units_of_name[air_name]
            if time_elapse == 0:
                air.plotted_course([(16.9357, -92.2723), (17.1357, -91.4723), (17.55, -91.23)])
                air.set_throttle(Throttle.Full)
                air.set_alt_manual(True)  # 设置高度手动控制
                air.set_desired_height(3000)  # 中高3000米
            else:
                if 17.05 <= air.Lat < 17.25:
                    # 在此纬度区间，如果还没有投送货物，则投送货物
                    if air.Cargos:
                        # 飞机还有货物
                        air.unload_cargo()  # 卸货
                if not air.Cargos:
                    air_status = air.get_status_type()
                    if air_status != AirValidStatus.InAirRTB:
                        # 飞机非在返航中
                        air.return_to_base()  # 飞机返航

    def aircraft_air_set(self, situation, air_level=1):
        """
        飞机空中飞行高度设置
        :param situation: tuple: units, contacts
        :param air_level: int: 高度层序号
        :return:
        """
        T50_DBID = 2232  # T50飞机的DBID
        if self.T50_max_height is None:
            self.T50_max_height = query.get_aircraft_ceiling(T50_DBID)  # 查询T50飞机升限
            self.T50_max_speed = query.get_aircraft_max_speed(T50_DBID)  # 查询T50飞机最大飞行速度
            self.T50_min_speed = query.get_aircraft_min_speed(T50_DBID)  # 查询T50飞机最小飞行速度

        units, contacts = situation
        for unit_id, unit in units.items():
            # 遍历态势的我方单元
            if unit['category'] == 'Aircraft' and unit['dbid'] == T50_DBID:
                # 找到T-50飞机
                air = self.get_unit(unit_id)
                air_status = air.get_status_type()
                if air_status == AirValidStatus.InAir:
                    # 飞机在空
                    if air is not None:
                        if air_level == 1:
                            air.set_alt_level('LOW2000')  # 设置飞机高度层
                        elif air_level == 2:
                            air.set_alt_level('MEDIUM12000')
                        else:
                            air.set_alt_level('HIGH25000')

    def sensors_operation(self, situation):
        """
        传感器控制
        :param situation: tuple, 态势
        :return: 
        """
        select_units_guid = list(self.client_info['unit'].keys())  # 智能体开发平台或客户端选择的单元对象id列表
        # 飞机传感器控制
        for guid in select_units_guid:
            air = self.get_unit(guid)  # 飞机对象 Aircraft
            if air is not None and air.Type in (AircraftType.Fighter.value, AircraftType.Multirole.value,
                                                AircraftType.AEW.value, AircraftType.AirborneCP.value):
                # 是战斗机或预警机或指挥飞机
                air_status = air.get_status_type()
                if air_status == AirValidStatus.InAir:
                    # 飞机状态：在空中
                    air.EMCON_Obey_doctrine(False)
                    for sensor_id, sensor_info in air.Sensors.items():
                        if "Component" in sensor_info and "Type" in sensor_info["Component"]:
                            if sensor_info["Component"]["Type"] in (4001, 4002, 4003):
                                # 是激光传感器
                                if sensor_info["CanBeActive"] and not sensor_info["IsActive"]:
                                    # 可以开启激光传感器，但现在没有开启
                                    res = air.switch_laser(sensor_id, True)  # 开启激光传感器
                            elif sensor_info["Component"]["Type"] == 2001:
                                # 是雷达
                                if sensor_info["CanBeActive"] and not sensor_info["IsActive"]:
                                    # 可以开启雷达传感器，但现在没有开启
                                    if "MaxRange" in sensor_info:
                                        # 雷达最大探测范围
                                        if sensor_info["MaxRange"] > 100:
                                            # 探测范围大于100海里
                                            air.switch_radar(True)  # 开启雷达
                                        else:
                                            air.switch_radar(False)  # 关闭雷达
                            elif sensor_info["Component"]["Type"] == 3002:
                                # 是电子干扰传感器
                                if sensor_info["CanBeActive"] and not sensor_info["IsActive"]:
                                    # 可以开启电子干扰传感器，但现在没有开启
                                    air.switch_OECM(True)  # 开启电子干扰传感器

        for _, ship in self.ships.items():
            # 遍历我方舰船
            if ship.Category == 2002:
                # 水面战斗舰艇  global_util.py中查询：舰船类别-category
                ship.EMCON_Obey_doctrine(False)
                for sensor_id, sensor_info in ship.Sensors.items():
                    if "Component" in sensor_info and "Type" in sensor_info["Component"]:
                        if sensor_info["Component"]["Type"] == 3002:
                            # 是电子干扰传感器
                            if sensor_info["CanBeActive"] and not sensor_info["IsActive"]:
                                # 可以开启电子干扰传感器，但现在没有开启
                                ship.switch_OECM(True)  # 开启电子干扰传感器
                        elif 5001 <= sensor_info["Component"]["Type"] <= 5041:
                            # 是声呐传感器
                            if sensor_info["CanBeActive"] and not sensor_info["IsActive"]:
                                # 可以开启声呐传感器，但现在没有开启
                                ship.switch_sonar(True)  # 开启声呐传感器

    def submarine_operation(self):
        """
        单元（潜艇）设置航路，设置潜艇深度，纯方位打击
        :return:
        """
        # 潜艇操作
        sel_sub_name = "PLA-885M “亚森”级多用途核潜艇"
        if sel_sub_name in self.units_of_name:
            sub = self.units_of_name[sel_sub_name]  # 获取到潜艇单元对象
            lat, lon = sub.Lat, sub.Lon
            sub.plotted_course([(lat - 0.2, lon + 0.4), (lat - 0.5, lon + 0.7)])  # 设置航路
            sub.set_alt_manual(True)  # 设置高度手动控制
            sub.set_depth(40)  # 设置潜艇深度 水下40米

            # 纯方位打击
            for i in range(2, 5):
                sub.attack_BOL(19.5569 + 0.015 * i, -106.8472, 3264, 1173, 1)
                sub.attack_BOL(19.5569 + 0.015 * i, -106.8290, 3264, 1173, 1)
                sub.attack_BOL(19.5569 + 0.015 * i, -106.8070, 3264, 1173, 1)
                sub.attack_BOL(19.5569 + 0.015 * i, -106.7860, 3264, 1173, 1)

    def aircraft_fighter_rules(self, situation):
        """
        设置飞机条令规则, 武器使用规则
        :param situation: tuple: units, contacts
        :return:
        """
        fighter_airs_type = (AircraftType.Fighter.value, AircraftType.Multirole.value)
        for _, air in self.aircrafts.items():
            if air.Type in fighter_airs_type:
                # 战斗机或多用途战机
                air.doctrine_weapon_control_status_air(WeaponControlStatus.Hold)  # 对空目标限制开火

        # 设置飞机的武器使用规则
        air_name = "图-160M Blackjack 轰炸机 #1"
        if air_name in self.units_of_name:
            air = self.units_of_name[air_name]
            # AS-15C肯特导弹武器，自动开火距离
            air.wra_firing_range('935', WRA_WeaponTargetType.Air_Base_Single_Unit_Airfield, '1000')
            # AS-15C肯特导弹武器
            air.wra_qty_salvo('935', WRA_WeaponTargetType.Runway_Facility_Unspecified, 'Max')  # 
            air.set_weapon_release_authority('935', WRA_WeaponTargetType.Runway, quantity_salvo='Max',
                                             shooter_salvo='Max', firing_range='Max', self_defense='Max')
        air_name = "苏-34“鸭嘴兽”轰炸机 #1"
        if air_name in self.units_of_name:
            air = self.units_of_name[air_name]
            air.wra_qty_salvo('978', WRA_WeaponTargetType.Ship_Unspecified, 'Max')  # 
            air.wra_qty_salvo('978', WRA_WeaponTargetType.Ship_Carrier_25001_45000_tons, 'Max')  #

    def aircraft_weapons(self, situation):
        """
        飞机控制武器打击目标
        :param situation: tuple: units, contacts
        :return:
        """
        # 获取当前所有空中目标
        units, contacts = situation
        air_targets = {}  # key: target id, value: target position
        for target_id, contact in contacts.items():
            if "Side" in contact and contact["Side"] in ('中立国', '民用'):
                continue
            if 'Type' in contact and contact['Type'] == ContactType.Air.value:
                # 遍历所有已探明情报，获取空中情报目标
                if "Lat" not in contact or 'Lon' not in contact:
                    continue
                air_targets[target_id] = contact['Lat'], contact['Lon']  # 获取情报目标的位置

        if not air_targets:
            return

        # 查询飞机当前武器--找到最近射程内目标，打击
        fighter_airs_type = (AircraftType.Fighter.value, AircraftType.Multirole.value)
        have_set_attack = set()
        for _, air in self.aircrafts.items():
            air_status = air.get_status_type()
            if air_status != AirValidStatus.InAir:
                # 只考虑在空飞机
                continue
            if air.Type in fighter_airs_type:
                # 是战斗机或多用途战机
                air_weapons = {}  # key: weapon id, value:(count, range)
                max_range_weapon_id = 0
                crt_max_range = 0
                for k, v in air.Loadouts.items():
                    # 查找飞机挂载方案里的武器
                    if "WeaponRecs" in v:
                        for rid, weapon_info in v["WeaponRecs"].items():
                            if 'CurrentLoad' not in weapon_info or "AirRangeMax" not in weapon_info:
                                continue
                            count = weapon_info["CurrentLoad"]  # 挂载方案中武器数量
                            if count > 0:
                                weapon_id = weapon_info["WeaponDBID"]  # 武器DBID
                                air_range = weapon_info["AirRangeMax"]  # 对空武器射程
                                # SurfaceRangeMax:最海武器射程 LandRangeMax:对陆武器射程 SubsurfaceRangeMax:对潜武器射程
                                if air_range > 5:
                                    # 射程大于5海里
                                    if weapon_id not in air_weapons:
                                        air_weapons[weapon_id] = count, air_range
                                    else:
                                        crt_count, air_range = air_weapons[weapon_id]
                                        air_weapons[weapon_id] = crt_count + count, air_range
                                    if air_range > crt_max_range:
                                        # 选择射程远的武器
                                        crt_max_range = air_range
                                        max_range_weapon_id = weapon_id
                if air_weapons:
                    air.attack_drop_target_all()  # 放弃之前分配的目标
                    # 挂载了有效对空武器
                    count, air_range = air_weapons[max_range_weapon_id]  # 最大射程的武器数量和射程
                    air_position = air.Lat, air.Lon
                    for target_id, target_pos in air_targets.items():
                        if target_id in have_set_attack:
                            continue
                        distance = geo.get_horizontal_distance(air_position, target_pos) * geo.NM2KM  # 飞机到目标的距离，海里
                        if distance < air_range * 3 / 5:
                            # 到目标距离小于武器射程的3/5
                            attack_count = min(2, count)  # 打击目标数量
                            air.attack_weapon_allocate_to_target(target_id, max_range_weapon_id, attack_count)  # 打击目标
                            have_set_attack.add(target_id)  # 已分配武器打击目标

    def set_aircraft_loadout(self, situation):
        """
        战前规划时间，飞机可以设置挂载---------比赛中可实现
        :param situation: tuple, 态势
        :return: 
        """
        units = situation[0]
        for guid, unit in units.items():
            dbid = unit["dbid"]
            if dbid == 2346:
                # 米格-29型“支点A”战斗机
                self.set_aircraft_loadout_plan_time(guid, 5299)  # 战前规划时间飞机设置空空战挂载
                """
                此时飞机设置挂载方案，无需停靠的机场或航母弹药库中有相应挂载方案相应武器。且准备时间为0.
                挂载方案ID（11012）在客户端实体相关信息查看界面中-飞机挂载表格中，最后一列DBID#查看。
                """
            elif dbid == 2622:
                # 安-26运输机
                air = self.get_unit(guid)
                if air is not None:
                    air_status = air.get_status_type()
                    if air_status == AirValidStatus.WaitReady:
                        # 飞机没有挂载好
                        """
                        飞机设置挂载方案，如果挂载方案有武器，则需停靠的机场或航母弹药库中有相应挂载方案相应武器。
                        此时飞机需数小时准备时间。
                        挂载方案ID（11012）在客户端实体相关信息查看界面中-飞机挂载表格中，最后一列DBID#查看。
                        """
                        air.set_loadout(11012)

    def get_unit_valid_weapons(self, unit, target=ContactType.Air):
        """
        获取单元（舰船、飞机等）对相应目标可用武器信息，数量+射程
        :param unit: 飞机、舰船等单元对象
        :param target: ContactType类型, 目标类型
        :return: 
        """
        valid_weapon = {}  # key: weapon id  value: {'c': count, 'r': range}
        if unit.UnitType == 1:
            for _, mount in unit.Loadouts.items():
                # 查找单元挂架里的武器
                if "WeaponRecs" not in mount:
                    continue
                for rid, weapon_info in mount["WeaponRecs"].items():
                    if 'CurrentLoad' not in weapon_info or "TimeToFire" not in weapon_info or \
                            "WeaponDBID" not in weapon_info:
                        continue
                    count = weapon_info["CurrentLoad"]  # 挂载方案中武器数量
                    if count > 0 and weapon_info["TimeToFire"] < 1:
                        weapon_id = weapon_info["WeaponDBID"]  # 武器DBID
                        weapon_range = None
                        if target == ContactType.Air or target == ContactType.Missile:
                            weapon_range = weapon_info["AirRangeMax"]  # 对空中单元武器射程
                        elif target == ContactType.Surface:
                            weapon_range = weapon_info["SurfaceRangeMax"]  # 对水面单元武器射程
                        elif target == ContactType.Facility_Fixed or target == ContactType.Facility_Mobile:
                            weapon_range = weapon_info["LandRangeMax"]  # 对地面单元武器射程
                        elif target == ContactType.Submarine:
                            weapon_range = weapon_info["SubsurfaceRangeMax"]  # 对水下单元武器射程
                        if weapon_range is not None and weapon_range > 0.1:
                            if weapon_id not in valid_weapon:
                                valid_weapon[weapon_id] = {'c': count, 'r': weapon_range}
                            else:
                                valid_weapon[weapon_id]['c'] = valid_weapon[weapon_id]['c'] + count

        for _, mount in unit.Mounts.items():
            # 查找单元挂架里的武器
            if "WeaponRecs" not in mount:
                continue
            if "Component" in mount and "Status" in mount["Component"]:
                if 0 != mount["Component"]["Status"]:
                    # 部件状态：0：正常  1：损坏  2：摧毁
                    continue
                if "DBID" in mount["Component"]:
                    mount_dbid = mount["Component"]["DBID"]  # 挂架的id，手动打击目标指定挂架和武器
            else:
                continue
            for rid, weapon_info in mount["WeaponRecs"].items():
                if 'CurrentLoad' not in weapon_info or "TimeToFire" not in weapon_info or \
                        "WeaponDBID" not in weapon_info:
                    continue
                count = weapon_info["CurrentLoad"]  # 挂载方案中武器数量
                if count > 0 and weapon_info["TimeToFire"] < 1:
                    weapon_id = weapon_info["WeaponDBID"]  # 武器DBID
                    weapon_range = None
                    if target == ContactType.Air or target == ContactType.Missile:
                        if "AirRangeMax" in weapon_info:
                            weapon_range = weapon_info["AirRangeMax"]  # 对空中单元武器射程
                    elif target == ContactType.Surface:
                        if "SurfaceRangeMax" in weapon_info:
                            weapon_range = weapon_info["SurfaceRangeMax"]  # 对水面单元武器射程
                    elif target == ContactType.Facility_Fixed or target == ContactType.Facility_Mobile:
                        if "LandRangeMax" in weapon_info:
                            weapon_range = weapon_info["LandRangeMax"]  # 对地面单元武器射程
                    elif target == ContactType.Submarine:
                        if "SubsurfaceRangeMax" in weapon_info:
                            weapon_range = weapon_info["SubsurfaceRangeMax"]  # 对水下单元武器射程
                    if weapon_range is not None and weapon_range > 1.2:
                        if weapon_id not in valid_weapon:
                            valid_weapon[weapon_id] = {'c': count, 'r': weapon_range}
                        else:
                            valid_weapon[weapon_id]['c'] = valid_weapon[weapon_id]['c'] + count
        return valid_weapon

    def attack_ship_targets(self, situation):
        """
        查询舰船当前武器，打击目标
        :param situation: tuple, 态势
        :return: 
        """
        units, contacts = situation

        target_ship_info = {}  # key: target id value: target point
        for target_id, contact in contacts.items():
            if "Side" in contact and contact["Side"] in ('中立国', '民用'):
                continue
            if 'Type' in contact and contact['Type'] == ContactType.Surface.value:
                # 遍历所有已探明情报，获取水面舰船情报目标
                if "Lat" not in contact or 'Lon' not in contact:
                    continue
                target_ship_info[target_id] = contact['Lat'], contact['Lon']

        for weapon_id, weapon in self.weapons.items():
            if weapon.AI and "PrimaryTarget" in weapon.AI and weapon.AI["PrimaryTarget"]:
                if "PrimaryTarget_Type" not in weapon.AI:
                    continue
                if weapon.AI["PrimaryTarget_Type"] == ContactType.Surface:
                    target_id = weapon.AI["PrimaryTarget"]  # 目标已在被发射的武器攻击中
                    if target_id in target_ship_info:
                        del target_ship_info[target_id]

        if not target_ship_info:
            # 没有需要打击的水面目标，返回
            return

        for unit_id, unit_info in units.items():
            if unit_info["category"] == "Ship" or unit_info["category"] == "Submarine" \
                    or unit_info["category"] == "Facility":
                unit = self.get_unit(unit_info['guid'])
                if unit is not None and unit.Weaponry and "WeaponAssignments" in unit.Weaponry and \
                        unit.Weaponry["WeaponAssignments"]:
                    if "Target" in unit.Weaponry["WeaponAssignments"] and unit.Weaponry["WeaponAssignments"]["Target"]:
                        target_id = unit.Weaponry["WeaponAssignments"]["Target"]
                        if target_id in target_ship_info:
                            del target_ship_info[target_id]

        if not target_ship_info:
            # 没有需要打击的水面目标，返回
            return

        max_assign_count = 2
        for unit_id, unit in units.items():
            if unit["category"] == "Ship" or unit["category"] == "Submarine" or unit["category"] == "Facility":
                # 单元是舰船或潜艇，或地面设施
                attack_unit = self.get_unit(unit['guid'])  # 单元对象

                unit_position = unit['lat'], unit['lon']
                valid_weapons = self.get_unit_valid_weapons(attack_unit, ContactType.Surface)
                if valid_weapons:
                    for target_id, position in target_ship_info.items():
                        # 遍历所有目标
                        distance = geo.get_horizontal_distance(unit_position, position) / geo.NM2KM
                        for weapon_id, info in valid_weapons.items():
                            # 遍历本单元所有可打击敌船的武器
                            if info['r'] > distance:
                                # 武器射程大于到目标的距离
                                count = info['c']
                                assign_count = min(max_assign_count, count)
                                # 单元分配武器打击目标
                                if "南高" in attack_unit.Name and weapon_id == 18:
                                    # 南高护卫舰，使用武器:SS-N-22M型“日炙”反舰导弹
                                    missile_course = []
                                    for i in range(1, 3):
                                        point_lat = unit_position[0] + i * (position[0] - unit_position[0]) / 3
                                        point_lon = unit_position[1] + i * (position[1] - unit_position[1]) / 3
                                        if i == 2:
                                            point_lon += random.uniform(-0.5, 0.5)  # 最后一个点经度随机变化
                                        missile_course.append((point_lat, point_lon))
                                    attack_unit.attack_weapon_allocate_to_target(target_id, weapon_id, assign_count, 
                                                                                 weapon_course=missile_course)
                                else:
                                    attack_unit.attack_weapon_allocate_to_target(target_id, weapon_id,
                                                                                 assign_count)

    def contacts_operation(self, situation):
        """
        情报操作：标识敌方，重命名，获取位置，探测级别等
        :param situation: tuple, 态势
        :return: 
        """
        for target_id, contact in situation[1].items():
            if "Name" in contact and "水面#5" in contact["Name"]:
                res = self.contact_mark_posture(target_id, ContactStance.Hostile)  # 标识情报目标为敌方
                res2 = self.contact_rename(target_id, "世宗大王船")  # 重命名情报目标
            else:
                if "IDStatus" in contact and "Lon" in contact and 'Lat' in contact:
                    # 2: 知道飞机舰船导弹等类型；3：知道战斗机、驱逐舰等详细类型; 4:识别出具体单元型号，例如J-10C, 等
                    detect_status = contact["IDStatus"]
                    if detect_status == 4:
                        # 理论上识别类型到4，才能知道具体型号DBID
                        if "DBID" in contact:
                            unit_dbid = contact["DBID"]
                    elif detect_status == 3:
                        # 探测级别为3
                        detect_units = []  # 探测识别到目标的我方单元
                        if "Type" in contact and contact["Type"] == 2 and "Record" in contact:
                            # 目标是水面舰船
                            for record in contact["Record"]:
                                # 遍历探测记录
                                if "UnitID" in record:
                                    detect_units.append(record["UnitID"])
                        target_position = contact['Lat'], contact['Lon']
                        for unit_id in detect_units:
                            unit = self.get_unit(unit_id)
                            if unit is not None and unit.UnitType == 2:
                                # 探测的单元还有被摧毁，且是舰船
                                azimuth = geo.get_azimuth((unit.Lat, unit.Lon), target_position)  # 计算本单元到目标朝向
                                # 计算本单元朝向目标的10千米远的点
                                point = geo.get_geopoint_from_distance((unit.Lat, unit.Lon), azimuth, 10000)
                                # 船往目标方向走
                                unit.plotted_course([point])

    def _get_current_air_fuel(self, aircraft):
        """
        获取飞机当前油量
        :param aircraft: Aircraft对象: 飞机对象
        :return: 飞机当前燃油，公斤
        """
        # 飞机燃油
        current_fuel = 0.0
        if aircraft.FuelRecs:
            for fuel_id, info in aircraft.FuelRecs.items():
                if "CQ" in info:
                    current_fuel += info["CQ"]
        # 考虑副油箱
        if aircraft.Loadouts:
            for load_id, info in aircraft.Loadouts.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 aircraft_fuel_control(self):
        """
        遍历飞机，获取飞机飞行状态，获取飞机主基地，飞机设置新基地，预估飞机当前可飞距离，飞机加油
        :param aircraft: Aircraft对象: 飞机对象
        :return: 飞机当前燃油，公斤
        """
        tanker_info = {}
        for guid, air in self.aircrafts.items():
            if air.Type == AircraftType.Tanker.value:
                # 是空中加油机
                air_status = air.get_status_type()
                if air_status == AirValidStatus.InAir:
                    # 加油机在空中
                    tanker_info[guid] = air.Lat, air.Lon

        if not self.base_info:
            for guid, unit in self.facilitys.items():
                if unit.Category == 9001:
                    # global_util.py中查看地面装备-category:
                    self.base_info[guid] = unit.Lat, unit.Lon

        # 获取飞机油量-油量不足则返航或者空中加油
        for guid, air in self.aircrafts.items():
            if air.DBID == 2232:
                # 飞机DBID是2232，即T-50型飞机
                air_status = air.get_status_type()
                if air_status == AirValidStatus.InAir:
                    # 只考虑在空飞机
                    crt_position = air.Lat, air.Lon
                    nearest_id = ""
                    nearest_distance = 9999
                    for base_id, position in self.base_info.items():
                        distance = geo.get_horizontal_distance(position, crt_position)  # 获取地球上两点距离
                        if distance < nearest_distance:
                            nearest_distance = distance
                            nearest_id = base_id
                    if nearest_id:
                        # 获取当前基地
                        if air.AirOps and "AHU" in air.AirOps and air.AirOps["AHU"] != nearest_id:
                            # 飞机当前基地不是最近的基地
                            air.select_new_base(nearest_id)  # 飞机设置新基地
                        current_fuel = self._get_current_air_fuel(air)  # 飞机当前油量
                        if air.FuelRate > 0:
                            # 计算当前飞机还可飞行距离  千米
                            max_fly_distance = air.CurrentSpeed * current_fuel / (air.FuelRate * 3600) / geo.NM2KM
                            if max_fly_distance < nearest_distance * 1.5:
                                # 最大可飞行距离小于最近到机场距离的1.5倍，找最近加油机
                                nearest_tanker_id = ""
                                nearest_tanker_distance = 9999
                                for tanker_id, position in tanker_info.items():
                                    distance = geo.get_horizontal_distance(position, crt_position)
                                    if distance < nearest_tanker_distance:
                                        nearest_tanker_distance = distance
                                        nearest_tanker_id = tanker_id
                                if nearest_tanker_id and nearest_tanker_distance < 300:
                                    air.refuel(nearest_tanker_id)  # 飞机加油

    def unit_commDevices(self):
        # 遍历单元的通信设备
        for guid, air in self.aircrafts.items():
            for _, comm in air.CommDevices.items():
                if "IsActive" in comm and comm["IsActive"]:
                    # 是开启的：
                    if "Component" in comm:
                        comm_dbid = comm["Component"]["DBID"]  # 通信设备数据库ID，DBID
                        comm_type = comm["Component"]["Type"]  # 通信设备类型Type
                        status = comm["Component"]["Status"]
                        if 0 == status:
                            # 通信设备正常
                            pass  # 可对通信设备的类型和ID进行和别的单元进行通信判断等

    def unit_magazine_weapons(self):
        # 遍历舰船、地面装备或潜艇的弹药库
        weapon_count = 0
        query_weapon_id = 3442  # 查找我方单元弹药库中KN-06型地空导弹数量
        for _, facility in self.facilitys.items():
            if "Weapons" in facility.Magazines:
                for wid, weapon_info in facility.Magazines["Weapons"].items():
                    if weapon_info["WeaponDBID"] == query_weapon_id:
                        weapon_count += weapon_info["CurrentLoad"]
        for _, ship in self.ships.items():
            if "Weapons" in ship.Magazines:
                for wid, weapon_info in ship.Magazines["Weapons"].items():
                    if weapon_info["WeaponDBID"] == query_weapon_id:
                        weapon_count += weapon_info["CurrentLoad"]
        return weapon_count

    def air_fight_control(self, time_elapse):
        """
        创建巡逻任务-防空作战巡逻
        获取单元已分配的任务，获取飞机飞行状态，飞机编队起飞, 编队中移除某单元
        获取飞机油门，设置油门
        :param time_elapse: int: 推演时间，单位：秒
        :return:
        """
        patrol_area = [(13.2, -93.1), (13.2, -91.9), (11.8, -91.9), (11.8, -93.1)]
        self.air_control_mission = self.get_mission_by_name(self.air_control_mission_name)  # 获取任务对象
        if self.air_control_mission is None:
            # 还没有创建巡逻任务, 创建“巡逻任务”-“防空作战巡逻”
            self.air_control_mission = self.create_patrol_mission(self.air_control_mission_name, MissionPatrolType.AIR,
                                                                  patrol_area)
            self.air_control_mission.set_one_third_rule(False)  # 设置巡逻任务不执行三分之一规则，不然不会全部飞机第一时间起飞
            # 设置任务不对巡逻区外的探测目标进行分析，不然会出巡逻区打击巡逻区外空中目标
            self.air_control_mission.patrol_checkOPA(False)

        air_ids = []  # 飞机ID列表
        air_names = ["苏-30SM“侧卫G”战斗机 #%d" % i for i in range(1, 5)]
        air_is_reading = False  # 飞机飞行状态是否在机场已准备好
        air_group = None
        for air_name in air_names:
            if air_name in self.units_of_name:
                air = self.units_of_name[air_name]
                if air.AssignedMission != self.air_control_mission.ID:
                    # 获取单元的任务，不是需要的巡逻任务
                    self.air_control_mission.assign_unit(air.ID)  # 分配单元到任务
                air_ids.append(air.ID)
                air_status = air.get_status()  # 获取飞机飞行状态
                if air_status == AirOpsCondition.Airborne.value:
                    # 飞机在空中
                    air_throttle = air.Throttle  # 飞机油门 1：慢速 2：巡航 3：全速 4：加力
                    if air_throttle != 2:
                        # 飞机油门不是巡航
                        air.set_throttle(Throttle.Cruise)
                if not air_is_reading:
                    if air_status == AirOpsCondition.Readying.value:
                        air_is_reading = True
                if air_group is None and air.GroupID:
                    # 获取飞机编队
                    air_group = self.get_unit(air.GroupID)

        if air_is_reading:
            # 飞机状态是在机场准备好挂载
            self.air_group_out(air_ids)  # 飞机编队起飞
        elif 300 < time_elapse < 480:
            if air_group is not None:
                air_ids = air_group.Members  # 获取编队中单元id
                if len(air_ids) >= 4:
                    air_id = air_ids[-1]
                    self.group_remove_unit(air_id)  # 飞机编队移除单元
        elif 480 < time_elapse < 900:
            if air_group is not None:
                air_ids = air_group.Members  # 获取编队中单元id
                if len(air_ids) >= 3:
                    air_id = air_ids[-1]
                    self.group_remove_unit(air_id)  # 飞机编队移除单元

    def air_course_control(self, time_elapse):
        """
        飞机编队，取消编队
        飞机设置飞行航路，遍历传感器，获取一个雷达
        航路点设置速度和高度，航路点设置条令和传感器
        :param time_elapse: int: 推演时间，单位：秒
        :return:
        """
        if self.air_alt_set:
            # 已设置航路，以及航路点速度、高度和条令等
            return
        air_name = "米格-31BM型“猎狐”战斗机#1"
        air_name2 = "米格-31BM型“猎狐”战斗机#2"
        if air_name in self.units_of_name:
            air = self.units_of_name[air_name]

            group_set_course = False  # 编队已设置航路
            if time_elapse <= 120 and not air.GroupID:
                # 推演2分钟前，且飞机还没有编队
                if air_name2 in self.units_of_name:
                    air2 = self.units_of_name[air_name2]
                    # 将两架飞机编队
                    self.group_add([air.ID, air2.ID], group_name="米格31编队")
            elif air.GroupID:
                # 飞机有编队
                group = self.get_unit(air.GroupID)
                if group is not None:
                    if group.GroupLead != air.ID:
                        # 获取飞机编队的领队
                        group.set_group_leader(air.ID)  # 设置飞机编队的领队为飞机1
                    elif 180 < time_elapse < 300:
                        # 推演开始3分钟之后，5分钟之前
                        if air_name2 in self.units_of_name:
                            air2 = self.units_of_name[air_name2]
                            self.group_detach([air.ID, air2.ID])  # 解散编队

                    if "PC" in group.Navigator and group.Navigator["PC"]:
                        group_set_course = True

            if group_set_course or ("PC" in air.Navigator and air.Navigator["PC"]):
                # 获取雷达ID
                radar_id = ""
                for sensor_id, sensor_info in air.Sensors.items():
                    if "Component" in sensor_info and "Type" in sensor_info["Component"]:
                        if sensor_info["Component"]["Type"] == 2001:
                            # 是雷达
                            if sensor_info["CanBeActive"]:
                                radar_id = sensor_id
                                break
                # 飞机已设置航路点
                for point in air.Navigator["PC"]:
                    if "GeoPoint" in point:
                        point_id = point["GeoPoint"]["ID"]
                        point_name = point["GeoPoint"]["Name"]
                        point_lat = point["GeoPoint"]["Lat"]
                        if point_lat < 12.6:
                            air.set_weapoint(point_id, 500, 8000)  # 设置单元航路点速度和高度
                            if point_name:
                                air.set_waypoint_sensor(point_name, sensor_id=radar_id, radar=False)  # 设置单元航路点雷达关闭
                            waypoint = air.get_way_point(point_id, point_name)  # 获取航路点对象  
                            waypoint.doctrine_weapon_control_status_surface(WeaponControlStatus.Free)  # 设置航路点条令
                        elif point_lat < 12.95:
                            air.set_weapoint(point_id, 600, 3000)  # 设置单元航路点速度和高度
                            if point_name:
                                air.set_waypoint_sensor(point_name, sensor_id=radar_id, radar=True)  # 设置单元航路点雷达打开
                            waypoint = air.get_way_point(point_id, point_name)  # 获取航路点对象  
                            waypoint.doctrine_weapon_control_status_subsurface(WeaponControlStatus.Hold)  # 设置航路点条令
                        self.air_alt_set = True
            else:
                # 还没有设置航路点
                air.plotted_course([(12.4, -88.5, 'A1'), (12.9, -89.6, 'A2'), (14.5, -90.6, 'A3')])  # 设置飞机航路

    def get_ship_damage(self):
        """
        获取舰船毁伤情况
        Fire: 0:无  1:小  2：较大  3：严重  4：大火
        Flood：0:无  1:小  2：较大  3：严重  4：倾覆
        :return:
        """
        for _, ship in self.ships.items():
            if ship.Damage:
                if ("Fire" in ship.Damage and ship.Damage["Fire"] >= 2) or \
                        ("Fire" in ship.Damage and ship.Damage["Flood"] >= 2):
                    # 船上损毁较大的火或水之后，关闭雷达，关闭声纳，降低航速
                    ship.EMCON_Obey_doctrine(False)
                    ship.switch_radar(False)
                    ship.switch_sonar(False)
                    ship.set_throttle(Throttle.Loiter)

    def missile_assessment(self, time_elapse):
        """
        接收武器发射事件，评估摧毁概率
        :param time_elapse: int: 推演时间
        :return:
        """
        unit_destroyed_list = self.KeyMsg.get(ul.UNIT_DESTROYED_TYPE, [])  # 获取单元（包含武器）摧毁事件列表

        weapon_fired = self.KeyMsg.get(ul.WEAPON_FIRED_TYPE, [])  # 获取武器发射事件
        if weapon_fired:
            for fire_info in weapon_fired:
                if fire_info["WeaponType"] == 2001:
                    # 是发射的导弹
                    fire_unit_type = fire_info["FiringUnitType"]  # 发射单元类型：1：飞机，2：舰船，3:潜艇，4：地面装备
                    if fire_unit_type not in self.missile_possibility:
                        self.missile_possibility[fire_unit_type] = {}
                    weapon_id = fire_info["WeaponID"]  # 发射的武器的guid
                    self.missile_possibility[fire_unit_type][weapon_id] = 0  # 已发射还在空中，没有结果
            self.KeyMsg[ul.WEAPON_FIRED_TYPE] = []  # 处理完毕，清空

        weapon_destroy = self.KeyMsg.get(ul.WEAPON_DESTROYED_TYPE, [])  # 获取武器销毁事件
        if weapon_destroy:
            for destroy in weapon_destroy:
                if destroy["WeaponType"] == 2001:
                    # 是销毁的导弹
                    fire_unit_type = destroy["FiringUnitType"]  # 发射单元类型：1：飞机，2：舰船，3:潜艇，4：地面装备
                    if fire_unit_type not in self.missile_possibility:
                        self.missile_possibility[fire_unit_type] = {}
                    weapon_id = destroy["WeaponID"]  # 发射的武器的guid
                    self.missile_possibility[fire_unit_type][weapon_id] = destroy["Reason"]  # 武器销毁结果
                    # 结果：1：被浪费（干扰等），2：被拦截，3：命中，4：未命中
            self.KeyMsg[ul.WEAPON_DESTROYED_TYPE] = []  # 处理完毕，清空
        for i in range(1, 10):
            if i*1000-40 < time_elapse < i*1000+40:
                # 每1000秒统计一次
                type_des = {
                    1: "飞机",
                    2: "舰船",
                    3: "潜艇",
                    4: "地面装备"
                }
                missile_ok = {}
                for unit_type, info in self.missile_possibility.items():
                    all_count = len(info)
                    value_counts_dict = dict(Counter(info.values()))
                    if unit_type in type_des:
                        des = type_des[unit_type]
                        if 3 in value_counts_dict:
                            missile_ok[des] = "发射导弹数:%d，命中率:%.1lf%%" % (all_count, value_counts_dict[3] * 100 / all_count)
                        else:
                            missile_ok[des] = "发射导弹数:%d，命中率:0%%" % all_count
                # print("missile strike target rate:{}".format(missile_ok))  # 输出命中率

    def score_assessment(self, time_elapse):
        """
        获取当前推演方得分
        :param time_elapse: int: 推演时间
        :return:
        """
        for i in range(1, 10):
            if i*1000-40 < time_elapse < i*1000+40:
                pass
                # print("current %s time:%d score:%d" % (self.Side, time_elapse, self.TotalScore))  # 打印当前推演方得分

    def missile_car_move(self, time_elapse):  # 导弹车移动位置
        """
        获取当前导弹车移动
        :param time_elapse: int: 推演时间
        :return:
        """
        car_name = "地地导弹营(“舞水端-1”型中程弹道导弹)"
        if car_name in self.units_of_name:
            car = self.units_of_name[car_name]  # 获取到潜艇单元对象
            if time_elapse < 10:
                car.plotted_course([(car.Lat+0.5, car.Lon-0.8)])
            elif 10 < time_elapse < 1200:
                ele = self.get_elevation((car.Lat, car.Lon))
                if ele > self.car_max_height:
                    self.car_max_height = ele
            elif time_elapse > 1200:
                ele = self.get_elevation((car.Lat, car.Lon))
                if ele > self.car_max_height:
                    # 到高点
                    if not self.set_car_hold:
                        car.hold_positon(True)  # 地面单元保持阵位
                        self.set_car_hold = True
                        self.agent_message_text("单元-%s已停止移动" % car_name)  # 智能体属性消息到客户端智能体消息栏

    def zone_operation(self, time_elapse):
        """
        禁航区或封锁区设置
        :param time_elapse: int: 推演时间
        :return:
        """
        no_navi1 = False  # 还没有创建禁航区N1
        for _, navi in self.NoNavZones.items():
            if "Description" in navi:
                if navi["Description"] == self.no_nave_zone1:
                    no_navi1 = True
        if not no_navi1:
            zone = [(19.6, -94.2), (18.5, -94.0), (17.1, -94.6), (17.1, -93.6), (18.4, -93.7), (19.4, -93.3)]
            self.zone_add_no_navigate(self.no_nave_zone1, zone, affects=[1])  # 创建禁航区

        no_exclusion1 = False  # 还没有创建禁航区N1
        for _, navi in self.ExclusionZones.items():
            if "Description" in navi:
                if navi["Description"] == self.exclusion_zone1:
                    no_exclusion1 = True
        if not no_exclusion1:
            zone = [(14.1, -109.2), (17.5, -108.0), (21.1, -105.6), (19.2, -104.6), (13.4, -107.7)]
            self.zone_add_exclusion(self.exclusion_zone1, zone, affects=[1, 2, 3])  # 创建封锁区

    def initial(self, situation):
        """
        初始化函数，每局推演开始前被调用. 可用于任务创建、设置，条令设置，变量初始化等
        :param situation: tuple: units, contacts
        :return:
        """
        self.zone_operation(0)  # 禁航区或封锁区设置
        self.units_move()  # 移动单元位置 
        self.aircraft_course_cargo()  # 飞机设置航路和投送货物
        self.aircraft_fighter_rules(situation)  # 设置飞机条令
        self.set_aircraft_loadout(situation)  # 战前规划时间，飞机可以设置挂载---------比赛中可实现
        self.sensors_operation(situation)  # 传感器操作 
        self.unit_commDevices()  # 单元通信设备
        self.aircraft_weapons(situation)  # 查询飞机当前武器，打击目标
        self.attack_ship_targets(situation)  # 查询舰船当前武器，打击目标
        self.contacts_operation(situation)  # 情报目标操作
        self.submarine_operation()  # 潜艇操作
        self.missile_car_move(0)  # 导弹车移动位置

    def step(self, time_elapse, situation):
        """
        # 每步决策函数
        :param time_elapse:  int, time elapse
        :param situation: tuple: units-list, contacts-list
        :return:
        """
        self.step_index += 1
        self.score_assessment(time_elapse)  # 得分获取
        self.missile_assessment(time_elapse)  # 评估武器发射和命中
        self.aircraft_course_cargo(time_elapse)  # 飞机设置航路和投送货物
        self.aircraft_air_set(situation, self.step_index % 3)
        self.sensors_operation(situation)  # 传感器操作 
        self.aircraft_weapons(situation)  # 查询飞机当前武器  打击目标
        self.attack_ship_targets(situation)  # 查询舰船当前武器，打击目标
        self.air_course_control(time_elapse)  # 飞机编队，航路控制
        self.air_fight_control(time_elapse)  # 飞机获取任务、油门和编队起飞，获取编队，编队中移除一个单元
        self.aircraft_fuel_control()  # 获取飞机油量-油量不足则返航或者空中加油
        self.contacts_operation(situation)  # 情报
        self.missile_car_move(time_elapse)  # 导弹车移动位置

    def deduction_end(self):
        # agent结束后自动被调用函数， 可保存模型，比如记录本局策略后的得分等
        logging.info('%s:%s play game result:%d' % (self.Name, self.__class__.__name__, self.TotalScore))

    def is_done(self):
        # agent主动结束本局推演，比如一定胜利或失败后
        # 只能训练时调用此函数，比赛对战时此函数不可用
        return False
