вторник, 20 декабря 2016 г.

Освоение новых возможностей. Работа с blf.

В первой версии была у меня система текстовых меток и подсказок. И хотя результат меня худо-бедно устраивал, наличие большого количества дополнительных объектов и второй оверлейной сцены меня несколько напрягало.Но тогда у меня было стремление любой ценой довести проект хотя бы до промежуточного результат и я не стал искать других путей. А они были, между прочим...
Есть в составе инструментов Блендера весьма полезная функция blf, правда, чтобы ей пользоваться, никак не обойтись без дао обезъяны, а именно - для начала иметь хоть какой-то исходный код, который можно сплагиатить, извратить переработать. Такой код с выводом текст а"Хелло Ворлд" в АПИ Блендера имелся.
Однако тут воспоследовали проблемы. Сам текст, понятно, как сформировать, но надо его запихнуть в нужное место экрана, а потом еще и дать ему нужный цвет для удобоваримого чтения. После трехдневной возни с подсказками dron-a, на свет божий явился промежуточный результат. Скрин приводится ниже.



На ИЛС присутствует надпись F-16_цифры. Синего цвета. Это говорит о том, что радар взял на сопровождение F-16C "синих", плюс до него указывается дистанция. В перспективе охота получить возможность маркировать все цели в округе, как свои, так и чужие, плюс иметь возможность отключать или включать это маркирование. Пока вариант весьма сырой, хотя и работающий, поэтому приведу код. Часть первая - скрипт ClassHud с функциями чтения и отсчета экранных координат цели с возможностью вывода положения метки в пределах ИЛС.

import bge

#МЕТКИ ЦЕЛИ НА КОЛЛИМАТОРЕ - ОБЕСПЕЧИВАЕТСЯ ЕЕ ПЛАВНОЕ ПЕРЕМЕЩЕНИЕ ВСЛЕД ЗА ЦЕЛЬЮ  
#Чтение экранных координат    
def readCoord():
    cont = bge.logic.getCurrentController()
    scene = bge.logic.getCurrentScene()
    own = cont.owner
    camera = bge.logic.getCurrentScene().active_camera
   
    targetMarka = scene.objects['targetMetka']
    #collimatorRight = scene.objects['collRight']
    #collimatorLeft = scene.objects['collLeft']
    targetLabel = scene.objects['targetMarka']
   
    camera = scene.active_camera
   
    #Метка цели работает только при включенном радаре и выставленной дальности
    try:
       
        targetMarka.worldPosition[0] = bge.logic.globalDict['targetCoord'][0] * 20.0
        targetMarka.worldPosition[1] = -bge.logic.globalDict['targetCoord'][1] * 11.3
       
       
        plusX = targetLabel['limX'] #collimatorRight.worldPosition[0]
        minusX = -targetLabel['limX'] #collimatorLeft.worldPosition[0]
        plusY = targetLabel['limY'] #collimatorRight.worldPosition[2]
        minusY = -targetLabel['limY'] #collimatorLeft.worldPosition[2]
       
        targetLabel.worldPosition[0] = targetMarka.worldPosition[0] - 10.0
        targetLabel.worldPosition[2] = targetMarka.worldPosition[1] + 5.65
       
        if targetLabel.worldPosition[0] < minusX:
            targetLabel.worldPosition[0] = minusX
        elif targetLabel.worldPosition[0] > plusX:
            targetLabel.worldPosition[0] = plusX
        if targetLabel.worldPosition[2] < minusY:
            targetLabel.worldPosition[2] = minusY
        elif targetLabel.worldPosition[2] > plusY:
            targetLabel.worldPosition[2] = plusY
    
    except:
        targetLabel.worldPosition = [0.0, 0.0, 0.0]
        
    import RenderText
    RenderText.init()
   
       
#Передача экранных координат   
def writeCoord():
    cont = bge.logic.getCurrentController()
    scene = bge.logic.getCurrentScene()
    own = cont.owner

    try:
        target = scene.objects.from_id(int(own['idTarget']))
       
        camera = scene.active_camera

        #if camera.getVectTo(target)[2][2] > 0:
        bge.logic.globalDict['targetCoord'] = camera.getScreenPosition(target)
        #print(target['unitName'], bge.logic.globalDict['targetCoord'])
       
      
        winXcoord = bge.render.getWindowWidth(target)*camera.getScreenPosition(target)[0]
        winYcoord = bge.render.getWindowHeight(target)*camera.getScreenPosition(target)[1]
        infoTarget = target['unitName'] + str(int(own.getDistanceTo(target)/1000))
        bge.logic.globalDict['targetPixel'] = [winXcoord, winYcoord, infoTarget]
       
        targetDistance = own.getDistanceTo(target)
        targetSpeed = target.localLinearVelocity[1]
        targetHeight = target.worldPosition[2]
       
        bge.logic.globalDict['targetData'] = [targetDistance, targetSpeed, targetHeight]
       
    except:
       
        bge.logic.globalDict['targetPixel'] = [0, 0, '']
        bge.logic.globalDict['targetCoord'] = [0, 0]
        bge.logic.globalDict['targetData'] = [0, 0, 0]
       


И второй скрипт - тот самый, для отрисовки текста. Изменения коснулись прежде всего вывода самого текста и его положения на экране. Думаю, здесь понятно, откуда берутся данные - из глобального словаря. В тексте присутствуют следы неудачных проб и ошибок в виде неиспользуемых переменных. Скрипт будет подвергаться дальнейшей доработке.

# import game engine modules
from bge import render
from bge import logic
# import stand alone modules
import bgl
import blf

winXcoord = logic.globalDict['targetPixel'][0]
winYcoord = logic.globalDict['targetPixel'][1]
infoText = logic.globalDict['targetPixel'][2]


def init():
    logic.font_id = 0 #blf.load(font_path)
    scene = logic.getCurrentScene()
    scene.post_draw = [write]
    #print(logic.globalDict['targetPixel'][2])
   

def write():
    width = render.getWindowWidth()
    height = render.getWindowHeight()
  
   
    bgl.glMatrixMode(bgl.GL_PROJECTION)
    bgl.glLoadIdentity()
    bgl.gluOrtho2D(0, width, 0, height)
    bgl.glMatrixMode(bgl.GL_MODELVIEW)
    bgl.glLoadIdentity()
    
    font_id = logic.font_id
    blf.position(font_id, logic.globalDict['targetPixel'][0], logic.globalDict['targetPixel'][1], 0)
    blf.size(font_id, 25, 36)
   
    bgl.glColor3f(0.0,0.0,1.0)
    #bgl.glColor4f
   
    bgl.glDisable(bgl.GL_LIGHTING)
    blf.draw(font_id, logic.globalDict['targetPixel'][2])
   
   
   

вторник, 13 декабря 2016 г.

Цветомузыка СПО.

СПО - сокращение от "Системы Предупреждения об облучении". Ныне эти системы весьма распространены, в том числе и среди автолюбителей, называясь "антирадаром" (чтобы заранее узнать, где сидит злой гаишник, готовый выписать штраф за превышение скорости)...
На самолетах военных же СПО предназначена для идентификации угрозы со стороны вражеских машин, использующих радары. И именно радары - именно на них реагирует СПО. Впрочем, также успешно СПО реагирует на облучение радаров ЗРК, если известна их частота 9в первые дни войны Судного Дня израильтянам здорово досталось от "Кубов",  частоты работы которых, в отличие от ЗРК С-75 и С-125, израильтянам известны не были и СПО их самолетов на работу станций ЗРК "Куб" не реагировали).
Так что СПО не спасет от внезапного выстрела из ПЗРК или неожиданно пущенной в хвост ракеты с ТГСН, которую враг запустил, используя теплопеленгатор. Но вот внезапного пуска ракеты с радийной головкой самонаведения не выйдет в принципе, если СПО зафиксировала работу радара. Причем радар выдает себя на гораздо большем удалении, нежели может охватить - отраженные импульсы с увеличением дистанции теряют силу,  не доходя до приемника, а вот излученные импульсы вполне способны СПО "потревожить" на дистанции, с которой противник просто не сможет принять отреженный сигнал.
все это - небольшое лирико-техническое отступление. В игре СПО только одна - и находится она у самолета игрока, точнее, на оверлейной сцене кабины. Ее единственное назначение - сообщить игроку об угрозе со стороны противника. Причем во второй версии СПО угрозы селектирует по степени опасности. тут играет свою роль не только дистанция до противника, но и его готовность открыть огонь или уже выпустившего ракету.
Ниже приведен скрин работающей СПО-10 "Сирена-3" в кабине истребителя МиГ-23МФ.
 Панель СПО - в белом прямоугольнике.


 Устройство включает в себя по четыре жельые и красные лампы, расположенных по периферии круглой панели и одну большую красную лампу посередине на изображении самолета. На скрине она потушена. Горят одна жельая лампа (правый передний квадрант - угроза спереди справа) и четыре красные лампы - показываюшие расстояние до источника угрозы - в пределах 20-30 км.
Мне неизвестно точно в деталях, как работает СПО-10, поэтому в соответствии работы красных лампочек я не уверен - тут я исходил из индикации работы СПО-15, на которой имеется ряд лампочек, отвечающих за отображение  дистанции до угрозы. Да и индикация СПО-15 тоже куда как более продвинута, о ней я напишу потом, когда до нее доберусь.
Пришлось повозиться со звуками, потому что СПО выдает не только световые, но и звукаовые сигналы земмером, меняющим тон и частоту звучания в соответствии с обстановкой.
А теперь немного кода.Сначала я дописал небольшую функцию для передачи в кабину данных об угрозах.

def SPODATA(own, target):
 
    indexOwn = str(id(own))                                        #Индекс перехватчика
    #indexTarget = str(id(target))                                  #Индекс цели
    distance = own.getDistanceTo(target)/1000                #Дистанция от источника угрозы
    LockOn = own['LockOn']                                   #Режим работы сенсора - облучение, захват, подсветка
    typeSensor = own['localDict']['sensList'][own['typeSensor']]   #Тип сенсора
    targetWectOwn = target.getVectTo(own)[2]                       #Ориентация цели относительно перехватчика
    #ownVectTarget = own.getVectTo(target)[2]                        #Обратная ориентация
    heigtUpDown = own.worldPosition[2] - target.worldPosition[2]   #Показатель превышения-принижения перехватчика перед целью
    PR = own['PR']
    AVTO = own['avto']
   
    #Список харакетирисимкм угрозыдля работы СПО оверлейной сцены
    SPODATA = [indexOwn, AVTO, PR, LockOn, distance, typeSensor, targetWectOwn, heigtUpDown]
   
    #Глобальный словарь
   
    bge.logic.globalDict['SPODATA'].append(SPODATA)

Думаю, в комментариях к коду понятно, ради чего все это нужно. Через определенные промежутки времени идет обнуление данных - многомерный список "пустеет", чтобы не засорять память. А "на том конце провода" идет селекция угроз из списка и работает индикация.  Звкеи работают в другом модуле, его я приводить не буду, поскольку ЕМНИП, я уже выкладывал тут его код. Саму функцию выбора звука я чуть доработал, но принцип все тот же - выбирается актуатор по НАЗВАНИЮ, задаваемый проперти audioProp.
Ну, в этой функции и задана работа ламп индикации по принципу "видно- не видно". СПО-10 система относительно простая, даже примитивная, гораздо сложнее ее СПО-15, правда, там индикация более подробная.
Итак, код работы СПО-10.

#SPODATA = [indexOwn, AVTO, PR, LockOn, distance, typeSensor, targetWectOwn, heigtUpDown]

def spo10():   
    cont = bge.logic.getCurrentController()
    scene = bge.logic.getCurrentScene()
    own = cont.owner
   
    #Это списки по степени угрозы
    listAVTO = []
    listPR = []
    listLockOn = []
    listNotThreat = []
   
    SelectThreatList = bge.logic.globalDict['SPODATA']
   
    #Список ламп СПО-10
    quad1 = scene.objects['SPOquad1']
    quad2 = scene.objects['SPOquad2']
    quad3 = scene.objects['SPOquad3']
    quad4 = scene.objects['SPOquad4']
    SPOalarm = scene.objects['SPOalarm']
    SPOdist5km = scene.objects['SPOdist5km']
    SPOdist10km = scene.objects['SPOdist10km']
    SPOdist15km = scene.objects['SPOdist15km']
    SPOdist20km = scene.objects['SPOdist20km']
   
    Cockpit = scene.objects['Cockpit']
   
    lampQuadrant = [quad1,quad2,quad3,quad4]
    lampDistance = [SPOdist5km,SPOdist10km,SPOdist15km,SPOdist20km]
   
    SPODATA = bge.logic.globalDict['SPODATA']
   
    #Если есть что  обрабатывать
    if len(SPODATA) > 0:
        for threat in SPODATA:
           
            #Если просто облучают
            if threat[1] == 0:
                listNotThreat.append(threat)
           
            #Если взяли на сопровождение
            elif threat[1] == 1:
                listNotAVTO.append(threat)
               
                #Если смаолет игрока захвачен и противник готов применить оружие
                if threat[2] == 1:
                    listPR.append(threat)
               
                #Если противник уже выпустил ракету и подсвечивает ее
                if threat[3] == 1:
                    listLockOn.append(threat)
                   
    #Теперь селектируем приоритетную угрозу в списке SelectThreatList                  
    #Идет сортировка по степени угрозы, выбирается сначала самая опасная
    if len(listLockOn) > 0:
        SelectThreatList = sorted(listLockOn, key = lambda obj:obj[4])
    else: 
        if len(listPR) > 0:
            SelectThreatList = sorted(listPR, key = lambda obj:obj[4])
        else: 
            if len(listAVTO) > 0:
                SelectThreatList = sorted(listAVTO, key = lambda obj:obj[4])
            else: 
                if len(listNotThreat) > 0:
                    SelectThreatList = sorted(listNotThreat, key = lambda obj:obj[4])
                else: 
                    SelectThreatList = []  
   
    #Если угрозы отсутствую, гасим все лампы на панели СПО       
    if len(SelectThreatList) == 0:
        for lampQuadrants in lampQuadrant:
            lampQuadrants.visible = 0
        for lampDistances in lampDistance:
            lampDistances.visible = 0      
    else:
        #А иначе, начинаем обработку обстановки
       
        #Лампа ракетной атаки противника
        if SelectThreatList[0][3] == 1:
            SPOalarm.visible - 1
        else:
            SPOalarm.visible = 0
       
        #Лмапы дистанции до угрозы   
        if SelectThreatList[0][4] > 30:
            lampDistance[0].visible = 0
            lampDistance[1].visible = 0
            lampDistance[2].visible = 0
            lampDistance[3].visible = 0
       
        elif 20 < SelectThreatList[0][4] < 30:
            lampDistance[0].visible = 1
            lampDistance[1].visible = 1
            lampDistance[2].visible = 1
            lampDistance[3].visible = 1
           
        elif 10 < SelectThreatList[0][4] < 15:
            lampDistance[0].visible = 1
            lampDistance[1].visible = 1
            lampDistance[2].visible = 1
            lampDistance[3].visible = 0
           
        elif 5 < SelectThreatList[0][4] < 10:
            lampDistance[0].visible = 1
            lampDistance[1].visible = 1
            lampDistance[2].visible = 0
            lampDistance[3].visible = 0
           
        elif SelectThreatList[0][4] < 5:
            lampDistance[0].visible = 1
            lampDistance[1].visible = 0
            lampDistance[2].visible = 0
            lampDistance[3].visible = 0
           
        #Лампы квадрантов
       
        #Слева спереди
        if SelectThreatList[0][6][0] < 0 and SelectThreatList[0][6][0] > 0:              
            lampQuadrant[0].visible = 0
            lampQuadrant[1].visible = 1
            lampQuadrant[2].visible = 0
            lampQuadrant[3].visible = 0
      
        #Справа спереди
        elif SelectThreatList[0][6][0] > 0 and SelectThreatList[0][6][0] > 0:              
            lampQuadrant[0].visible = 1
            lampQuadrant[1].visible = 0
            lampQuadrant[2].visible = 0
            lampQuadrant[3].visible = 0
          
       
        #Справа сзади
        elif SelectThreatList[0][6][0] > 0 and SelectThreatList[0][6][0] < 0:              
            lampQuadrant[0].visible = 0
            lampQuadrant[1].visible = 0
            lampQuadrant[2].visible = 1
            lampQuadrant[3].visible = 0
           
        #Слева сзади
        elif SelectThreatList[0][6][0] < 0 and SelectThreatList[0][6][0] < 0:              
            lampQuadrant[0].visible = 0
            lampQuadrant[1].visible = 0
            lampQuadrant[2].visible = 0
            lampQuadrant[3].visible = 1

        #Звуковая сигнализация
        if len(SelectThreatList) == 0:
            Cockpit['audioProp'] = 'NULL'
            Cockpit['volume'] = 0.0
            Cockpit['pitchSound'] = 0.0
        else:
            if SelectThreatList[0][3] == 1:
                Cockpit['audioProp'] = 'InfoLaunch'
                Cockpit['volume'] = 1.0
                Cockpit['pitchSound'] = 2.0
            else:
                if SelectThreatList[0][2] == 1:
                    Cockpit['audioProp'] = 'InfoLock'
                    Cockpit['volume'] = 1.0
                    Cockpit['pitchSound'] = 3.0
                else:
                    if SelectThreatList[0][1] == 1:
                        Cockpit['audioProp'] = 'InfoLock'
                        Cockpit['volume'] = 1.0
                        Cockpit['pitchSound'] = 1.0
                    else:
                        Cockpit['audioProp'] = 'InfoLock'
                        Cockpit['volume'] = 1.0
                        Cockpit['pitchSound'] = 0.0

Это, скорее, черновой вариант. Тут можно еще подработать код. Главный принцип 0 из общей мешанины вложенных списков выделяются списки по категориям угрозы. И эти списки сортируются по дальности до источника угрозы - приоритет отдается ближайшему. И вот тут-то выбрана последняя "матрешка" - выбранный элемент большого списка - это тоже списочек, только маленький, а внем расписано направление на угрозу, дистанция, степень опасности и так далее. Типа Кощеевой иглы, можно и так сказать (игла в яйце, яйцо - в утке, утка в зайце, заяц - в шоке). Также в этой функции выдается команда на использование звука, его частоту и громкость.
Таким образом, еще один важный этап позади. впереди - обмен информацией между ботами -  кто кого "ведет" и по кому бьет, а также отработка боевого маневрирования и стрельбы. Если это удастся сделать, то основная часть работы будет позади. Все остальное - уже детали и код-заготовка для таких деталей уже будет в наличии.

Цветомузыка СПО.

Пусть пока повисит, как памятник моей невнимательности. Сообщение выше с тем же названием содержит полезную информацию. Потом, если получится, удалю.

четверг, 8 декабря 2016 г.

Пробуждение искусственного разума.

В этом посте не будет картинок. по причине довольно рутинных вещей на этих самых картинках.
После ударного труда над подключением F-16, и особенно отработки размещения бомб и ракет типа "Мейверки" на пилонах внешней подвески встал извечный вопрос. Немного похожий на извечный русский вопрос: "Что делать?" Точнее, как мне кажется, извечный вопрос игродела: "Что делать дальше?". Пара оппонентов г7отовы - Ф-15 и Ф-16. МиГ-23 и Су-25 вроде как тоже готовы. Хочешь не хочешь, а надо браться за искусственный интеллект. Для опробования смены уровней детализации хватало примитивногог рефлекса для бота - тот тупо разгонялся и пер себе по прямой, позволяя камере кружить вокруг него и выискивать недочеты. Но рано или поздно большая часть недочетов устраняется и приходится заниматься уже искусственным интеллектом.
Для начала я попробовал несколько усложнить маршевый режим. На высоте менее 65 процентов от предельной для бота в скрипте я выставил ему увеличение мощности до 900 из 1100 и прописал увеличить тангаж, но не слишком сильно, насовав в код ограничений:

def marsh():
    scene = bge.logic.getCurrentScene()
    cont = bge.logic.getCurrentController()
    self = cont.owner
   
    orient = self.worldOrientation[2][1]
   
    ROLLnull(self)
   
    if self.worldPosition[2] < self['heightMax'] * 0.65:
        if orient < self['limitPitch'] * 0.3:
            upPitch(self)
        elif orient > self['limitPitch'] * 0.3:
            #downPitch(self)
            self['rotatX'] = 0.0
        self['localDict']['etalonDvig'] = 400
    else:
        PITCHnull(self)
        self['localDict']['etalonDvig'] = 900
      
После ряда подгонок и вылавливания мелких ошибок я с удовлетворением наблюдал на экране, как Ф-16 послушно задрал нос и полез вверх. Причем, как и хотел - не строго вертикально, а под некоторым углом.
Набрав нужную высоту, истребитель начал опускать нос и пробил звуковой барьер. Но ненадолго, - он начал постепено сбрасывать тягу до 400, как и прописано в скрипте. Сбросив тягу он полетел дальше, по горизонтали, выровнявшись по глобальным осям. Первые зачатки искусственного разума проявились...
Далее быстренько насовав ограничителей по тем функциям, где картина уже более-менее ясна, типа набора энергии, при котором надо включить форсаж, убрать тормоз и выровняться, как и в описанном выше случае, я задался вопросом применения оружия. Но прежде чем егог применять, боту еще надо выбрать6
1. Тип работы прицела - "земля" или "воздух".
2. Выбрать ВСЕ оружие, пригодное в данном случае,
3. Из всего арсенала выбрать что-то подальнобойнее.
4. Включить нужный сенсор, ибо, к примеру УРВВ Р-24Р нужна обязательная подсветка БРЛС, а вот Р-24Т абсолютно все равно, получит ли она целеуказание от БРЛС или теплопеленгатора. И подсвечивать ей цель не надо.

Выполняет все эти заумные манипуляции еще одна функция, сочиненная вчера и сегодня дополненная строчкой выбора сенсора. В принципе, все там понятно, единственно что, постороннему человеку будет сожновато понять, откуда берутся все эти многочисленные свойства-проперти. Да, их много, но пока это "черновой" вариант, в будущем можно будет как-то постепенно упрощать  текст, увеличивая скорость работы игры. пока что мне важно получить вменяемый и работающий (самое главное!) вариант, от которого можно отталкиваться.Тем более, что во второй версии добавление нового проходит на порядок быстрее и легче, чем в первой (хотя не будь первой, не было бы и этой версии - опыт в одночасье не получишь). Итак смотрим скрипт, а точнее, функцию выбора оружия, сенсора и типа прицеливания.

 def sensorBot(self):
    listBotWeapon = [[],[],[]]
    listWeaponTemp = []
    #Выбор режима работы прицельных систем - воздух или земля
    if self['tipMissions'] in battleAir:
        self['targetType'] = 0
    elif self['tipMissions'] in battleGround:
        self['targetType'] = 1
       
    for weapon in self.childrenRecursive:
        if 'weapon' in weapon:
             
            if weapon['childBK'] > 0:
                #Если имеется такое проперти, то этот тип оружия -
                #либо управляемые или неуправляемые ракеты или же управляемые бомбы
                if 'distMax' in weapon:
                    #все управляемое оружие имеет приоритет по очередности использования
                    #заносим в сегментр 1 многомерного списка (то есть нулевой)
                    if 'tipGSN' in weapon:
                        #Кроме наличия ГСН, необходимо, чтобы режим работы прицела соответствовал
                        #предназначению оружия - земля или небо
                        if str(self['targetType']) in weapon['typeTarget']:
                            listBotWeapon[0].append(weapon)
                   
                    #а вот ракеты НАР, блоки НАР ставим с ледующий сегмент
                    elif 'tipGSN' not in weapon:
                        listBotWeapon[1].append(weapon)
               
                #Это - для всевозможных кассет, бомб и зажигательных баков
                elif 'distMax' not in weapon:
                    listBotWeapon[2].append(weapon)
                """
                Таким образом выстраивается оечередность использования вооружений
                1. Управляемое оружие - ракеты и бомбы
                2. Неуправляемые ракеты - одиночные и блоки
                3. Всевозможные боеприпасы СВОБОДНОГО падения
                4. Пушки и пушечные контейнеры - для них значение проперти weapon - нулевое
                """
      
    #А далее - сортируем пог дальности действия оружия - выбираем самое дальнобойное.   
    for objWeaponListSegment in listBotWeapon:
        #Сначала сортируем список управляемого оружия
        if len(listBotWeapon[0]) > 0:
            listWeaponTemp = sorted(listBotWeapon[0], key = lambda obj:obj['distMax'])
            self['weapon'] = listWeaponTemp[-1]['weapon']
        #Теперь - неуправляемые ракеты и блоки НАР
        elif len(listBotWeapon[1]) > 0:
            listWeaponTemp = sorted(listBotWeapon[1], key = lambda obj:obj['distMax'])
            self['weapon'] = listWeaponTemp[-1]['weapon']
        #И затем - неуправляемые боеприпасы свободногог падения   
        elif len(listBotWeapon[2]) > 0:
            listWeaponTemp = sorted(listBotWeapon[2], key = lambda obj:obj['distMax'])
            self['weapon'] = listWeaponTemp[-1]['weapon']
        #Если вообще ничего нет, то переходим на пушки
        elif len(listBotWeapon[2]) == 0:
            self['weapon'] = 0 
   
   
    #Последний штрих - оружие-то выбрано и режим работы прицела, но надо еще и сенсор выбрать
    #для сопряжения оружия, режима работы сенсора и типа самого сенсора. Есть РЛГСН,которым нужна
    #только РЛС для работы, а вот целеуказание ИКГСН можно и РЛС задавать и теплопеленгатор
    #использовать, так что этот момент тоже весьма важен      
    for sensorUnit in self['localDict']['sensList']:
        if sensorUnit in listWeaponTemp[-1]['typeSensor']:       
            self['typeSensor'] = self['localDict']['sensList'].index(sensorUnit)
           
    #print(self['typeSensor'], self['unitName'],self['weapon'],self['nameWeapon'])

Для облегчения понимания того, что здесь наворочено, написано много комментариев. Это освежит и собственную память. И да, надо вставлять пояснения, откуда берутся проперти типа ['localdict']['listSensor'], пготому как оно появляется при старте игры во время загрузки юнита и в другом скрипте. Ничего не поделаешь, нынешняя боевая техника - вещь сложная(хотя если делать модель персонажа, то там проперти наберется как бы не больше).

Запустив очередной тест игрового файла я дождался загрузки и нормального старта без ругани консоли (это произошло где-то с третьей попытки после отлова синтаксических ошибок), посмотрел распечатку принат в консоли. Она гласила - 1 F-16C_ 2 AIM-120C, что в переводе на человеческий означало:
1. Сенсор из списка - по номеру второй от начала, то есть 1 после нулевого. Это - БРЛС.
2. Тип бота - Ф-16Це.
3. Выбрана вторая пара подвесок.
4. Имя выбранного оружия - АИМ-120Це АМРААМ.

ЧТД.

Поскольку в легенде миссии я прописал на Ф-16 на первую пару АИМ-7, на вторую - АМРААМ, а на третью - "Сайдуиндеры". Самые дальнобойные-то и выбрались.  Для них нужна работа БРЛС, и она также включилась.
Далее, необходимо обеспечить многочисленные нюансы. Разворот бота в сторону цели, корректирование его ориентации в пространстве. Очень тщательно выверить работу взаимодействия сенсоров юнитов между собой - в первую очередь. Все дело в том, что необходимо сообщать в случае работы сенсоров о совем местонахождении. Самолет, облучающий перед собой участок неба, буквально орет на весь район о своем присутствии. И наоборот - тихо (ну ладно, громко летящий) у самой земли перхватчик, включивший вместо БРЛС теплопленгатор, не насторожит оппонента, прущего где-то вверху и стороне от его курса. И противник поймет, что что-то не так только после взрыва ракеты с тепловой ГСН...
Но ботам я пока стрелять не дам. Сначала необходимо отработать списки угроз, их обновление и структуру, отработать СПО с ее истерикой - мерзким зуммером и миганием разноцветных огоньков, а уж потом дать возможность стрелять противнику.
такая вот диспозиция на сегодняшний день.