env.info('*** MOOSE GITHUB Commit Hash ID: 2023-01-31T10:27:12.0000000Z-48721859fabbb5006ddef989822ad92ee5def497 ***')
env.info('*** MOOSE STATIC INCLUDE START *** ')
ENUMS={}
ENUMS.ROE={
WeaponFree=0,
OpenFireWeaponFree=1,
OpenFire=2,
ReturnFire=3,
WeaponHold=4,
}
ENUMS.ROT={
NoReaction=0,
PassiveDefense=1,
EvadeFire=2,
BypassAndEscape=3,
AllowAbortMission=4,
}
ENUMS.AlarmState={
Auto=0,
Green=1,
Red=2,
}
ENUMS.WeaponFlag={
LGB=2,
TvGB=4,
SNSGB=8,
HEBomb=16,
Penetrator=32,
NapalmBomb=64,
FAEBomb=128,
ClusterBomb=256,
Dispencer=512,
CandleBomb=1024,
ParachuteBomb=2147483648,
LightRocket=2048,
MarkerRocket=4096,
CandleRocket=8192,
HeavyRocket=16384,
AntiRadarMissile=32768,
AntiShipMissile=65536,
AntiTankMissile=131072,
FireAndForgetASM=262144,
LaserASM=524288,
TeleASM=1048576,
CruiseMissile=2097152,
AntiRadarMissile2=1073741824,
SRAM=4194304,
MRAAM=8388608,
LRAAM=16777216,
IR_AAM=33554432,
SAR_AAM=67108864,
AR_AAM=134217728,
GunPod=268435456,
BuiltInCannon=536870912,
GuidedBomb=14,
AnyUnguidedBomb=2147485680,
AnyBomb=2147485694,
AnyRocket=30720,
GuidedASM=1572864,
TacticalASM=1835008,
AnyASM=4161536,
AnyASM2=1077903360,
AnyAAM=264241152,
AnyAutonomousMissile=36012032,
AnyMissile=268402688,
Cannons=805306368,
Torpedo=4294967296,
Auto=3221225470,
AutoDCS=1073741822,
AnyAG=2956984318,
AnyAA=264241152,
AnyUnguided=2952822768,
AnyGuided=268402702,
}
ENUMS.WeaponType={}
ENUMS.WeaponType.Bomb={
LGB=2,
TvGB=4,
SNSGB=8,
HEBomb=16,
Penetrator=32,
NapalmBomb=64,
FAEBomb=128,
ClusterBomb=256,
Dispencer=512,
CandleBomb=1024,
ParachuteBomb=2147483648,
GuidedBomb=14,
AnyUnguidedBomb=2147485680,
AnyBomb=2147485694,
}
ENUMS.WeaponType.Rocket={
LightRocket=2048,
MarkerRocket=4096,
CandleRocket=8192,
HeavyRocket=16384,
AnyRocket=30720,
}
ENUMS.WeaponType.Gun={
GunPod=268435456,
BuiltInCannon=536870912,
Cannons=805306368,
}
ENUMS.WeaponType.Missile={
AntiRadarMissile=32768,
AntiShipMissile=65536,
AntiTankMissile=131072,
FireAndForgetASM=262144,
LaserASM=524288,
TeleASM=1048576,
CruiseMissile=2097152,
AntiRadarMissile2=1073741824,
GuidedASM=1572864,
TacticalASM=1835008,
AnyASM=4161536,
AnyASM2=1077903360,
AnyAutonomousMissile=36012032,
AnyMissile=268402688,
}
ENUMS.WeaponType.AAM={
SRAM=4194304,
MRAAM=8388608,
LRAAM=16777216,
IR_AAM=33554432,
SAR_AAM=67108864,
AR_AAM=134217728,
AnyAAM=264241152,
}
ENUMS.WeaponType.Torpedo={
Torpedo=4294967296,
}
ENUMS.WeaponType.Any={
Weapon=3221225470,
AG=2956984318,
AA=264241152,
Unguided=2952822768,
Guided=268402702,
}
ENUMS.MissionTask={
NOTHING="Nothing",
AFAC="AFAC",
ANTISHIPSTRIKE="Antiship Strike",
AWACS="AWACS",
CAP="CAP",
CAS="CAS",
ESCORT="Escort",
FIGHTERSWEEP="Fighter Sweep",
GROUNDATTACK="Ground Attack",
INTERCEPT="Intercept",
PINPOINTSTRIKE="Pinpoint Strike",
RECONNAISSANCE="Reconnaissance",
REFUELING="Refueling",
RUNWAYATTACK="Runway Attack",
SEAD="SEAD",
TRANSPORT="Transport",
}
ENUMS.Formation={}
ENUMS.Formation.FixedWing={}
ENUMS.Formation.FixedWing.LineAbreast={}
ENUMS.Formation.FixedWing.LineAbreast.Close=65537
ENUMS.Formation.FixedWing.LineAbreast.Open=65538
ENUMS.Formation.FixedWing.LineAbreast.Group=65539
ENUMS.Formation.FixedWing.Trail={}
ENUMS.Formation.FixedWing.Trail.Close=131073
ENUMS.Formation.FixedWing.Trail.Open=131074
ENUMS.Formation.FixedWing.Trail.Group=131075
ENUMS.Formation.FixedWing.Wedge={}
ENUMS.Formation.FixedWing.Wedge.Close=196609
ENUMS.Formation.FixedWing.Wedge.Open=196610
ENUMS.Formation.FixedWing.Wedge.Group=196611
ENUMS.Formation.FixedWing.EchelonRight={}
ENUMS.Formation.FixedWing.EchelonRight.Close=262145
ENUMS.Formation.FixedWing.EchelonRight.Open=262146
ENUMS.Formation.FixedWing.EchelonRight.Group=262147
ENUMS.Formation.FixedWing.EchelonLeft={}
ENUMS.Formation.FixedWing.EchelonLeft.Close=327681
ENUMS.Formation.FixedWing.EchelonLeft.Open=327682
ENUMS.Formation.FixedWing.EchelonLeft.Group=327683
ENUMS.Formation.FixedWing.FingerFour={}
ENUMS.Formation.FixedWing.FingerFour.Close=393217
ENUMS.Formation.FixedWing.FingerFour.Open=393218
ENUMS.Formation.FixedWing.FingerFour.Group=393219
ENUMS.Formation.FixedWing.Spread={}
ENUMS.Formation.FixedWing.Spread.Close=458753
ENUMS.Formation.FixedWing.Spread.Open=458754
ENUMS.Formation.FixedWing.Spread.Group=458755
ENUMS.Formation.FixedWing.BomberElement={}
ENUMS.Formation.FixedWing.BomberElement.Close=786433
ENUMS.Formation.FixedWing.BomberElement.Open=786434
ENUMS.Formation.FixedWing.BomberElement.Group=786435
ENUMS.Formation.FixedWing.BomberElementHeight={}
ENUMS.Formation.FixedWing.BomberElementHeight.Close=851968
ENUMS.Formation.FixedWing.FighterVic={}
ENUMS.Formation.FixedWing.FighterVic.Close=917505
ENUMS.Formation.FixedWing.FighterVic.Open=917506
ENUMS.Formation.RotaryWing={}
ENUMS.Formation.RotaryWing.Column={}
ENUMS.Formation.RotaryWing.Column.D70=720896
ENUMS.Formation.RotaryWing.Wedge={}
ENUMS.Formation.RotaryWing.Wedge.D70=8
ENUMS.Formation.RotaryWing.FrontRight={}
ENUMS.Formation.RotaryWing.FrontRight.D300=655361
ENUMS.Formation.RotaryWing.FrontRight.D600=655362
ENUMS.Formation.RotaryWing.FrontLeft={}
ENUMS.Formation.RotaryWing.FrontLeft.D300=655617
ENUMS.Formation.RotaryWing.FrontLeft.D600=655618
ENUMS.Formation.RotaryWing.EchelonRight={}
ENUMS.Formation.RotaryWing.EchelonRight.D70=589825
ENUMS.Formation.RotaryWing.EchelonRight.D300=589826
ENUMS.Formation.RotaryWing.EchelonRight.D600=589827
ENUMS.Formation.RotaryWing.EchelonLeft={}
ENUMS.Formation.RotaryWing.EchelonLeft.D70=590081
ENUMS.Formation.RotaryWing.EchelonLeft.D300=590082
ENUMS.Formation.RotaryWing.EchelonLeft.D600=590083
ENUMS.Formation.Vehicle={}
ENUMS.Formation.Vehicle.Vee="Vee"
ENUMS.Formation.Vehicle.EchelonRight="EchelonR"
ENUMS.Formation.Vehicle.OffRoad="Off Road"
ENUMS.Formation.Vehicle.Rank="Rank"
ENUMS.Formation.Vehicle.EchelonLeft="EchelonL"
ENUMS.Formation.Vehicle.OnRoad="On Road"
ENUMS.Formation.Vehicle.Cone="Cone"
ENUMS.Formation.Vehicle.Diamond="Diamond"
ENUMS.FormationOld={}
ENUMS.FormationOld.FixedWing={}
ENUMS.FormationOld.FixedWing.LineAbreast=1
ENUMS.FormationOld.FixedWing.Trail=2
ENUMS.FormationOld.FixedWing.Wedge=3
ENUMS.FormationOld.FixedWing.EchelonRight=4
ENUMS.FormationOld.FixedWing.EchelonLeft=5
ENUMS.FormationOld.FixedWing.FingerFour=6
ENUMS.FormationOld.FixedWing.SpreadFour=7
ENUMS.FormationOld.FixedWing.BomberElement=12
ENUMS.FormationOld.FixedWing.BomberElementHeight=13
ENUMS.FormationOld.FixedWing.FighterVic=14
ENUMS.FormationOld.RotaryWing={}
ENUMS.FormationOld.RotaryWing.Wedge=8
ENUMS.FormationOld.RotaryWing.Echelon=9
ENUMS.FormationOld.RotaryWing.Front=10
ENUMS.FormationOld.RotaryWing.Column=11
ENUMS.Morse={}
ENUMS.Morse.A="* -"
ENUMS.Morse.B="- * * *"
ENUMS.Morse.C="- * - *"
ENUMS.Morse.D="- * *"
ENUMS.Morse.E="*"
ENUMS.Morse.F="* * - *"
ENUMS.Morse.G="- - *"
ENUMS.Morse.H="* * * *"
ENUMS.Morse.I="* *"
ENUMS.Morse.J="* - - -"
ENUMS.Morse.K="- * -"
ENUMS.Morse.L="* - * *"
ENUMS.Morse.M="- -"
ENUMS.Morse.N="- *"
ENUMS.Morse.O="- - -"
ENUMS.Morse.P="* - - *"
ENUMS.Morse.Q="- - * -"
ENUMS.Morse.R="* - *"
ENUMS.Morse.S="* * *"
ENUMS.Morse.T="-"
ENUMS.Morse.U="* * -"
ENUMS.Morse.V="* * * -"
ENUMS.Morse.W="* - -"
ENUMS.Morse.X="- * * -"
ENUMS.Morse.Y="- * - -"
ENUMS.Morse.Z="- - * *"
ENUMS.Morse.N1="* - - - -"
ENUMS.Morse.N2="* * - - -"
ENUMS.Morse.N3="* * * - -"
ENUMS.Morse.N4="* * * * -"
ENUMS.Morse.N5="* * * * *"
ENUMS.Morse.N6="- * * * *"
ENUMS.Morse.N7="- - * * *"
ENUMS.Morse.N8="- - - * *"
ENUMS.Morse.N9="- - - - *"
ENUMS.Morse.N0="- - - - -"
ENUMS.Morse[" "]=" "
ENUMS.ISOLang=
{
Arabic='AR',
Chinese='ZH',
English='EN',
French='FR',
German='DE',
Russian='RU',
Spanish='ES',
Japanese='JA',
Italian='IT',
}
ENUMS.Phonetic=
{
A='Alpha',
B='Bravo',
C='Charlie',
D='Delta',
E='Echo',
F='Foxtrot',
G='Golf',
H='Hotel',
I='India',
J='Juliett',
K='Kilo',
L='Lima',
M='Mike',
N='November',
O='Oscar',
P='Papa',
Q='Quebec',
R='Romeo',
S='Sierra',
T='Tango',
U='Uniform',
V='Victor',
W='Whiskey',
X='Xray',
Y='Yankee',
Z='Zulu',
}
ENUMS.ReportingName=
{
NATO={
Dragon="JF-17",
Fagot="MiG-15",
Farmer="MiG-19",
Felon="Su-57",
Fencer="Su-24",
Fishbed="MiG-21",
Fitter="Su-17",
Flogger="MiG-23",
Flogger_D="MiG-27",
Flagon="Su-15",
Foxbat="MiG-25",
Fulcrum="MiG-29",
Foxhound="MiG-31",
Flanker="Su-27",
Flanker_C="Su-30",
Flanker_E="Su-35",
Flanker_F="Su-37",
Flanker_L="J-11A",
Firebird="J-10",
Sea_Flanker="Su-33",
Fullback="Su-34",
Frogfoot="Su-25",
Tomcat="F-14",
Mirage="Mirage",
Codling="Yak-40",
Maya="L-39",
Warthog="A-10",
Skyhawk="A-4E",
Viggen="AJS37",
Harrier="AV-8B",
Spirit="B-2",
Aviojet="C-101",
Nighthawk="F-117A",
Eagle="F-15C",
Mudhen="F-15E",
Viper="F-16",
Phantom="F-4E",
Tiger="F-5",
Sabre="F-86",
Hornet="A-18",
Hawk="Hawk",
Albatros="L-39",
Goshawk="T-45",
Starfighter="F-104",
Tornado="Tornado",
Atlas="A400",
Lancer="B1-B",
Stratofortress="B-52H",
Hercules="C-130",
Super_Hercules="Hercules",
Globemaster="C-17",
Greyhound="C-2A",
Galaxy="C-5",
Hawkeye="E-2D",
Sentry="E-3A",
Stratotanker="KC-135",
Extender="KC-10",
Orion="P-3C",
Viking="S-3B",
Osprey="V-22",
Badger="H6-J",
Bear_J="Tu-142",
Bear="Tu-95",
Blinder="Tu-22",
Blackjack="Tu-160",
Clank="An-30",
Curl="An-26",
Candid="IL-76",
Midas="IL-78",
Mainstay="A-50",
Mainring="KJ-2000",
Yak="Yak-52",
Helix="Ka-27",
Shark="Ka-50",
Hind="Mi-24",
Halo="Mi-26",
Hip="Mi-8",
Havoc="Mi-28",
Gazelle="SA342",
Huey="UH-1H",
Cobra="AH-1",
Apache="AH-64",
Chinook="CH-47",
Sea_Stallion="CH-53",
Kiowa="OH-58",
Seahawk="SH-60",
Blackhawk="UH-60",
Sea_King="S-61",
UCAV="WingLoong",
Reaper="MQ-9",
Predator="MQ-1A",
}
}
env.setErrorMessageBoxEnabled(false)
routines={}
routines.majorVersion=3
routines.minorVersion=3
routines.build=22
routines.utils={}
routines.utils.round=function(number,decimals)
local power=10^decimals
return math.floor(number*power)/power
end
routines.utils.deepCopy=function(object)
local lookup_table={}
local function _copy(object)
if type(object)~="table"then
return object
elseif lookup_table[object]then
return lookup_table[object]
end
local new_table={}
lookup_table[object]=new_table
for index,value in pairs(object)do
new_table[_copy(index)]=_copy(value)
end
return setmetatable(new_table,getmetatable(object))
end
local objectreturn=_copy(object)
return objectreturn
end
routines.utils.oneLineSerialize=function(tbl)
lookup_table={}
local function _Serialize(tbl)
if type(tbl)=='table'then
if lookup_table[tbl]then
return lookup_table[object]
end
local tbl_str={}
lookup_table[tbl]=tbl_str
tbl_str[#tbl_str+1]='{'
for ind,val in pairs(tbl)do
local ind_str={}
if type(ind)=="number"then
ind_str[#ind_str+1]='['
ind_str[#ind_str+1]=tostring(ind)
ind_str[#ind_str+1]=']='
else
ind_str[#ind_str+1]='['
ind_str[#ind_str+1]=routines.utils.basicSerialize(ind)
ind_str[#ind_str+1]=']='
end
local val_str={}
if((type(val)=='number')or(type(val)=='boolean'))then
val_str[#val_str+1]=tostring(val)
val_str[#val_str+1]=','
tbl_str[#tbl_str+1]=table.concat(ind_str)
tbl_str[#tbl_str+1]=table.concat(val_str)
elseif type(val)=='string'then
val_str[#val_str+1]=routines.utils.basicSerialize(val)
val_str[#val_str+1]=','
tbl_str[#tbl_str+1]=table.concat(ind_str)
tbl_str[#tbl_str+1]=table.concat(val_str)
elseif type(val)=='nil'then
val_str[#val_str+1]='nil,'
tbl_str[#tbl_str+1]=table.concat(ind_str)
tbl_str[#tbl_str+1]=table.concat(val_str)
elseif type(val)=='table'then
if ind=="__index"then
else
val_str[#val_str+1]=_Serialize(val)
val_str[#val_str+1]=','
tbl_str[#tbl_str+1]=table.concat(ind_str)
tbl_str[#tbl_str+1]=table.concat(val_str)
end
elseif type(val)=='function'then
else
end
end
tbl_str[#tbl_str+1]='}'
return table.concat(tbl_str)
else
if type(tbl)=='string'then
return tbl
else
return tostring(tbl)
end
end
end
local objectreturn=_Serialize(tbl)
return objectreturn
end
routines.utils.basicSerialize=function(s)
if s==nil then
return"\"\""
else
if((type(s)=='number')or(type(s)=='boolean')or(type(s)=='function')or(type(s)=='table')or(type(s)=='userdata'))then
return tostring(s)
elseif type(s)=='string'then
s=string.format('%s',s:gsub("%%","%%%%"))
return s
end
end
end
routines.utils.toDegree=function(angle)
return angle*180/math.pi
end
routines.utils.toRadian=function(angle)
return angle*math.pi/180
end
routines.utils.metersToNM=function(meters)
return meters/1852
end
routines.utils.metersToFeet=function(meters)
return meters/0.3048
end
routines.utils.NMToMeters=function(NM)
return NM*1852
end
routines.utils.feetToMeters=function(feet)
return feet*0.3048
end
routines.utils.mpsToKnots=function(mps)
return mps*3600/1852
end
routines.utils.mpsToKmph=function(mps)
return mps*3.6
end
routines.utils.knotsToMps=function(knots)
return knots*1852/3600
end
routines.utils.kmphToMps=function(kmph)
return kmph/3.6
end
function routines.utils.makeVec2(Vec3)
if Vec3.z then
return{x=Vec3.x,y=Vec3.z}
else
return{x=Vec3.x,y=Vec3.y}
end
end
function routines.utils.makeVec3(Vec2,y)
if not Vec2.z then
if not y then
y=0
end
return{x=Vec2.x,y=y,z=Vec2.y}
else
return{x=Vec2.x,y=Vec2.y,z=Vec2.z}
end
end
function routines.utils.makeVec3GL(Vec2,offset)
local adj=offset or 0
if not Vec2.z then
return{x=Vec2.x,y=(land.getHeight(Vec2)+adj),z=Vec2.y}
else
return{x=Vec2.x,y=(land.getHeight({x=Vec2.x,y=Vec2.z})+adj),z=Vec2.z}
end
end
routines.utils.zoneToVec3=function(zone)
local new={}
if type(zone)=='table'and zone.point then
new.x=zone.point.x
new.y=zone.point.y
new.z=zone.point.z
return new
elseif type(zone)=='string'then
zone=trigger.misc.getZone(zone)
if zone then
new.x=zone.point.x
new.y=zone.point.y
new.z=zone.point.z
return new
end
end
end
function routines.utils.getDir(vec,point)
local dir=math.atan2(vec.z,vec.x)
dir=dir+routines.getNorthCorrection(point)
if dir<0 then
dir=dir+2*math.pi
end
return dir
end
function routines.utils.get2DDist(point1,point2)
point1=routines.utils.makeVec3(point1)
point2=routines.utils.makeVec3(point2)
return routines.vec.mag({x=point1.x-point2.x,y=0,z=point1.z-point2.z})
end
function routines.utils.get3DDist(point1,point2)
return routines.vec.mag({x=point1.x-point2.x,y=point1.y-point2.y,z=point1.z-point2.z})
end
routines.vec={}
routines.vec.add=function(vec1,vec2)
return{x=vec1.x+vec2.x,y=vec1.y+vec2.y,z=vec1.z+vec2.z}
end
routines.vec.sub=function(vec1,vec2)
return{x=vec1.x-vec2.x,y=vec1.y-vec2.y,z=vec1.z-vec2.z}
end
routines.vec.scalarMult=function(vec,mult)
return{x=vec.x*mult,y=vec.y*mult,z=vec.z*mult}
end
routines.vec.scalar_mult=routines.vec.scalarMult
routines.vec.dp=function(vec1,vec2)
return vec1.x*vec2.x+vec1.y*vec2.y+vec1.z*vec2.z
end
routines.vec.cp=function(vec1,vec2)
return{x=vec1.y*vec2.z-vec1.z*vec2.y,y=vec1.z*vec2.x-vec1.x*vec2.z,z=vec1.x*vec2.y-vec1.y*vec2.x}
end
routines.vec.mag=function(vec)
return(vec.x^2+vec.y^2+vec.z^2)^0.5
end
routines.vec.getUnitVec=function(vec)
local mag=routines.vec.mag(vec)
return{x=vec.x/mag,y=vec.y/mag,z=vec.z/mag}
end
routines.vec.rotateVec2=function(vec2,theta)
return{x=vec2.x*math.cos(theta)-vec2.y*math.sin(theta),y=vec2.x*math.sin(theta)+vec2.y*math.cos(theta)}
end
routines.tostringMGRS=function(MGRS,acc)
if acc==0 then
return MGRS.UTMZone..' '..MGRS.MGRSDigraph
else
return MGRS.UTMZone..' '..MGRS.MGRSDigraph..' '..string.format('%0'..acc..'d',routines.utils.round(MGRS.Easting/(10^(5-acc)),0))..' '..string.format('%0'..acc..'d',routines.utils.round(MGRS.Northing/(10^(5-acc)),0))
end
end
routines.tostringLL=function(lat,lon,acc,DMS)
local latHemi,lonHemi
if lat>0 then
latHemi='N'
else
latHemi='S'
end
if lon>0 then
lonHemi='E'
else
lonHemi='W'
end
lat=math.abs(lat)
lon=math.abs(lon)
local latDeg=math.floor(lat)
local latMin=(lat-latDeg)*60
local lonDeg=math.floor(lon)
local lonMin=(lon-lonDeg)*60
if DMS then
local oldLatMin=latMin
latMin=math.floor(latMin)
local latSec=routines.utils.round((oldLatMin-latMin)*60,acc)
local oldLonMin=lonMin
lonMin=math.floor(lonMin)
local lonSec=routines.utils.round((oldLonMin-lonMin)*60,acc)
if latSec==60 then
latSec=0
latMin=latMin+1
end
if lonSec==60 then
lonSec=0
lonMin=lonMin+1
end
local secFrmtStr
if acc<=0 then
secFrmtStr='%02d'
else
local width=3+acc
secFrmtStr='%0'..width..'.'..acc..'f'
end
return string.format('%02d',latDeg)..' '..string.format('%02d',latMin)..'\' '..string.format(secFrmtStr,latSec)..'"'..latHemi..'   '..string.format('%02d',lonDeg)..' '..string.format('%02d',lonMin)..'\' '..string.format(secFrmtStr,lonSec)..'"'..lonHemi
else
latMin=routines.utils.round(latMin,acc)
lonMin=routines.utils.round(lonMin,acc)
if latMin==60 then
latMin=0
latDeg=latDeg+1
end
if lonMin==60 then
lonMin=0
lonDeg=lonDeg+1
end
local minFrmtStr
if acc<=0 then
minFrmtStr='%02d'
else
local width=3+acc
minFrmtStr='%0'..width..'.'..acc..'f'
end
return string.format('%02d',latDeg)..' '..string.format(minFrmtStr,latMin)..'\''..latHemi..'   '..string.format('%02d',lonDeg)..' '..string.format(minFrmtStr,lonMin)..'\''..lonHemi
end
end
routines.tostringBR=function(az,dist,alt,metric)
az=routines.utils.round(routines.utils.toDegree(az),0)
if metric then
dist=routines.utils.round(dist/1000,2)
else
dist=routines.utils.round(routines.utils.metersToNM(dist),2)
end
local s=string.format('%03d',az)..' for '..dist
if alt then
if metric then
s=s..' at '..routines.utils.round(alt,0)
else
s=s..' at '..routines.utils.round(routines.utils.metersToFeet(alt),0)
end
end
return s
end
routines.getNorthCorrection=function(point)
if not point.z then
point.z=point.y
point.y=0
end
local lat,lon=coord.LOtoLL(point)
local north_posit=coord.LLtoLO(lat+1,lon)
return math.atan2(north_posit.z-point.z,north_posit.x-point.x)
end
do
local idNum=0
routines.addEventHandler=function(f)
local handler={}
idNum=idNum+1
handler.id=idNum
handler.f=f
handler.onEvent=function(self,event)
self.f(event)
end
world.addEventHandler(handler)
end
routines.removeEventHandler=function(id)
for key,handler in pairs(world.eventHandlers)do
if handler.id and handler.id==id then
world.eventHandlers[key]=nil
return true
end
end
return false
end
end
function routines.getRandPointInCircle(point,radius,innerRadius)
local theta=2*math.pi*math.random()
local rad=math.random()+math.random()
if rad>1 then
rad=2-rad
end
local radMult
if innerRadius and innerRadius<=radius then
radMult=(radius-innerRadius)*rad+innerRadius
else
radMult=radius*rad
end
if not point.z then
point.z=point.y
end
local rndCoord
if radius>0 then
rndCoord={x=math.cos(theta)*radMult+point.x,y=math.sin(theta)*radMult+point.z}
else
rndCoord={x=point.x,y=point.z}
end
return rndCoord
end
routines.goRoute=function(group,path)
local misTask={id='Mission',params={route={points=routines.utils.deepCopy(path)}}}
if type(group)=='string'then
group=Group.getByName(group)
end
local groupCon=group:getController()
if groupCon then
groupCon:setTask(misTask)
return true
end
Controller.setTask(groupCon,misTask)
return false
end
routines.ground={}
routines.fixedWing={}
routines.heli={}
routines.ground.buildWP=function(point,overRideForm,overRideSpeed)
local wp={}
wp.x=point.x
if point.z then
wp.y=point.z
else
wp.y=point.y
end
local form,speed
if point.speed and not overRideSpeed then
wp.speed=point.speed
elseif type(overRideSpeed)=='number'then
wp.speed=overRideSpeed
else
wp.speed=routines.utils.kmphToMps(20)
end
if point.form and not overRideForm then
form=point.form
else
form=overRideForm
end
if not form then
wp.action='Cone'
else
form=string.lower(form)
if form=='off_road'or form=='off road'then
wp.action='Off Road'
elseif form=='on_road'or form=='on road'then
wp.action='On Road'
elseif form=='rank'or form=='line_abrest'or form=='line abrest'or form=='lineabrest'then
wp.action='Rank'
elseif form=='cone'then
wp.action='Cone'
elseif form=='diamond'then
wp.action='Diamond'
elseif form=='vee'then
wp.action='Vee'
elseif form=='echelon_left'or form=='echelon left'or form=='echelonl'then
wp.action='EchelonL'
elseif form=='echelon_right'or form=='echelon right'or form=='echelonr'then
wp.action='EchelonR'
else
wp.action='Cone'
end
end
wp.type='Turning Point'
return wp
end
routines.fixedWing.buildWP=function(point,WPtype,speed,alt,altType)
local wp={}
wp.x=point.x
if point.z then
wp.y=point.z
else
wp.y=point.y
end
if alt and type(alt)=='number'then
wp.alt=alt
else
wp.alt=2000
end
if altType then
altType=string.lower(altType)
if altType=='radio'or'agl'then
wp.alt_type='RADIO'
elseif altType=='baro'or'asl'then
wp.alt_type='BARO'
end
else
wp.alt_type='RADIO'
end
if point.speed then
speed=point.speed
end
if point.type then
WPtype=point.type
end
if not speed then
wp.speed=routines.utils.kmphToMps(500)
else
wp.speed=speed
end
if not WPtype then
wp.action='Turning Point'
else
WPtype=string.lower(WPtype)
if WPtype=='flyover'or WPtype=='fly over'or WPtype=='fly_over'then
wp.action='Fly Over Point'
elseif WPtype=='turningpoint'or WPtype=='turning point'or WPtype=='turning_point'then
wp.action='Turning Point'
else
wp.action='Turning Point'
end
end
wp.type='Turning Point'
return wp
end
routines.heli.buildWP=function(point,WPtype,speed,alt,altType)
local wp={}
wp.x=point.x
if point.z then
wp.y=point.z
else
wp.y=point.y
end
if alt and type(alt)=='number'then
wp.alt=alt
else
wp.alt=500
end
if altType then
altType=string.lower(altType)
if altType=='radio'or'agl'then
wp.alt_type='RADIO'
elseif altType=='baro'or'asl'then
wp.alt_type='BARO'
end
else
wp.alt_type='RADIO'
end
if point.speed then
speed=point.speed
end
if point.type then
WPtype=point.type
end
if not speed then
wp.speed=routines.utils.kmphToMps(200)
else
wp.speed=speed
end
if not WPtype then
wp.action='Turning Point'
else
WPtype=string.lower(WPtype)
if WPtype=='flyover'or WPtype=='fly over'or WPtype=='fly_over'then
wp.action='Fly Over Point'
elseif WPtype=='turningpoint'or WPtype=='turning point'or WPtype=='turning_point'then
wp.action='Turning Point'
else
wp.action='Turning Point'
end
end
wp.type='Turning Point'
return wp
end
routines.groupToRandomPoint=function(vars)
local group=vars.group
local point=vars.point
local radius=vars.radius or 0
local innerRadius=vars.innerRadius
local form=vars.form or'Cone'
local heading=vars.heading or math.random()*2*math.pi
local headingDegrees=vars.headingDegrees
local speed=vars.speed or routines.utils.kmphToMps(20)
local useRoads
if not vars.disableRoads then
useRoads=true
else
useRoads=false
end
local path={}
if headingDegrees then
heading=headingDegrees*math.pi/180
end
if heading>=2*math.pi then
heading=heading-2*math.pi
end
local rndCoord=routines.getRandPointInCircle(point,radius,innerRadius)
local offset={}
local posStart=routines.getLeadPos(group)
offset.x=routines.utils.round(math.sin(heading-(math.pi/2))*50+rndCoord.x,3)
offset.z=routines.utils.round(math.cos(heading+(math.pi/2))*50+rndCoord.y,3)
path[#path+1]=routines.ground.buildWP(posStart,form,speed)
if useRoads==true and((point.x-posStart.x)^2+(point.z-posStart.z)^2)^0.5>radius*1.3 then
path[#path+1]=routines.ground.buildWP({['x']=posStart.x+11,['z']=posStart.z+11},'off_road',speed)
path[#path+1]=routines.ground.buildWP(posStart,'on_road',speed)
path[#path+1]=routines.ground.buildWP(offset,'on_road',speed)
else
path[#path+1]=routines.ground.buildWP({['x']=posStart.x+25,['z']=posStart.z+25},form,speed)
end
path[#path+1]=routines.ground.buildWP(offset,form,speed)
path[#path+1]=routines.ground.buildWP(rndCoord,form,speed)
routines.goRoute(group,path)
return
end
routines.groupRandomDistSelf=function(gpData,dist,form,heading,speed)
local pos=routines.getLeadPos(gpData)
local fakeZone={}
fakeZone.radius=dist or math.random(300,1000)
fakeZone.point={x=pos.x,y=pos.y,z=pos.z}
routines.groupToRandomZone(gpData,fakeZone,form,heading,speed)
return
end
routines.groupToRandomZone=function(gpData,zone,form,heading,speed)
if type(gpData)=='string'then
gpData=Group.getByName(gpData)
end
if type(zone)=='string'then
zone=trigger.misc.getZone(zone)
elseif type(zone)=='table'and not zone.radius then
zone=trigger.misc.getZone(zone[math.random(1,#zone)])
end
if speed then
speed=routines.utils.kmphToMps(speed)
end
local vars={}
vars.group=gpData
vars.radius=zone.radius
vars.form=form
vars.headingDegrees=heading
vars.speed=speed
vars.point=routines.utils.zoneToVec3(zone)
routines.groupToRandomPoint(vars)
return
end
routines.isTerrainValid=function(coord,terrainTypes)
if coord.z then
coord.y=coord.z
end
local typeConverted={}
if type(terrainTypes)=='string'then
for constId,constData in pairs(land.SurfaceType)do
if string.lower(constId)==string.lower(terrainTypes)or string.lower(constData)==string.lower(terrainTypes)then
table.insert(typeConverted,constId)
end
end
elseif type(terrainTypes)=='table'then
for typeId,typeData in pairs(terrainTypes)do
for constId,constData in pairs(land.SurfaceType)do
if string.lower(constId)==string.lower(typeData)or string.lower(constData)==string.lower(typeId)then
table.insert(typeConverted,constId)
end
end
end
end
for validIndex,validData in pairs(typeConverted)do
if land.getSurfaceType(coord)==land.SurfaceType[validData]then
return true
end
end
return false
end
routines.groupToPoint=function(gpData,point,form,heading,speed,useRoads)
if type(point)=='string'then
point=trigger.misc.getZone(point)
end
if speed then
speed=routines.utils.kmphToMps(speed)
end
local vars={}
vars.group=gpData
vars.form=form
vars.headingDegrees=heading
vars.speed=speed
vars.disableRoads=useRoads
vars.point=routines.utils.zoneToVec3(point)
routines.groupToRandomPoint(vars)
return
end
routines.getLeadPos=function(group)
if type(group)=='string'then
group=Group.getByName(group)
end
local units=group:getUnits()
local leader=units[1]
if not leader then
local lowestInd=math.huge
for ind,unit in pairs(units)do
if ind<lowestInd then
lowestInd=ind
leader=unit
end
end
end
if leader and Unit.isExist(leader)then
return leader:getPosition().p
end
end
routines.getMGRSString=function(vars)
local units=vars.units
local acc=vars.acc or 5
local avgPos=routines.getAvgPos(units)
if avgPos then
return routines.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(avgPos)),acc)
end
end
routines.getLLString=function(vars)
local units=vars.units
local acc=vars.acc or 3
local DMS=vars.DMS
local avgPos=routines.getAvgPos(units)
if avgPos then
local lat,lon=coord.LOtoLL(avgPos)
return routines.tostringLL(lat,lon,acc,DMS)
end
end
routines.getBRStringZone=function(vars)
local zone=trigger.misc.getZone(vars.zone)
local ref=routines.utils.makeVec3(vars.ref,0)
local alt=vars.alt
local metric=vars.metric
if zone then
local vec={x=zone.point.x-ref.x,y=zone.point.y-ref.y,z=zone.point.z-ref.z}
local dir=routines.utils.getDir(vec,ref)
local dist=routines.utils.get2DDist(zone.point,ref)
if alt then
alt=zone.y
end
return routines.tostringBR(dir,dist,alt,metric)
else
env.info('routines.getBRStringZone: error: zone is nil')
end
end
routines.getBRString=function(vars)
local units=vars.units
local ref=routines.utils.makeVec3(vars.ref,0)
local alt=vars.alt
local metric=vars.metric
local avgPos=routines.getAvgPos(units)
if avgPos then
local vec={x=avgPos.x-ref.x,y=avgPos.y-ref.y,z=avgPos.z-ref.z}
local dir=routines.utils.getDir(vec,ref)
local dist=routines.utils.get2DDist(avgPos,ref)
if alt then
alt=avgPos.y
end
return routines.tostringBR(dir,dist,alt,metric)
end
end
routines.getLeadingPos=function(vars)
local units=vars.units
local heading=vars.heading
local radius=vars.radius
if vars.headingDegrees then
heading=routines.utils.toRadian(vars.headingDegrees)
end
local unitPosTbl={}
for i=1,#units do
local unit=Unit.getByName(units[i])
if unit and unit:isExist()then
unitPosTbl[#unitPosTbl+1]=unit:getPosition().p
end
end
if#unitPosTbl>0 then
local maxPos=-math.huge
local maxPosInd
for i=1,#unitPosTbl do
local rotatedVec2=routines.vec.rotateVec2(routines.utils.makeVec2(unitPosTbl[i]),heading)
if(not maxPos)or maxPos<rotatedVec2.x then
maxPos=rotatedVec2.x
maxPosInd=i
end
end
local avgPos
if radius then
local maxUnitPos=unitPosTbl[maxPosInd]
local avgx,avgy,avgz,totNum=0,0,0,0
for i=1,#unitPosTbl do
if routines.utils.get2DDist(maxUnitPos,unitPosTbl[i])<=radius then
avgx=avgx+unitPosTbl[i].x
avgy=avgy+unitPosTbl[i].y
avgz=avgz+unitPosTbl[i].z
totNum=totNum+1
end
end
avgPos={x=avgx/totNum,y=avgy/totNum,z=avgz/totNum}
else
avgPos=unitPosTbl[maxPosInd]
end
return avgPos
end
end
routines.getLeadingMGRSString=function(vars)
local pos=routines.getLeadingPos(vars)
if pos then
local acc=vars.acc or 5
return routines.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(pos)),acc)
end
end
routines.getLeadingLLString=function(vars)
local pos=routines.getLeadingPos(vars)
if pos then
local acc=vars.acc or 3
local DMS=vars.DMS
local lat,lon=coord.LOtoLL(pos)
return routines.tostringLL(lat,lon,acc,DMS)
end
end
routines.getLeadingBRString=function(vars)
local pos=routines.getLeadingPos(vars)
if pos then
local ref=vars.ref
local alt=vars.alt
local metric=vars.metric
local vec={x=pos.x-ref.x,y=pos.y-ref.y,z=pos.z-ref.z}
local dir=routines.utils.getDir(vec,ref)
local dist=routines.utils.get2DDist(pos,ref)
if alt then
alt=pos.y
end
return routines.tostringBR(dir,dist,alt,metric)
end
end
routines.msgMGRS=function(vars)
local units=vars.units
local acc=vars.acc
local text=vars.text
local displayTime=vars.displayTime
local msgFor=vars.msgFor
local s=routines.getMGRSString{units=units,acc=acc}
local newText
if string.find(text,'%%s')then
newText=string.format(text,s)
else
newText=text..s
end
routines.message.add{text=newText,displayTime=displayTime,msgFor=msgFor}
end
routines.msgLL=function(vars)
local units=vars.units
local acc=vars.acc
local DMS=vars.DMS
local text=vars.text
local displayTime=vars.displayTime
local msgFor=vars.msgFor
local s=routines.getLLString{units=units,acc=acc,DMS=DMS}
local newText
if string.find(text,'%%s')then
newText=string.format(text,s)
else
newText=text..s
end
routines.message.add{text=newText,displayTime=displayTime,msgFor=msgFor}
end
routines.msgBR=function(vars)
local units=vars.units
local ref=vars.ref
local alt=vars.alt
local metric=vars.metric
local text=vars.text
local displayTime=vars.displayTime
local msgFor=vars.msgFor
local s=routines.getBRString{units=units,ref=ref,alt=alt,metric=metric}
local newText
if string.find(text,'%%s')then
newText=string.format(text,s)
else
newText=text..s
end
routines.message.add{text=newText,displayTime=displayTime,msgFor=msgFor}
end
routines.msgBullseye=function(vars)
if string.lower(vars.ref)=='red'then
vars.ref=routines.DBs.missionData.bullseye.red
routines.msgBR(vars)
elseif string.lower(vars.ref)=='blue'then
vars.ref=routines.DBs.missionData.bullseye.blue
routines.msgBR(vars)
end
end
routines.msgBRA=function(vars)
if Unit.getByName(vars.ref)then
vars.ref=Unit.getByName(vars.ref):getPosition().p
if not vars.alt then
vars.alt=true
end
routines.msgBR(vars)
end
end
routines.msgLeadingMGRS=function(vars)
local units=vars.units
local heading=vars.heading
local radius=vars.radius
local headingDegrees=vars.headingDegrees
local acc=vars.acc
local text=vars.text
local displayTime=vars.displayTime
local msgFor=vars.msgFor
local s=routines.getLeadingMGRSString{units=units,heading=heading,radius=radius,headingDegrees=headingDegrees,acc=acc}
local newText
if string.find(text,'%%s')then
newText=string.format(text,s)
else
newText=text..s
end
routines.message.add{text=newText,displayTime=displayTime,msgFor=msgFor}
end
routines.msgLeadingLL=function(vars)
local units=vars.units
local heading=vars.heading
local radius=vars.radius
local headingDegrees=vars.headingDegrees
local acc=vars.acc
local DMS=vars.DMS
local text=vars.text
local displayTime=vars.displayTime
local msgFor=vars.msgFor
local s=routines.getLeadingLLString{units=units,heading=heading,radius=radius,headingDegrees=headingDegrees,acc=acc,DMS=DMS}
local newText
if string.find(text,'%%s')then
newText=string.format(text,s)
else
newText=text..s
end
routines.message.add{text=newText,displayTime=displayTime,msgFor=msgFor}
end
routines.msgLeadingBR=function(vars)
local units=vars.units
local heading=vars.heading
local radius=vars.radius
local headingDegrees=vars.headingDegrees
local metric=vars.metric
local alt=vars.alt
local ref=vars.ref
local text=vars.text
local displayTime=vars.displayTime
local msgFor=vars.msgFor
local s=routines.getLeadingBRString{units=units,heading=heading,radius=radius,headingDegrees=headingDegrees,metric=metric,alt=alt,ref=ref}
local newText
if string.find(text,'%%s')then
newText=string.format(text,s)
else
newText=text..s
end
routines.message.add{text=newText,displayTime=displayTime,msgFor=msgFor}
end
function spairs(t,order)
local keys={}
for k in pairs(t)do
keys[#keys+1]=k
end
if order then
table.sort(keys,function(a,b)
return order(t,a,b)
end)
else
table.sort(keys)
end
local i=0
return function()
i=i+1
if keys[i]then
return keys[i],t[keys[i]]
end
end
end
function routines.IsPartOfGroupInZones(CargoGroup,LandingZones)
local CurrentZoneID=nil
if CargoGroup then
local CargoUnits=CargoGroup:getUnits()
for CargoUnitID,CargoUnit in pairs(CargoUnits)do
if CargoUnit and CargoUnit:getLife()>=1.0 then
CurrentZoneID=routines.IsUnitInZones(CargoUnit,LandingZones)
if CurrentZoneID then
break
end
end
end
end
return CurrentZoneID
end
function routines.IsUnitInZones(TransportUnit,LandingZones)
local TransportZoneResult=nil
local TransportZonePos=nil
local TransportZone=nil
if TransportUnit then
local TransportUnitPos=TransportUnit:getPosition().p
if type(LandingZones)=="table"then
for LandingZoneID,LandingZoneName in pairs(LandingZones)do
TransportZone=trigger.misc.getZone(LandingZoneName)
if TransportZone then
TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z}
if(((TransportUnitPos.x-TransportZonePos.x)^2+(TransportUnitPos.z-TransportZonePos.z)^2)^0.5<=TransportZonePos.radius)then
TransportZoneResult=LandingZoneID
break
end
end
end
else
TransportZone=trigger.misc.getZone(LandingZones)
TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z}
if(((TransportUnitPos.x-TransportZonePos.x)^2+(TransportUnitPos.z-TransportZonePos.z)^2)^0.5<=TransportZonePos.radius)then
TransportZoneResult=1
end
end
if TransportZoneResult then
else
end
return TransportZoneResult
else
return nil
end
end
function routines.IsUnitNearZonesRadius(TransportUnit,LandingZones,ZoneRadius)
local TransportZoneResult=nil
local TransportZonePos=nil
local TransportZone=nil
if TransportUnit then
local TransportUnitPos=TransportUnit:getPosition().p
if type(LandingZones)=="table"then
for LandingZoneID,LandingZoneName in pairs(LandingZones)do
TransportZone=trigger.misc.getZone(LandingZoneName)
if TransportZone then
TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z}
if(((TransportUnitPos.x-TransportZonePos.x)^2+(TransportUnitPos.z-TransportZonePos.z)^2)^0.5<=ZoneRadius)then
TransportZoneResult=LandingZoneID
break
end
end
end
else
TransportZone=trigger.misc.getZone(LandingZones)
TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z}
if(((TransportUnitPos.x-TransportZonePos.x)^2+(TransportUnitPos.z-TransportZonePos.z)^2)^0.5<=ZoneRadius)then
TransportZoneResult=1
end
end
if TransportZoneResult then
else
end
return TransportZoneResult
else
return nil
end
end
function routines.IsStaticInZones(TransportStatic,LandingZones)
local TransportZoneResult=nil
local TransportZonePos=nil
local TransportZone=nil
local TransportStaticPos=TransportStatic:getPosition().p
if type(LandingZones)=="table"then
for LandingZoneID,LandingZoneName in pairs(LandingZones)do
TransportZone=trigger.misc.getZone(LandingZoneName)
if TransportZone then
TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z}
if(((TransportStaticPos.x-TransportZonePos.x)^2+(TransportStaticPos.z-TransportZonePos.z)^2)^0.5<=TransportZonePos.radius)then
TransportZoneResult=LandingZoneID
break
end
end
end
else
TransportZone=trigger.misc.getZone(LandingZones)
TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z}
if(((TransportStaticPos.x-TransportZonePos.x)^2+(TransportStaticPos.z-TransportZonePos.z)^2)^0.5<=TransportZonePos.radius)then
TransportZoneResult=1
end
end
return TransportZoneResult
end
function routines.IsUnitInRadius(CargoUnit,ReferencePosition,Radius)
local Valid=true
local CargoPos=CargoUnit:getPosition().p
local ReferenceP=ReferencePosition.p
if(((CargoPos.x-ReferenceP.x)^2+(CargoPos.z-ReferenceP.z)^2)^0.5<=Radius)then
else
Valid=false
end
return Valid
end
function routines.IsPartOfGroupInRadius(CargoGroup,ReferencePosition,Radius)
local Valid=true
Valid=routines.ValidateGroup(CargoGroup,"CargoGroup",Valid)
local CargoUnits=CargoGroup:getUnits()
for CargoUnitId,CargoUnit in pairs(CargoUnits)do
local CargoUnitPos=CargoUnit:getPosition().p
local ReferenceP=ReferencePosition.p
if(((CargoUnitPos.x-ReferenceP.x)^2+(CargoUnitPos.z-ReferenceP.z)^2)^0.5<=Radius)then
else
Valid=false
break
end
end
return Valid
end
function routines.ValidateString(Variable,VariableName,Valid)
if type(Variable)=="string"then
if Variable==""then
error("routines.ValidateString: error: "..VariableName.." must be filled out!")
Valid=false
end
else
error("routines.ValidateString: error: "..VariableName.." is not a string.")
Valid=false
end
return Valid
end
function routines.ValidateNumber(Variable,VariableName,Valid)
if type(Variable)=="number"then
else
error("routines.ValidateNumber: error: "..VariableName.." is not a number.")
Valid=false
end
return Valid
end
function routines.ValidateGroup(Variable,VariableName,Valid)
if Variable==nil then
error("routines.ValidateGroup: error: "..VariableName.." is a nil value!")
Valid=false
end
return Valid
end
function routines.ValidateZone(LandingZones,VariableName,Valid)
if LandingZones==nil then
error("routines.ValidateGroup: error: "..VariableName.." is a nil value!")
Valid=false
end
if type(LandingZones)=="table"then
for LandingZoneID,LandingZoneName in pairs(LandingZones)do
if trigger.misc.getZone(LandingZoneName)==nil then
error("routines.ValidateGroup: error: Zone "..LandingZoneName.." does not exist!")
Valid=false
break
end
end
else
if trigger.misc.getZone(LandingZones)==nil then
error("routines.ValidateGroup: error: Zone "..LandingZones.." does not exist!")
Valid=false
end
end
return Valid
end
function routines.ValidateEnumeration(Variable,VariableName,Enum,Valid)
local ValidVariable=false
for EnumId,EnumData in pairs(Enum)do
if Variable==EnumData then
ValidVariable=true
break
end
end
if ValidVariable then
else
error('TransportValidateEnum: " .. VariableName .. " is not a valid type.'..Variable)
Valid=false
end
return Valid
end
function routines.getGroupRoute(groupIdent,task)
local gpId=groupIdent
if type(groupIdent)=='string'and not tonumber(groupIdent)then
gpId=_DATABASE.Templates.Groups[groupIdent].groupId
end
for coa_name,coa_data in pairs(env.mission.coalition)do
if(coa_name=='red'or coa_name=='blue')and type(coa_data)=='table'then
if coa_data.country then
for cntry_id,cntry_data in pairs(coa_data.country)do
for obj_type_name,obj_type_data in pairs(cntry_data)do
if obj_type_name=="helicopter"or obj_type_name=="ship"or obj_type_name=="plane"or obj_type_name=="vehicle"then
if((type(obj_type_data)=='table')and obj_type_data.group and(type(obj_type_data.group)=='table')and(#obj_type_data.group>0))then
for group_num,group_data in pairs(obj_type_data.group)do
if group_data and group_data.groupId==gpId then
if group_data.route and group_data.route.points and#group_data.route.points>0 then
local points={}
for point_num,point in pairs(group_data.route.points)do
local routeData={}
if env.mission.version>7 then
routeData.name=env.getValueDictByKey(point.name)
else
routeData.name=point.name
end
if not point.point then
routeData.x=point.x
routeData.y=point.y
else
routeData.point=point.point
end
routeData.form=point.action
routeData.speed=point.speed
routeData.alt=point.alt
routeData.alt_type=point.alt_type
routeData.airdromeId=point.airdromeId
routeData.helipadId=point.helipadId
routeData.type=point.type
routeData.action=point.action
if task then
routeData.task=point.task
end
points[point_num]=routeData
end
return points
end
return
end
end
end
end
end
end
end
end
end
end
routines.ground.patrolRoute=function(vars)
local tempRoute={}
local useRoute={}
local gpData=vars.gpData
if type(gpData)=='string'then
gpData=Group.getByName(gpData)
end
local useGroupRoute
if not vars.useGroupRoute then
useGroupRoute=vars.gpData
else
useGroupRoute=vars.useGroupRoute
end
local routeProvided=false
if not vars.route then
if useGroupRoute then
tempRoute=routines.getGroupRoute(useGroupRoute)
end
else
useRoute=vars.route
local posStart=routines.getLeadPos(gpData)
useRoute[1]=routines.ground.buildWP(posStart,useRoute[1].action,useRoute[1].speed)
routeProvided=true
end
local overRideSpeed=vars.speed or'default'
local pType=vars.pType
local offRoadForm=vars.offRoadForm or'default'
local onRoadForm=vars.onRoadForm or'default'
if routeProvided==false and#tempRoute>0 then
local posStart=routines.getLeadPos(gpData)
useRoute[#useRoute+1]=routines.ground.buildWP(posStart,offRoadForm,overRideSpeed)
for i=1,#tempRoute do
local tempForm=tempRoute[i].action
local tempSpeed=tempRoute[i].speed
if offRoadForm=='default'then
tempForm=tempRoute[i].action
end
if onRoadForm=='default'then
onRoadForm='On Road'
end
if(string.lower(tempRoute[i].action)=='on road'or string.lower(tempRoute[i].action)=='onroad'or string.lower(tempRoute[i].action)=='on_road')then
tempForm=onRoadForm
else
tempForm=offRoadForm
end
if type(overRideSpeed)=='number'then
tempSpeed=overRideSpeed
end
useRoute[#useRoute+1]=routines.ground.buildWP(tempRoute[i],tempForm,tempSpeed)
end
if pType and string.lower(pType)=='doubleback'then
local curRoute=routines.utils.deepCopy(useRoute)
for i=#curRoute,2,-1 do
useRoute[#useRoute+1]=routines.ground.buildWP(curRoute[i],curRoute[i].action,curRoute[i].speed)
end
end
useRoute[1].action=useRoute[#useRoute].action
end
local cTask3={}
local newPatrol={}
newPatrol.route=useRoute
newPatrol.gpData=gpData:getName()
cTask3[#cTask3+1]='routines.ground.patrolRoute('
cTask3[#cTask3+1]=routines.utils.oneLineSerialize(newPatrol)
cTask3[#cTask3+1]=')'
cTask3=table.concat(cTask3)
local tempTask={id='WrappedAction',params={action={id='Script',params={command=cTask3}}}}
useRoute[#useRoute].task=tempTask
routines.goRoute(gpData,useRoute)
end
routines.ground.patrol=function(gpData,pType,form,speed)
local vars={}
if type(gpData)=='table'and gpData:getName()then
gpData=gpData:getName()
end
vars.useGroupRoute=gpData
vars.gpData=gpData
vars.pType=pType
vars.offRoadForm=form
vars.speed=speed
routines.ground.patrolRoute(vars)
end
function routines.GetUnitHeight(CheckUnit)
local UnitPoint=CheckUnit:getPoint()
local UnitPosition={x=UnitPoint.x,y=UnitPoint.z}
local UnitHeight=UnitPoint.y
local LandHeight=land.getHeight(UnitPosition)
return UnitHeight-LandHeight
end
Su34Status={status={}}
boardMsgRed={statusMsg=""}
boardMsgAll={timeMsg=""}
SpawnSettings={}
Su34MenuPath={}
Su34Menus=0
function Su34AttackCarlVinson(groupName)
local groupSu34=Group.getByName(groupName)
local controllerSu34=groupSu34.getController(groupSu34)
local groupCarlVinson=Group.getByName("US Carl Vinson #001")
controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE)
controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
if groupCarlVinson~=nil then
controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupCarlVinson:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=true}})
end
Su34Status.status[groupName]=1
MessageToRed(string.format('%s: ',groupName)..'Attacking carrier Carl Vinson. ',10,'RedStatus'..groupName)
end
function Su34AttackWest(groupName)
local groupSu34=Group.getByName(groupName)
local controllerSu34=groupSu34.getController(groupSu34)
local groupShipWest1=Group.getByName("US Ship West #001")
local groupShipWest2=Group.getByName("US Ship West #002")
controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE)
controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
if groupShipWest1~=nil then
controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipWest1:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=true}})
end
if groupShipWest2~=nil then
controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipWest2:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=true}})
end
Su34Status.status[groupName]=2
MessageToRed(string.format('%s: ',groupName)..'Attacking invading ships in the west. ',10,'RedStatus'..groupName)
end
function Su34AttackNorth(groupName)
local groupSu34=Group.getByName(groupName)
local controllerSu34=groupSu34.getController(groupSu34)
local groupShipNorth1=Group.getByName("US Ship North #001")
local groupShipNorth2=Group.getByName("US Ship North #002")
local groupShipNorth3=Group.getByName("US Ship North #003")
controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE)
controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
if groupShipNorth1~=nil then
controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipNorth1:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=false}})
end
if groupShipNorth2~=nil then
controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipNorth2:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=false}})
end
if groupShipNorth3~=nil then
controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipNorth3:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=false}})
end
Su34Status.status[groupName]=3
MessageToRed(string.format('%s: ',groupName)..'Attacking invading ships in the north. ',10,'RedStatus'..groupName)
end
function Su34Orbit(groupName)
local groupSu34=Group.getByName(groupName)
local controllerSu34=groupSu34:getController()
controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD)
controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
controllerSu34:pushTask({id='ControlledTask',params={task={id='Orbit',params={pattern=AI.Task.OrbitPattern.RACE_TRACK}},stopCondition={duration=600}}})
Su34Status.status[groupName]=4
MessageToRed(string.format('%s: ',groupName)..'In orbit and awaiting further instructions. ',10,'RedStatus'..groupName)
end
function Su34TakeOff(groupName)
local groupSu34=Group.getByName(groupName)
local controllerSu34=groupSu34:getController()
controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD)
controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE)
Su34Status.status[groupName]=8
MessageToRed(string.format('%s: ',groupName)..'Take-Off. ',10,'RedStatus'..groupName)
end
function Su34Hold(groupName)
local groupSu34=Group.getByName(groupName)
local controllerSu34=groupSu34:getController()
controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD)
controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE)
Su34Status.status[groupName]=5
MessageToRed(string.format('%s: ',groupName)..'Holding Weapons. ',10,'RedStatus'..groupName)
end
function Su34RTB(groupName)
Su34Status.status[groupName]=6
MessageToRed(string.format('%s: ',groupName)..'Return to Krasnodar. ',10,'RedStatus'..groupName)
end
function Su34Destroyed(groupName)
Su34Status.status[groupName]=7
MessageToRed(string.format('%s: ',groupName)..'Destroyed. ',30,'RedStatus'..groupName)
end
function GroupAlive(groupName)
local groupTest=Group.getByName(groupName)
local groupExists=false
if groupTest then
groupExists=groupTest:isExist()
end
return groupExists
end
function Su34IsDead()
end
function Su34OverviewStatus()
local msg=""
local currentStatus=0
local Exists=false
for groupName,currentStatus in pairs(Su34Status.status)do
env.info(('Su34 Overview Status: GroupName = '..groupName))
Alive=GroupAlive(groupName)
if Alive then
if currentStatus==1 then
msg=msg..string.format("%s: ",groupName)
msg=msg.."Attacking carrier Carl Vinson. "
elseif currentStatus==2 then
msg=msg..string.format("%s: ",groupName)
msg=msg.."Attacking supporting ships in the west. "
elseif currentStatus==3 then
msg=msg..string.format("%s: ",groupName)
msg=msg.."Attacking invading ships in the north. "
elseif currentStatus==4 then
msg=msg..string.format("%s: ",groupName)
msg=msg.."In orbit and awaiting further instructions. "
elseif currentStatus==5 then
msg=msg..string.format("%s: ",groupName)
msg=msg.."Holding Weapons. "
elseif currentStatus==6 then
msg=msg..string.format("%s: ",groupName)
msg=msg.."Return to Krasnodar. "
elseif currentStatus==7 then
msg=msg..string.format("%s: ",groupName)
msg=msg.."Destroyed. "
elseif currentStatus==8 then
msg=msg..string.format("%s: ",groupName)
msg=msg.."Take-Off. "
end
else
if currentStatus==7 then
msg=msg..string.format("%s: ",groupName)
msg=msg.."Destroyed. "
else
Su34Destroyed(groupName)
end
end
end
boardMsgRed.statusMsg=msg
end
function UpdateBoardMsg()
Su34OverviewStatus()
MessageToRed(boardMsgRed.statusMsg,15,'RedStatus')
end
function MusicReset(flg)
trigger.action.setUserFlag(95,flg)
end
function PlaneActivate(groupNameFormat,flg)
local groupName=groupNameFormat..string.format("#%03d",trigger.misc.getUserFlag(flg))
trigger.action.activateGroup(Group.getByName(groupName))
end
function Su34Menu(groupName)
local groupSu34=Group.getByName(groupName)
if Su34Status.status[groupName]==1 or Su34Status.status[groupName]==2 or Su34Status.status[groupName]==3 or Su34Status.status[groupName]==4 or Su34Status.status[groupName]==5 then
if Su34MenuPath[groupName]==nil then
if planeMenuPath==nil then
planeMenuPath=missionCommands.addSubMenuForCoalition(coalition.side.RED,"SU-34 anti-ship flights",nil)
end
Su34MenuPath[groupName]=missionCommands.addSubMenuForCoalition(coalition.side.RED,"Flight "..groupName,planeMenuPath)
missionCommands.addCommandForCoalition(coalition.side.RED,"Attack carrier Carl Vinson",Su34MenuPath[groupName],Su34AttackCarlVinson,groupName)
missionCommands.addCommandForCoalition(coalition.side.RED,"Attack ships in the west",Su34MenuPath[groupName],Su34AttackWest,groupName)
missionCommands.addCommandForCoalition(coalition.side.RED,"Attack ships in the north",Su34MenuPath[groupName],Su34AttackNorth,groupName)
missionCommands.addCommandForCoalition(coalition.side.RED,"Hold position and await instructions",Su34MenuPath[groupName],Su34Orbit,groupName)
missionCommands.addCommandForCoalition(coalition.side.RED,"Report status",Su34MenuPath[groupName],Su34OverviewStatus)
end
else
if Su34MenuPath[groupName]then
missionCommands.removeItemForCoalition(coalition.side.RED,Su34MenuPath[groupName])
end
end
end
function ChooseInfantry(TeleportPrefixTable,TeleportMax)
TeleportPrefixTableCount=#TeleportPrefixTable
TeleportPrefixTableIndex=math.random(1,TeleportPrefixTableCount)
local TeleportFound=false
local TeleportLoop=true
local Index=TeleportPrefixTableIndex
local TeleportPrefix=''
while TeleportLoop do
TeleportPrefix=TeleportPrefixTable[Index]
if SpawnSettings[TeleportPrefix]then
if SpawnSettings[TeleportPrefix]['SpawnCount']-1<TeleportMax then
SpawnSettings[TeleportPrefix]['SpawnCount']=SpawnSettings[TeleportPrefix]['SpawnCount']+1
TeleportFound=true
else
TeleportFound=false
end
else
SpawnSettings[TeleportPrefix]={}
SpawnSettings[TeleportPrefix]['SpawnCount']=0
TeleportFound=true
end
if TeleportFound then
TeleportLoop=false
else
if Index<TeleportPrefixTableCount then
Index=Index+1
else
TeleportLoop=false
end
end
end
if TeleportFound==false then
TeleportLoop=true
Index=1
while TeleportLoop do
TeleportPrefix=TeleportPrefixTable[Index]
if SpawnSettings[TeleportPrefix]then
if SpawnSettings[TeleportPrefix]['SpawnCount']-1<TeleportMax then
SpawnSettings[TeleportPrefix]['SpawnCount']=SpawnSettings[TeleportPrefix]['SpawnCount']+1
TeleportFound=true
else
TeleportFound=false
end
else
SpawnSettings[TeleportPrefix]={}
SpawnSettings[TeleportPrefix]['SpawnCount']=0
TeleportFound=true
end
if TeleportFound then
TeleportLoop=false
else
if Index<TeleportPrefixTableIndex then
Index=Index+1
else
TeleportLoop=false
end
end
end
end
local TeleportGroupName=''
if TeleportFound==true then
TeleportGroupName=TeleportPrefix..string.format("#%03d",SpawnSettings[TeleportPrefix]['SpawnCount'])
else
TeleportGroupName=''
end
return TeleportGroupName
end
SpawnedInfantry=0
function LandCarrier(CarrierGroup,LandingZonePrefix)
local controllerGroup=CarrierGroup:getController()
local LandingZone=trigger.misc.getZone(LandingZonePrefix)
local LandingZonePos={}
LandingZonePos.x=LandingZone.point.x+math.random(LandingZone.radius*-1,LandingZone.radius)
LandingZonePos.y=LandingZone.point.z+math.random(LandingZone.radius*-1,LandingZone.radius)
controllerGroup:pushTask({id='Land',params={point=LandingZonePos,durationFlag=true,duration=10}})
end
EscortCount=0
function EscortCarrier(CarrierGroup,EscortPrefix,EscortLastWayPoint,EscortEngagementDistanceMax,EscortTargetTypes)
local CarrierName=CarrierGroup:getName()
local EscortMission={}
local CarrierMission={}
local EscortMission=SpawnMissionGroup(EscortPrefix)
local CarrierMission=SpawnMissionGroup(CarrierGroup:getName())
if EscortMission~=nil and CarrierMission~=nil then
EscortCount=EscortCount+1
EscortMissionName=string.format(EscortPrefix..'#Escort %s',CarrierName)
EscortMission.name=EscortMissionName
EscortMission.groupId=nil
EscortMission.lateActivation=false
EscortMission.taskSelected=false
local EscortUnits=#EscortMission.units
for u=1,EscortUnits do
EscortMission.units[u].name=string.format(EscortPrefix..'#Escort %s %02d',CarrierName,u)
EscortMission.units[u].unitId=nil
end
EscortMission.route.points[1].task={
id="ComboTask",
params={
tasks={
[1]={
enabled=true,
auto=false,
id="Escort",
number=1,
params={
lastWptIndexFlagChangedManually=false,
groupId=CarrierGroup:getID(),
lastWptIndex=nil,
lastWptIndexFlag=false,
engagementDistMax=EscortEngagementDistanceMax,
targetTypes=EscortTargetTypes,
pos={y=20,x=20,z=0}
}
}
}
}
}
SpawnGroupAdd(EscortPrefix,EscortMission)
end
end
function SendMessageToCarrier(CarrierGroup,CarrierMessage)
if CarrierGroup~=nil then
MessageToGroup(CarrierGroup,CarrierMessage,30,'Carrier/'..CarrierGroup:getName())
end
end
function MessageToGroup(MsgGroup,MsgText,MsgTime,MsgName)
if type(MsgGroup)=='string'then
MsgGroup=Group.getByName(MsgGroup)
end
if MsgGroup~=nil then
local MsgTable={}
MsgTable.text=MsgText
MsgTable.displayTime=MsgTime
MsgTable.msgFor={units={MsgGroup:getUnits()[1]:getName()}}
MsgTable.name=MsgName
end
end
function MessageToUnit(UnitName,MsgText,MsgTime,MsgName)
if UnitName~=nil then
local MsgTable={}
MsgTable.text=MsgText
MsgTable.displayTime=MsgTime
MsgTable.msgFor={units={UnitName}}
MsgTable.name=MsgName
end
end
function MessageToAll(MsgText,MsgTime,MsgName)
MESSAGE:New(MsgText,MsgTime,"Message"):ToCoalition(coalition.side.RED):ToCoalition(coalition.side.BLUE)
end
function MessageToRed(MsgText,MsgTime,MsgName)
MESSAGE:New(MsgText,MsgTime,"To Red Coalition"):ToCoalition(coalition.side.RED)
end
function MessageToBlue(MsgText,MsgTime,MsgName)
MESSAGE:New(MsgText,MsgTime,"To Blue Coalition"):ToCoalition(coalition.side.BLUE)
end
function getCarrierHeight(CarrierGroup)
if CarrierGroup~=nil then
if table.getn(CarrierGroup:getUnits())==1 then
local CarrierUnit=CarrierGroup:getUnits()[1]
local CurrentPoint=CarrierUnit:getPoint()
local CurrentPosition={x=CurrentPoint.x,y=CurrentPoint.z}
local CarrierHeight=CurrentPoint.y
local LandHeight=land.getHeight(CurrentPosition)
return CarrierHeight-LandHeight
else
return 999999
end
else
return 999999
end
end
function GetUnitHeight(CheckUnit)
local UnitPoint=CheckUnit:getPoint()
local UnitPosition={x=CurrentPoint.x,y=CurrentPoint.z}
local UnitHeight=CurrentPoint.y
local LandHeight=land.getHeight(CurrentPosition)
return UnitHeight-LandHeight
end
_MusicTable={}
_MusicTable.Files={}
_MusicTable.Queue={}
_MusicTable.FileCnt=0
function MusicRegister(SndRef,SndFile,SndTime)
env.info(('MusicRegister: SndRef = '..SndRef))
env.info(('MusicRegister: SndFile = '..SndFile))
env.info(('MusicRegister: SndTime = '..SndTime))
_MusicTable.FileCnt=_MusicTable.FileCnt+1
_MusicTable.Files[_MusicTable.FileCnt]={}
_MusicTable.Files[_MusicTable.FileCnt].Ref=SndRef
_MusicTable.Files[_MusicTable.FileCnt].File=SndFile
_MusicTable.Files[_MusicTable.FileCnt].Time=SndTime
if not _MusicTable.Function then
_MusicTable.Function=routines.scheduleFunction(MusicScheduler,{},timer.getTime()+10,10)
end
end
function MusicToPlayer(SndRef,PlayerName,SndContinue)
local PlayerUnits=AlivePlayerUnits()
for PlayerUnitIdx,PlayerUnit in pairs(PlayerUnits)do
local PlayerUnitName=PlayerUnit:getPlayerName()
if PlayerName==PlayerUnitName then
PlayerGroup=PlayerUnit:getGroup()
if PlayerGroup then
MusicToGroup(SndRef,PlayerGroup,SndContinue)
end
break
end
end
end
function MusicToGroup(SndRef,SndGroup,SndContinue)
if SndGroup~=nil then
if _MusicTable and _MusicTable.FileCnt>0 then
if SndGroup:isExist()then
if MusicCanStart(SndGroup:getUnit(1):getPlayerName())then
local SndIdx=0
if SndRef==''then
SndIdx=math.random(1,_MusicTable.FileCnt)
else
for SndIdx=1,_MusicTable.FileCnt do
if _MusicTable.Files[SndIdx].Ref==SndRef then
break
end
end
end
trigger.action.outSoundForGroup(SndGroup:getID(),_MusicTable.Files[SndIdx].File)
MessageToGroup(SndGroup,'Playing '.._MusicTable.Files[SndIdx].File,15,'Music-'..SndGroup:getUnit(1):getPlayerName())
local SndQueueRef=SndGroup:getUnit(1):getPlayerName()
if _MusicTable.Queue[SndQueueRef]==nil then
_MusicTable.Queue[SndQueueRef]={}
end
_MusicTable.Queue[SndQueueRef].Start=timer.getTime()
_MusicTable.Queue[SndQueueRef].PlayerName=SndGroup:getUnit(1):getPlayerName()
_MusicTable.Queue[SndQueueRef].Group=SndGroup
_MusicTable.Queue[SndQueueRef].ID=SndGroup:getID()
_MusicTable.Queue[SndQueueRef].Ref=SndIdx
_MusicTable.Queue[SndQueueRef].Continue=SndContinue
_MusicTable.Queue[SndQueueRef].Type=Group
end
end
end
end
end
function MusicCanStart(PlayerName)
local MusicOut=false
if _MusicTable['Queue']~=nil and _MusicTable.FileCnt>0 then
local PlayerFound=false
local MusicStart=0
local MusicTime=0
for SndQueueIdx,SndQueue in pairs(_MusicTable.Queue)do
if SndQueue.PlayerName==PlayerName then
PlayerFound=true
MusicStart=SndQueue.Start
MusicTime=_MusicTable.Files[SndQueue.Ref].Time
break
end
end
if PlayerFound then
if MusicStart+MusicTime<=timer.getTime()then
MusicOut=true
end
else
MusicOut=true
end
end
if MusicOut then
else
end
return MusicOut
end
function MusicScheduler()
if _MusicTable['Queue']~=nil and _MusicTable.FileCnt>0 then
for SndQueueIdx,SndQueue in pairs(_MusicTable.Queue)do
if SndQueue.Continue then
if MusicCanStart(SndQueue.PlayerName)then
MusicToPlayer('',SndQueue.PlayerName,true)
end
end
end
end
end
env.info(('Init: Scripts Loaded v1.1'))
SMOKECOLOR=trigger.smokeColor
FLARECOLOR=trigger.flareColor
BIGSMOKEPRESET={
SmallSmokeAndFire=1,
MediumSmokeAndFire=2,
LargeSmokeAndFire=3,
HugeSmokeAndFire=4,
SmallSmoke=5,
MediumSmoke=6,
LargeSmoke=7,
HugeSmoke=8,
}
DCSMAP={
Caucasus="Caucasus",
NTTR="Nevada",
Normandy="Normandy",
PersianGulf="PersianGulf",
TheChannel="TheChannel",
Syria="Syria",
MarianaIslands="MarianaIslands",
Falklands="Falklands",
}
CALLSIGN={
Aircraft={
Enfield=1,
Springfield=2,
Uzi=3,
Colt=4,
Dodge=5,
Ford=6,
Chevy=7,
Pontiac=8,
Hawg=9,
Boar=10,
Pig=11,
Tusk=12,
},
AWACS={
Overlord=1,
Magic=2,
Wizard=3,
Focus=4,
Darkstar=5,
},
Tanker={
Texaco=1,
Arco=2,
Shell=3,
},
JTAC={
Axeman=1,
Darknight=2,
Warrior=3,
Pointer=4,
Eyeball=5,
Moonbeam=6,
Whiplash=7,
Finger=8,
Pinpoint=9,
Ferret=10,
Shaba=11,
Playboy=12,
Hammer=13,
Jaguar=14,
Deathstar=15,
Anvil=16,
Firefly=17,
Mantis=18,
Badger=19,
},
FARP={
London=1,
Dallas=2,
Paris=3,
Moscow=4,
Berlin=5,
Rome=6,
Madrid=7,
Warsaw=8,
Dublin=9,
Perth=10,
},
F16={
Viper=9,
Venom=10,
Lobo=11,
Cowboy=12,
Python=13,
Rattler=14,
Panther=15,
Wolf=16,
Weasel=17,
Wild=18,
Ninja=19,
Jedi=20,
},
F18={
Hornet=9,
Squid=10,
Ragin=11,
Roman=12,
Sting=13,
Jury=14,
Jokey=15,
Ram=16,
Hawk=17,
Devil=18,
Check=19,
Snake=20,
},
F15E={
Dude=9,
Thud=10,
Gunny=11,
Trek=12,
Sniper=13,
Sled=14,
Best=15,
Jazz=16,
Rage=17,
Tahoe=18,
},
B1B={
Bone=9,
Dark=10,
Vader=11
},
B52={
Buff=9,
Dump=10,
Kenworth=11,
},
TransportAircraft={
Heavy=9,
Trash=10,
Cargo=11,
Ascot=12,
},
}
UTILS={
_MarkID=1
}
UTILS.IsInstanceOf=function(object,className)
if not type(className)=='string'then
if type(className)=='table'and className.IsInstanceOf~=nil then
className=className.ClassName
else
local err_str='className parameter should be a string; parameter received: '..type(className)
return false
end
end
if type(object)=='table'and object.IsInstanceOf~=nil then
return object:IsInstanceOf(className)
else
local basicDataTypes={'string','number','function','boolean','nil','table'}
for _,basicDataType in ipairs(basicDataTypes)do
if className==basicDataType then
return type(object)==basicDataType
end
end
end
return false
end
UTILS.DeepCopy=function(object)
local lookup_table={}
local function _copy(object)
if type(object)~="table"then
return object
elseif lookup_table[object]then
return lookup_table[object]
end
local new_table={}
lookup_table[object]=new_table
for index,value in pairs(object)do
new_table[_copy(index)]=_copy(value)
end
return setmetatable(new_table,getmetatable(object))
end
local objectreturn=_copy(object)
return objectreturn
end
UTILS.OneLineSerialize=function(tbl)
lookup_table={}
local function _Serialize(tbl)
if type(tbl)=='table'then
if lookup_table[tbl]then
return lookup_table[object]
end
local tbl_str={}
lookup_table[tbl]=tbl_str
tbl_str[#tbl_str+1]='{'
for ind,val in pairs(tbl)do
local ind_str={}
if type(ind)=="number"then
ind_str[#ind_str+1]='['
ind_str[#ind_str+1]=tostring(ind)
ind_str[#ind_str+1]=']='
else
ind_str[#ind_str+1]='['
ind_str[#ind_str+1]=routines.utils.basicSerialize(ind)
ind_str[#ind_str+1]=']='
end
local val_str={}
if((type(val)=='number')or(type(val)=='boolean'))then
val_str[#val_str+1]=tostring(val)
val_str[#val_str+1]=','
tbl_str[#tbl_str+1]=table.concat(ind_str)
tbl_str[#tbl_str+1]=table.concat(val_str)
elseif type(val)=='string'then
val_str[#val_str+1]=routines.utils.basicSerialize(val)
val_str[#val_str+1]=','
tbl_str[#tbl_str+1]=table.concat(ind_str)
tbl_str[#tbl_str+1]=table.concat(val_str)
elseif type(val)=='nil'then
val_str[#val_str+1]='nil,'
tbl_str[#tbl_str+1]=table.concat(ind_str)
tbl_str[#tbl_str+1]=table.concat(val_str)
elseif type(val)=='table'then
if ind=="__index"then
else
val_str[#val_str+1]=_Serialize(val)
val_str[#val_str+1]=','
tbl_str[#tbl_str+1]=table.concat(ind_str)
tbl_str[#tbl_str+1]=table.concat(val_str)
end
elseif type(val)=='function'then
tbl_str[#tbl_str+1]="f() "..tostring(ind)
tbl_str[#tbl_str+1]=','
else
env.info('unable to serialize value type '..routines.utils.basicSerialize(type(val))..' at index '..tostring(ind))
env.info(debug.traceback())
end
end
tbl_str[#tbl_str+1]='}'
return table.concat(tbl_str)
else
return tostring(tbl)
end
end
local objectreturn=_Serialize(tbl)
return objectreturn
end
UTILS.BasicSerialize=function(s)
if s==nil then
return"\"\""
else
if((type(s)=='number')or(type(s)=='boolean')or(type(s)=='function')or(type(s)=='table')or(type(s)=='userdata'))then
return tostring(s)
elseif type(s)=='string'then
s=string.format('%q',s)
return s
end
end
end
UTILS.ToDegree=function(angle)
return angle*180/math.pi
end
UTILS.ToRadian=function(angle)
return angle*math.pi/180
end
UTILS.MetersToNM=function(meters)
return meters/1852
end
UTILS.KiloMetersToNM=function(kilometers)
return kilometers/1852*1000
end
UTILS.MetersToSM=function(meters)
return meters/1609.34
end
UTILS.KiloMetersToSM=function(kilometers)
return kilometers/1609.34*1000
end
UTILS.MetersToFeet=function(meters)
return meters/0.3048
end
UTILS.KiloMetersToFeet=function(kilometers)
return kilometers/0.3048*1000
end
UTILS.NMToMeters=function(NM)
return NM*1852
end
UTILS.NMToKiloMeters=function(NM)
return NM*1852/1000
end
UTILS.FeetToMeters=function(feet)
return feet*0.3048
end
UTILS.KnotsToKmph=function(knots)
return knots*1.852
end
UTILS.KmphToKnots=function(knots)
return knots/1.852
end
UTILS.KmphToMps=function(kmph)
return kmph/3.6
end
UTILS.MpsToKmph=function(mps)
return mps*3.6
end
UTILS.MiphToMps=function(miph)
return miph*0.44704
end
UTILS.MpsToMiph=function(mps)
return mps/0.44704
end
UTILS.MpsToKnots=function(mps)
return mps*1.94384
end
UTILS.KnotsToMps=function(knots)
if type(knots)=="number"then
return knots/1.94384
else
return 0
end
end
UTILS.CelsiusToFahrenheit=function(Celcius)
return Celcius*9/5+32
end
UTILS.hPa2inHg=function(hPa)
return hPa*0.0295299830714
end
UTILS.KnotsToAltKIAS=function(knots,altitude)
return(knots*0.018*(altitude/1000))+knots
end
UTILS.hPa2mmHg=function(hPa)
return hPa*0.7500615613030
end
UTILS.kg2lbs=function(kg)
return kg*2.20462
end
UTILS.tostringLL=function(lat,lon,acc,DMS)
local latHemi,lonHemi
if lat>0 then
latHemi='N'
else
latHemi='S'
end
if lon>0 then
lonHemi='E'
else
lonHemi='W'
end
lat=math.abs(lat)
lon=math.abs(lon)
local latDeg=math.floor(lat)
local latMin=(lat-latDeg)*60
local lonDeg=math.floor(lon)
local lonMin=(lon-lonDeg)*60
if DMS then
local oldLatMin=latMin
latMin=math.floor(latMin)
local latSec=UTILS.Round((oldLatMin-latMin)*60,acc)
local oldLonMin=lonMin
lonMin=math.floor(lonMin)
local lonSec=UTILS.Round((oldLonMin-lonMin)*60,acc)
if latSec==60 then
latSec=0
latMin=latMin+1
end
if lonSec==60 then
lonSec=0
lonMin=lonMin+1
end
local secFrmtStr
secFrmtStr='%02d'
if acc<=0 then
secFrmtStr='%02d'
else
local width=3+acc
secFrmtStr='%0'..width..'.'..acc..'f'
end
return string.format('%03d°',latDeg)..string.format('%02d',latMin)..'\''..string.format(secFrmtStr,latSec)..'"'..latHemi..' '
..string.format('%03d°',lonDeg)..string.format('%02d',lonMin)..'\''..string.format(secFrmtStr,lonSec)..'"'..lonHemi
else
latMin=UTILS.Round(latMin,acc)
lonMin=UTILS.Round(lonMin,acc)
if latMin==60 then
latMin=0
latDeg=latDeg+1
end
if lonMin==60 then
lonMin=0
lonDeg=lonDeg+1
end
local minFrmtStr
if acc<=0 then
minFrmtStr='%02d'
else
local width=3+acc
minFrmtStr='%0'..width..'.'..acc..'f'
end
return string.format('%03d°',latDeg)..' '..string.format(minFrmtStr,latMin)..'\''..latHemi..'   '
..string.format('%03d°',lonDeg)..' '..string.format(minFrmtStr,lonMin)..'\''..lonHemi
end
end
UTILS.tostringMGRS=function(MGRS,acc)
if acc<=0 then
return MGRS.UTMZone..' '..MGRS.MGRSDigraph
else
if acc>5 then acc=5 end
local Easting=tostring(MGRS.Easting)
local Northing=tostring(MGRS.Northing)
local nE=5-string.len(Easting)
local nN=5-string.len(Northing)
for i=1,nE do Easting="0"..Easting end
for i=1,nN do Northing="0"..Northing end
return string.format("%s %s %s %s",MGRS.UTMZone,MGRS.MGRSDigraph,string.sub(Easting,1,acc),string.sub(Northing,1,acc))
end
end
function UTILS.Round(num,idp)
local mult=10^(idp or 0)
return math.floor(num*mult+0.5)/mult
end
function UTILS.DoString(s)
local f,err=loadstring(s)
if f then
return true,f()
else
return false,err
end
end
function UTILS.spairs(t,order)
local keys={}
for k in pairs(t)do keys[#keys+1]=k end
if order then
table.sort(keys,function(a,b)return order(t,a,b)end)
else
table.sort(keys)
end
local i=0
return function()
i=i+1
if keys[i]then
return keys[i],t[keys[i]]
end
end
end
function UTILS.kpairs(t,getkey,order)
local keys={}
local keyso={}
for k,o in pairs(t)do keys[#keys+1]=k keyso[#keyso+1]=getkey(o)end
if order then
table.sort(keys,function(a,b)return order(t,a,b)end)
else
table.sort(keys)
end
local i=0
return function()
i=i+1
if keys[i]then
return keyso[i],t[keys[i]]
end
end
end
function UTILS.rpairs(t)
local keys={}
for k in pairs(t)do keys[#keys+1]=k end
local random={}
local j=#keys
for i=1,j do
local k=math.random(1,#keys)
random[i]=keys[k]
table.remove(keys,k)
end
local i=0
return function()
i=i+1
if random[i]then
return random[i],t[random[i]]
end
end
end
function UTILS.GetMarkID()
UTILS._MarkID=UTILS._MarkID+1
return UTILS._MarkID
end
function UTILS.RemoveMark(MarkID,Delay)
if Delay and Delay>0 then
TIMER:New(UTILS.RemoveMark,MarkID):Start(Delay)
else
if MarkID then
trigger.action.removeMark(MarkID)
end
end
end
function UTILS.IsInRadius(InVec2,Vec2,Radius)
local InRadius=((InVec2.x-Vec2.x)^2+(InVec2.y-Vec2.y)^2)^0.5<=Radius
return InRadius
end
function UTILS.IsInSphere(InVec3,Vec3,Radius)
local InSphere=((InVec3.x-Vec3.x)^2+(InVec3.y-Vec3.y)^2+(InVec3.z-Vec3.z)^2)^0.5<=Radius
return InSphere
end
function UTILS.BeaufortScale(speed)
local bn=nil
local bd=nil
if speed<0.51 then
bn=0
bd="Calm"
elseif speed<2.06 then
bn=1
bd="Light Air"
elseif speed<3.60 then
bn=2
bd="Light Breeze"
elseif speed<5.66 then
bn=3
bd="Gentle Breeze"
elseif speed<8.23 then
bn=4
bd="Moderate Breeze"
elseif speed<11.32 then
bn=5
bd="Fresh Breeze"
elseif speed<14.40 then
bn=6
bd="Strong Breeze"
elseif speed<17.49 then
bn=7
bd="Moderate Gale"
elseif speed<21.09 then
bn=8
bd="Fresh Gale"
elseif speed<24.69 then
bn=9
bd="Strong Gale"
elseif speed<28.81 then
bn=10
bd="Storm"
elseif speed<32.92 then
bn=11
bd="Violent Storm"
else
bn=12
bd="Hurricane"
end
return bn,bd
end
function UTILS.Split(str,sep)
local result={}
local regex=("([^%s]+)"):format(sep)
for each in str:gmatch(regex)do
table.insert(result,each)
end
return result
end
function UTILS.GetCharacters(str)
local chars={}
for i=1,#str do
local c=str:sub(i,i)
table.insert(chars,c)
end
return chars
end
function UTILS.SecondsToClock(seconds,short)
if seconds==nil then
return nil
end
local seconds=tonumber(seconds)
local _seconds=seconds%(60*60*24)
if seconds<0 then
return nil
else
local hours=string.format("%02.f",math.floor(_seconds/3600))
local mins=string.format("%02.f",math.floor(_seconds/60-(hours*60)))
local secs=string.format("%02.f",math.floor(_seconds-hours*3600-mins*60))
local days=string.format("%d",seconds/(60*60*24))
local clock=hours..":"..mins..":"..secs.."+"..days
if short then
if hours=="00"then
clock=hours..":"..mins..":"..secs
else
clock=hours..":"..mins..":"..secs
end
end
return clock
end
end
function UTILS.SecondsOfToday()
local time=timer.getAbsTime()
local clock=UTILS.SecondsToClock(time,true)
return UTILS.ClockToSeconds(clock)
end
function UTILS.SecondsToMidnight()
return 24*60*60-UTILS.SecondsOfToday()
end
function UTILS.ClockToSeconds(clock)
if clock==nil then
return nil
end
local seconds=0
local dsplit=UTILS.Split(clock,"+")
if#dsplit>1 then
seconds=seconds+tonumber(dsplit[2])*60*60*24
end
local tsplit=UTILS.Split(dsplit[1],":")
local i=1
for _,time in ipairs(tsplit)do
if i==1 then
seconds=seconds+tonumber(time)*60*60
elseif i==2 then
seconds=seconds+tonumber(time)*60
elseif i==3 then
seconds=seconds+tonumber(time)
end
i=i+1
end
return seconds
end
function UTILS.DisplayMissionTime(duration)
duration=duration or 5
local Tnow=timer.getAbsTime()
local mission_time=Tnow-timer.getTime0()
local mission_time_minutes=mission_time/60
local mission_time_seconds=mission_time%60
local local_time=UTILS.SecondsToClock(Tnow)
local text=string.format("Time: %s - %02d:%02d",local_time,mission_time_minutes,mission_time_seconds)
MESSAGE:New(text,duration):ToAll()
end
function UTILS.ReplaceIllegalCharacters(Text,ReplaceBy)
ReplaceBy=ReplaceBy or"_"
local text=Text:gsub("[<>|/?*:\\]",ReplaceBy)
return text
end
function UTILS.RandomGaussian(x0,sigma,xmin,xmax,imax)
sigma=sigma or 10
imax=imax or 100
local r
local gotit=false
local i=0
while not gotit do
local x1=math.random()
local x2=math.random()
r=math.sqrt(-2*sigma*sigma*math.log(x1))*math.cos(2*math.pi*x2)+x0
i=i+1
if(r>=xmin and r<=xmax)or i>imax then
gotit=true
end
end
return r
end
function UTILS.Randomize(value,fac,lower,upper)
local min
if lower then
min=math.max(value-value*fac,lower)
else
min=value-value*fac
end
local max
if upper then
max=math.min(value+value*fac,upper)
else
max=value+value*fac
end
local r=math.random(min,max)
return r
end
function UTILS.VecDot(a,b)
return a.x*b.x+a.y*b.y+a.z*b.z
end
function UTILS.Vec2Dot(a,b)
return a.x*b.x+a.y*b.y
end
function UTILS.VecNorm(a)
return math.sqrt(UTILS.VecDot(a,a))
end
function UTILS.Vec2Norm(a)
return math.sqrt(UTILS.Vec2Dot(a,a))
end
function UTILS.VecDist2D(a,b)
local d=math.huge
if(not a)or(not b)then return d end
local c={x=b.x-a.x,y=b.y-a.y}
d=math.sqrt(c.x*c.x+c.y*c.y)
return d
end
function UTILS.VecDist3D(a,b)
local d=math.huge
if(not a)or(not b)then return d end
local c={x=b.x-a.x,y=b.y-a.y,z=b.z-a.z}
d=math.sqrt(UTILS.VecDot(c,c))
return d
end
function UTILS.VecCross(a,b)
return{x=a.y*b.z-a.z*b.y,y=a.z*b.x-a.x*b.z,z=a.x*b.y-a.y*b.x}
end
function UTILS.VecSubstract(a,b)
return{x=a.x-b.x,y=a.y-b.y,z=a.z-b.z}
end
function UTILS.Vec2Substract(a,b)
return{x=a.x-b.x,y=a.y-b.y}
end
function UTILS.VecAdd(a,b)
return{x=a.x+b.x,y=a.y+b.y,z=a.z+b.z}
end
function UTILS.Vec2Add(a,b)
return{x=a.x+b.x,y=a.y+b.y}
end
function UTILS.VecAngle(a,b)
local cosalpha=UTILS.VecDot(a,b)/(UTILS.VecNorm(a)*UTILS.VecNorm(b))
local alpha=0
if cosalpha>=0.9999999999 then
alpha=0
elseif cosalpha<=-0.999999999 then
alpha=math.pi
else
alpha=math.acos(cosalpha)
end
return math.deg(alpha)
end
function UTILS.VecHdg(a)
local h=math.deg(math.atan2(a.z,a.x))
if h<0 then
h=h+360
end
return h
end
function UTILS.Vec2Hdg(a)
local h=math.deg(math.atan2(a.y,a.x))
if h<0 then
h=h+360
end
return h
end
function UTILS.HdgDiff(h1,h2)
local alpha=math.rad(tonumber(h1))
local beta=math.rad(tonumber(h2))
local v1={x=math.cos(alpha),y=0,z=math.sin(alpha)}
local v2={x=math.cos(beta),y=0,z=math.sin(beta)}
local delta=UTILS.VecAngle(v1,v2)
return math.abs(delta)
end
function UTILS.VecTranslate(a,distance,angle)
local SX=a.x
local SY=a.z
local Radians=math.rad(angle or 0)
local TX=distance*math.cos(Radians)+SX
local TY=distance*math.sin(Radians)+SY
return{x=TX,y=a.y,z=TY}
end
function UTILS.Vec2Translate(a,distance,angle)
local SX=a.x
local SY=a.y
local Radians=math.rad(angle or 0)
local TX=distance*math.cos(Radians)+SX
local TY=distance*math.sin(Radians)+SY
return{x=TX,y=TY}
end
function UTILS.Rotate2D(a,angle)
local phi=math.rad(angle)
local x=a.z
local y=a.x
local Z=x*math.cos(phi)-y*math.sin(phi)
local X=x*math.sin(phi)+y*math.cos(phi)
local Y=a.y
local A={x=X,y=Y,z=Z}
return A
end
function UTILS.Vec2Rotate2D(a,angle)
local phi=math.rad(angle)
local x=a.x
local y=a.y
local X=x*math.cos(phi)-y*math.sin(phi)
local Y=x*math.sin(phi)+y*math.cos(phi)
local A={x=X,y=Y}
return A
end
function UTILS.TACANToFrequency(TACANChannel,TACANMode)
if type(TACANChannel)~="number"then
return nil
end
if TACANMode~="X"and TACANMode~="Y"then
return nil
end
local A=1151
local B=64
if TACANChannel<64 then
B=1
end
if TACANMode=='Y'then
A=1025
if TACANChannel<64 then
A=1088
end
else
if TACANChannel<64 then
A=962
end
end
return(A+TACANChannel-B)*1000000
end
function UTILS.GetDCSMap()
return env.mission.theatre
end
function UTILS.GetDCSMissionDate()
local year=tostring(env.mission.date.Year)
local month=tostring(env.mission.date.Month)
local day=tostring(env.mission.date.Day)
return string.format("%s/%s/%s",year,month,day),tonumber(year),tonumber(month),tonumber(day)
end
function UTILS.GetMissionDay(Time)
Time=Time or timer.getAbsTime()
local clock=UTILS.SecondsToClock(Time,false)
local x=tonumber(UTILS.Split(clock,"+")[2])
return x
end
function UTILS.GetMissionDayOfYear(Time)
local Date,Year,Month,Day=UTILS.GetDCSMissionDate()
local d=UTILS.GetMissionDay(Time)
return UTILS.GetDayOfYear(Year,Month,Day)+d
end
function UTILS.GetMagneticDeclination(map)
map=map or UTILS.GetDCSMap()
local declination=0
if map==DCSMAP.Caucasus then
declination=6
elseif map==DCSMAP.NTTR then
declination=12
elseif map==DCSMAP.Normandy then
declination=-10
elseif map==DCSMAP.PersianGulf then
declination=2
elseif map==DCSMAP.TheChannel then
declination=-10
elseif map==DCSMAP.Syria then
declination=5
elseif map==DCSMAP.MarianaIslands then
declination=2
elseif map==DCSMAP.Falklands then
declination=12
else
declination=0
end
return declination
end
function UTILS.FileExists(file)
if io then
local f=io.open(file,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
else
return nil
end
end
function UTILS.CheckMemory(output)
local time=timer.getTime()
local clock=UTILS.SecondsToClock(time)
local mem=collectgarbage("count")
if output then
env.info(string.format("T=%s  Memory usage %d kByte = %.2f MByte",clock,mem,mem/1024))
end
return mem
end
function UTILS.GetCoalitionName(Coalition)
if Coalition then
if Coalition==coalition.side.BLUE then
return"Blue"
elseif Coalition==coalition.side.RED then
return"Red"
elseif Coalition==coalition.side.NEUTRAL then
return"Neutral"
else
return"Unknown"
end
else
return"Unknown"
end
end
function UTILS.GetCoalitionEnemy(Coalition,Neutral)
local Coalitions={}
if Coalition then
if Coalition==coalition.side.RED then
Coalitions={coalition.side.BLUE}
elseif Coalition==coalition.side.BLUE then
Coalitions={coalition.side.RED}
elseif Coalition==coalition.side.NEUTRAL then
Coalitions={coalition.side.RED,coalition.side.BLUE}
end
end
if Neutral then
table.insert(Coalitions,coalition.side.NEUTRAL)
end
return Coalitions
end
function UTILS.GetModulationName(Modulation)
if Modulation then
if Modulation==0 then
return"AM"
elseif Modulation==1 then
return"FM"
else
return"Unknown"
end
else
return"Unknown"
end
end
function UTILS.GetReportingName(Typename)
local typename=string.lower(Typename)
for name,value in pairs(ENUMS.ReportingName.NATO)do
local svalue=string.lower(value)
if string.find(typename,svalue,1,true)then
return name
end
end
return"Bogey"
end
function UTILS.GetCallsignName(Callsign)
for name,value in pairs(CALLSIGN.Aircraft)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.AWACS)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.JTAC)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.Tanker)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.B1B)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.B52)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.F15E)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.F16)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.F18)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.FARP)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.TransportAircraft)do
if value==Callsign then
return name
end
end
return"Ghostrider"
end
function UTILS.GMTToLocalTimeDifference()
local theatre=UTILS.GetDCSMap()
if theatre==DCSMAP.Caucasus then
return 4
elseif theatre==DCSMAP.PersianGulf then
return 4
elseif theatre==DCSMAP.NTTR then
return-8
elseif theatre==DCSMAP.Normandy then
return 0
elseif theatre==DCSMAP.TheChannel then
return 2
elseif theatre==DCSMAP.Syria then
return 3
elseif theatre==DCSMAP.MarianaIslands then
return 10
elseif theatre==DCSMAP.Falklands then
return-3
else
BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0",tostring(theatre)))
return 0
end
end
function UTILS.GetDayOfYear(Year,Month,Day)
local floor=math.floor
local n1=floor(275*Month/9)
local n2=floor((Month+9)/12)
local n3=(1+floor((Year-4*floor(Year/4)+2)/3))
return n1-(n2*n3)+Day-30
end
function UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,Rising,Tlocal)
local zenith=90.83
local latitude=Latitude
local longitude=Longitude
local rising=Rising
local n=DayOfYear
Tlocal=Tlocal or 0
local rad=math.rad
local deg=math.deg
local floor=math.floor
local frac=function(n)return n-floor(n)end
local cos=function(d)return math.cos(rad(d))end
local acos=function(d)return deg(math.acos(d))end
local sin=function(d)return math.sin(rad(d))end
local asin=function(d)return deg(math.asin(d))end
local tan=function(d)return math.tan(rad(d))end
local atan=function(d)return deg(math.atan(d))end
local function fit_into_range(val,min,max)
local range=max-min
local count
if val<min then
count=floor((min-val)/range)+1
return val+count*range
elseif val>=max then
count=floor((val-max)/range)+1
return val-count*range
else
return val
end
end
local lng_hour=longitude/15
local t
if rising then
t=n+((6-lng_hour)/24)
else
t=n+((18-lng_hour)/24)
end
local M=(0.9856*t)-3.289
local L=fit_into_range(M+(1.916*sin(M))+(0.020*sin(2*M))+282.634,0,360)
local RA=fit_into_range(atan(0.91764*tan(L)),0,360)
local Lquadrant=floor(L/90)*90
local RAquadrant=floor(RA/90)*90
RA=RA+Lquadrant-RAquadrant
RA=RA/15
local sinDec=0.39782*sin(L)
local cosDec=cos(asin(sinDec))
local cosH=(cos(zenith)-(sinDec*sin(latitude)))/(cosDec*cos(latitude))
if rising and cosH>1 then
return"N/R"
elseif cosH<-1 then
return"N/S"
end
local H
if rising then
H=360-acos(cosH)
else
H=acos(cosH)
end
H=H/15
local T=H+RA-(0.06571*t)-6.622
local UT=fit_into_range(T-lng_hour+Tlocal,0,24)
return floor(UT)*60*60+frac(UT)*60*60
end
function UTILS.GetSunrise(Day,Month,Year,Latitude,Longitude,Tlocal)
local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day)
return UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tlocal)
end
function UTILS.GetSunset(Day,Month,Year,Latitude,Longitude,Tlocal)
local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day)
return UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tlocal)
end
function UTILS.GetOSTime()
if os then
local ts=0
local t=os.date("*t")
local s=t.sec
local m=t.min*60
local h=t.hour*3600
ts=s+m+h
return ts
else
return nil
end
end
function UTILS.ShuffleTable(t)
if t==nil or type(t)~="table"then
BASE:I("Error in ShuffleTable: Missing or wrong type of Argument")
return
end
math.random()
math.random()
math.random()
local TempTable={}
for i=1,#t do
local r=math.random(1,#t)
TempTable[i]=t[r]
table.remove(t,r)
end
return TempTable
end
function UTILS.GetRandomTableElement(t,replace)
if t==nil or type(t)~="table"then
BASE:I("Error in ShuffleTable: Missing or wrong type of Argument")
return
end
math.random()
math.random()
math.random()
local r=math.random(#t)
local element=t[r]
if not replace then
table.remove(t,r)
end
return element
end
function UTILS.IsLoadingDoorOpen(unit_name)
local unit=Unit.getByName(unit_name)
if unit~=nil then
local type_name=unit:getTypeName()
BASE:T("TypeName = "..type_name)
if type_name=="Mi-8MT"and(unit:getDrawArgumentValue(38)==1 or unit:getDrawArgumentValue(86)==1 or unit:getDrawArgumentValue(250)<0)then
BASE:T(unit_name.." Cargo doors are open or cargo door not present")
return true
end
if type_name=="Mi-24P"and(unit:getDrawArgumentValue(38)==1 or unit:getDrawArgumentValue(86)==1)then
BASE:T(unit_name.." a side door is open")
return true
end
if type_name=="UH-1H"and(unit:getDrawArgumentValue(43)==1 or unit:getDrawArgumentValue(44)==1)then
BASE:T(unit_name.." a side door is open ")
return true
end
if string.find(type_name,"SA342")and(unit:getDrawArgumentValue(34)==1)then
BASE:T(unit_name.." front door(s) are open or doors removed")
return true
end
if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1215)==1 and unit:getDrawArgumentValue(1216)==1)then
BASE:T(unit_name.." rear doors are open")
return true
end
if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1220)==1 or unit:getDrawArgumentValue(1221)==1)then
BASE:T(unit_name.." para doors are open")
return true
end
if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1217)==1)then
BASE:T(unit_name.." side door is open")
return true
end
if string.find(type_name,"Bell-47")then
BASE:T(unit_name.." door is open")
return true
end
if string.find(type_name,"UH-60L")and(unit:getDrawArgumentValue(401)==1 or unit:getDrawArgumentValue(402)==1)then
BASE:T(unit_name.." cargo door is open")
return true
end
if string.find(type_name,"UH-60L")and(unit:getDrawArgumentValue(38)==1 or unit:getDrawArgumentValue(400)==1)then
BASE:T(unit_name.." front door(s) are open")
return true
end
if type_name=="AH-64D_BLK_II"then
BASE:T(unit_name.." front door(s) are open")
return true
end
if type_name=="Bronco-OV-10A"then
BASE:T(unit_name.." front door(s) are open")
return true
end
return false
end
return nil
end
function UTILS.GenerateFMFrequencies()
local FreeFMFrequencies={}
for _first=3,7 do
for _second=0,5 do
for _third=0,9 do
local _frequency=((100*_first)+(10*_second)+_third)*100000
table.insert(FreeFMFrequencies,_frequency)
end
end
end
return FreeFMFrequencies
end
function UTILS.GenerateVHFrequencies()
local _skipFrequencies={
214,274,291.5,295,297.5,
300.5,304,305,307,309.5,311,312,312.5,316,
320,324,328,329,330,332,336,337,
342,343,348,351,352,353,358,
363,365,368,372.5,374,
380,381,384,385,389,395,396,
414,420,430,432,435,440,450,455,462,470,485,
507,515,520,525,528,540,550,560,570,577,580,
602,625,641,662,670,680,682,690,
705,720,722,730,735,740,745,750,770,795,
822,830,862,866,
905,907,920,935,942,950,995,
1000,1025,1030,1050,1065,1116,1175,1182,1210,1215
}
local FreeVHFFrequencies={}
local _start=200000
while _start<400000 do
local _found=false
for _,value in pairs(_skipFrequencies)do
if value*1000==_start then
_found=true
break
end
end
if _found==false then
table.insert(FreeVHFFrequencies,_start)
end
_start=_start+10000
end
_start=400000
while _start<850000 do
local _found=false
for _,value in pairs(_skipFrequencies)do
if value*1000==_start then
_found=true
break
end
end
if _found==false then
table.insert(FreeVHFFrequencies,_start)
end
_start=_start+10000
end
_start=850000
while _start<=999000 do
local _found=false
for _,value in pairs(_skipFrequencies)do
if value*1000==_start then
_found=true
break
end
end
if _found==false then
table.insert(FreeVHFFrequencies,_start)
end
_start=_start+50000
end
return FreeVHFFrequencies
end
function UTILS.GenerateUHFrequencies()
local FreeUHFFrequencies={}
local _start=220000000
while _start<399000000 do
if _start~=243000000 then
table.insert(FreeUHFFrequencies,_start)
end
_start=_start+500000
end
return FreeUHFFrequencies
end
function UTILS.GenerateLaserCodes()
local jtacGeneratedLaserCodes={}
local function ContainsDigit(_number,_numberToFind)
local _thisNumber=_number
local _thisDigit=0
while _thisNumber~=0 do
_thisDigit=_thisNumber%10
_thisNumber=math.floor(_thisNumber/10)
if _thisDigit==_numberToFind then
return true
end
end
return false
end
local _code=1111
local _count=1
while _code<1777 and _count<30 do
while true do
_code=_code+1
if not ContainsDigit(_code,8)
and not ContainsDigit(_code,9)
and not ContainsDigit(_code,0)then
table.insert(jtacGeneratedLaserCodes,_code)
break
end
end
_count=_count+1
end
return jtacGeneratedLaserCodes
end
function UTILS.EnsureTable(Object,ReturnNil)
if Object then
if type(Object)~="table"then
Object={Object}
end
else
if ReturnNil then
return nil
else
Object={}
end
end
return Object
end
function UTILS.SaveToFile(Path,Filename,Data)
if not io then
BASE:E("ERROR: io not desanitized. Can't save current file.")
return false
end
if Path==nil and not lfs then
BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
local path=nil
if lfs then
path=Path or lfs.writedir()
end
local filename=Filename
if path~=nil then
filename=path.."\\"..filename
end
local f=assert(io.open(filename,"wb"))
f:write(Data)
f:close()
return true
end
function UTILS.LoadFromFile(Path,Filename)
if not io then
BASE:E("ERROR: io not desanitized. Can't save current state.")
return false
end
if Path==nil and not lfs then
BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
local path=nil
if lfs then
path=Path or lfs.writedir()
end
local filename=Filename
if path~=nil then
filename=path.."\\"..filename
end
local exists=UTILS.CheckFileExists(Path,Filename)
if not exists then
BASE:E(string.format("ERROR: File %s does not exist!",filename))
return false
end
local file=assert(io.open(filename,"rb"))
local loadeddata={}
for line in file:lines()do
loadeddata[#loadeddata+1]=line
end
file:close()
return true,loadeddata
end
function UTILS.CheckFileExists(Path,Filename)
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
if not io then
BASE:E("ERROR: io not desanitized. Can't save current state.")
return false
end
if Path==nil and not lfs then
BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
local path=nil
if lfs then
path=Path or lfs.writedir()
end
local filename=Filename
if path~=nil then
filename=path.."\\"..filename
end
local exists=_fileexists(filename)
if not exists then
BASE:E(string.format("ERROR: File %s does not exist!",filename))
return false
else
return true
end
end
function UTILS.GetCountPerTypeName(Group)
local units=Group:GetUnits()
local TypeNameTable={}
for _,_unt in pairs(units)do
local unit=_unt
local typen=unit:GetTypeName()
if not TypeNameTable[typen]then
TypeNameTable[typen]=1
else
TypeNameTable[typen]=TypeNameTable[typen]+1
end
end
return TypeNameTable
end
function UTILS.SaveStationaryListOfGroups(List,Path,Filename,Structured)
local filename=Filename or"StateListofGroups"
local data="--Save Stationary List of Groups: "..Filename.."\n"
for _,_group in pairs(List)do
local group=GROUP:FindByName(_group)
if group and group:IsAlive()then
local units=group:CountAliveUnits()
local position=group:GetVec3()
if Structured then
local structure=UTILS.GetCountPerTypeName(group)
local strucdata=""
for typen,anzahl in pairs(structure)do
strucdata=strucdata..typen.."=="..anzahl..";"
end
data=string.format("%s%s,%d,%d,%d,%d,%s\n",data,_group,units,position.x,position.y,position.z,strucdata)
else
data=string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z)
end
else
data=string.format("%s%s,0,0,0,0\n",data,_group)
end
end
local outcome=UTILS.SaveToFile(Path,Filename,data)
return outcome
end
function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
local filename=Filename or"SetOfGroups"
local data="--Save SET of groups: "..Filename.."\n"
local List=Set:GetSetObjects()
for _,_group in pairs(List)do
local group=_group
if group and group:IsAlive()then
local name=group:GetName()
local template=string.gsub(name,"-(.+)$","")
if string.find(template,"#")then
template=string.gsub(name,"#(%d+)$","")
end
local units=group:CountAliveUnits()
local position=group:GetVec3()
if Structured then
local structure=UTILS.GetCountPerTypeName(group)
local strucdata=""
for typen,anzahl in pairs(structure)do
strucdata=strucdata..typen.."=="..anzahl..";"
end
data=string.format("%s%s,%s,%d,%d,%d,%d,%s\n",data,name,template,units,position.x,position.y,position.z,strucdata)
else
data=string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z)
end
end
end
local outcome=UTILS.SaveToFile(Path,Filename,data)
return outcome
end
function UTILS.SaveSetOfStatics(Set,Path,Filename)
local filename=Filename or"SetOfStatics"
local data="--Save SET of statics: "..Filename.."\n"
local List=Set:GetSetObjects()
for _,_group in pairs(List)do
local group=_group
if group and group:IsAlive()then
local name=group:GetName()
local position=group:GetVec3()
data=string.format("%s%s,%d,%d,%d\n",data,name,position.x,position.y,position.z)
end
end
local outcome=UTILS.SaveToFile(Path,Filename,data)
return outcome
end
function UTILS.SaveStationaryListOfStatics(List,Path,Filename)
local filename=Filename or"StateListofStatics"
local data="--Save Stationary List of Statics: "..Filename.."\n"
for _,_group in pairs(List)do
local group=STATIC:FindByName(_group,false)
if group and group:IsAlive()then
local position=group:GetVec3()
data=string.format("%s%s,1,%d,%d,%d\n",data,_group,position.x,position.y,position.z)
else
data=string.format("%s%s,0,0,0,0\n",data,_group)
end
end
local outcome=UTILS.SaveToFile(Path,Filename,data)
return outcome
end
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density)
local fires={}
local function Smokers(name,coord,effect,density)
local eff=math.random(8)
if type(effect)=="number"then eff=effect end
coord:BigSmokeAndFire(eff,density,name)
table.insert(fires,name)
end
local function Cruncher(group,typename,anzahl)
local units=group:GetUnits()
local reduced=0
for _,_unit in pairs(units)do
local typo=_unit:GetTypeName()
if typename==typo then
if Cinematic then
local coordinate=_unit:GetCoordinate()
local name=_unit:GetName()
Smokers(name,coordinate,Effect,Density)
end
_unit:Destroy(false)
reduced=reduced+1
if reduced==anzahl then break end
end
end
end
local reduce=true
if Reduce==false then reduce=false end
local filename=Filename or"StateListofGroups"
local datatable={}
if UTILS.CheckFileExists(Path,filename)then
local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename)
table.remove(loadeddata,1)
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local groupname=dataset[1]
local size=tonumber(dataset[2])
local posx=tonumber(dataset[3])
local posy=tonumber(dataset[4])
local posz=tonumber(dataset[5])
local structure=dataset[6]
local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz})
local data={groupname=groupname,size=size,coordinate=coordinate,group=GROUP:FindByName(groupname)}
if reduce then
local actualgroup=GROUP:FindByName(groupname)
if actualgroup and actualgroup:IsAlive()and actualgroup:CountAliveUnits()>size then
if Structured and structure then
local loadedstructure={}
local strcset=UTILS.Split(structure,";")
for _,_data in pairs(strcset)do
local datasplit=UTILS.Split(_data,"==")
loadedstructure[datasplit[1]]=tonumber(datasplit[2])
end
local originalstructure=UTILS.GetCountPerTypeName(actualgroup)
for _name,_number in pairs(originalstructure)do
local loadednumber=0
if loadedstructure[_name]then
loadednumber=loadedstructure[_name]
end
local reduce=false
if loadednumber<_number then reduce=true end
if reduce then
Cruncher(actualgroup,_name,_number-loadednumber)
end
end
else
local reduction=actualgroup:CountAliveUnits()-size
local units=actualgroup:GetUnits()
local units2=UTILS.ShuffleTable(units)
for i=1,reduction do
units2[i]:Destroy(false)
end
end
end
end
table.insert(datatable,data)
end
else
return nil
end
return datatable,fires
end
function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density)
local fires={}
local usedtemplates={}
local spawn=true
if Spawn==false then spawn=false end
local filename=Filename or"SetOfGroups"
local setdata=SET_GROUP:New()
local datatable={}
local function Smokers(name,coord,effect,density)
local eff=math.random(8)
if type(effect)=="number"then eff=effect end
coord:BigSmokeAndFire(eff,density,name)
table.insert(fires,name)
end
local function Cruncher(group,typename,anzahl)
local units=group:GetUnits()
local reduced=0
for _,_unit in pairs(units)do
local typo=_unit:GetTypeName()
if typename==typo then
if Cinematic then
local coordinate=_unit:GetCoordinate()
local name=_unit:GetName()
Smokers(name,coordinate,Effect,Density)
end
_unit:Destroy(false)
reduced=reduced+1
if reduced==anzahl then break end
end
end
end
local function PostSpawn(args)
local spwndgrp=args[1]
local size=args[2]
local structure=args[3]
setdata:AddObject(spwndgrp)
local actualsize=spwndgrp:CountAliveUnits()
if actualsize>size then
if Structured and structure then
local loadedstructure={}
local strcset=UTILS.Split(structure,";")
for _,_data in pairs(strcset)do
local datasplit=UTILS.Split(_data,"==")
loadedstructure[datasplit[1]]=tonumber(datasplit[2])
end
local originalstructure=UTILS.GetCountPerTypeName(spwndgrp)
for _name,_number in pairs(originalstructure)do
local loadednumber=0
if loadedstructure[_name]then
loadednumber=loadedstructure[_name]
end
local reduce=false
if loadednumber<_number then reduce=true end
if reduce then
Cruncher(spwndgrp,_name,_number-loadednumber)
end
end
else
local reduction=actualsize-size
local units=spwndgrp:GetUnits()
local units2=UTILS.ShuffleTable(units)
for i=1,reduction do
units2[i]:Destroy(false)
end
end
end
end
local function MultiUse(Data)
local template=Data.template
if template and usedtemplates[template]and usedtemplates[template].used and usedtemplates[template].used>1 then
if not usedtemplates[template].done then
local spwnd=0
local spawngrp=SPAWN:New(template)
spawngrp:InitLimit(0,usedtemplates[template].used)
for _,_entry in pairs(usedtemplates[template].data)do
spwnd=spwnd+1
local sgrp=spawngrp:SpawnFromCoordinate(_entry.coordinate,spwnd)
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
end
usedtemplates[template].done=true
end
return true
else
return false
end
end
if UTILS.CheckFileExists(Path,filename)then
local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename)
table.remove(loadeddata,1)
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local groupname=dataset[1]
local template=dataset[2]
local size=tonumber(dataset[3])
local posx=tonumber(dataset[4])
local posy=tonumber(dataset[5])
local posz=tonumber(dataset[6])
local structure=dataset[7]
local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz})
local group=nil
if size>0 then
local data={groupname=groupname,size=size,coordinate=coordinate,template=template,structure=structure}
table.insert(datatable,data)
if usedtemplates[template]then
usedtemplates[template].used=usedtemplates[template].used+1
table.insert(usedtemplates[template].data,data)
else
usedtemplates[template]={
data={},
used=1,
done=false,
}
table.insert(usedtemplates[template].data,data)
end
end
end
for _id,_entry in pairs(datatable)do
if spawn and not MultiUse(_entry)and _entry.size>0 then
local group=SPAWN:New(_entry.template)
local sgrp=group:SpawnFromCoordinate(_entry.coordinate)
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
end
end
else
return nil
end
if spawn then
return setdata,fires
else
return datatable
end
end
function UTILS.LoadSetOfStatics(Path,Filename)
local filename=Filename or"SetOfStatics"
local datatable=SET_STATIC:New()
if UTILS.CheckFileExists(Path,filename)then
local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename)
table.remove(loadeddata,1)
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local staticname=dataset[1]
local StaticObject=STATIC:FindByName(staticname,false)
if StaticObject then
datatable:AddObject(StaticObject)
end
end
else
return nil
end
return datatable
end
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density)
local fires={}
local reduce=true
if Reduce==false then reduce=false end
local filename=Filename or"StateListofStatics"
local datatable={}
if UTILS.CheckFileExists(Path,filename)then
local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename)
table.remove(loadeddata,1)
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local staticname=dataset[1]
local size=tonumber(dataset[2])
local posx=tonumber(dataset[3])
local posy=tonumber(dataset[4])
local posz=tonumber(dataset[5])
local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz})
local data={staticname=staticname,size=size,coordinate=coordinate,static=STATIC:FindByName(staticname,false)}
table.insert(datatable,data)
if size==0 and reduce then
local static=STATIC:FindByName(staticname,false)
if static then
if Dead then
local deadobject=SPAWNSTATIC:NewFromStatic(staticname,static:GetCountry())
deadobject:InitDead(true)
local heading=static:GetHeading()
local coord=static:GetCoordinate()
static:Destroy(false)
deadobject:SpawnFromCoordinate(coord,heading,staticname)
if Cinematic then
local effect=math.random(8)
if type(Effect)=="number"then
effect=Effect
end
coord:BigSmokeAndFire(effect,Density,staticname)
table.insert(fires,staticname)
end
else
static:Destroy(false)
end
end
end
end
else
return nil
end
return datatable,fires
end
function UTILS.BearingToCardinal(Heading)
if Heading>=0 and Heading<=22 then return"North"
elseif Heading>=23 and Heading<=66 then return"North-East"
elseif Heading>=67 and Heading<=101 then return"East"
elseif Heading>=102 and Heading<=146 then return"South-East"
elseif Heading>=147 and Heading<=201 then return"South"
elseif Heading>=202 and Heading<=246 then return"South-West"
elseif Heading>=247 and Heading<=291 then return"West"
elseif Heading>=292 and Heading<=338 then return"North-West"
elseif Heading>=339 then return"North"
end
end
function UTILS.ToStringBRAANATO(FromGrp,ToGrp)
local BRAANATO="Merged."
local GroupNumber=ToGrp:GetSize()
local GroupWords="Singleton"
if GroupNumber==2 then GroupWords="Two-Ship"
elseif GroupNumber>=3 then GroupWords="Heavy"
end
local grpLeadUnit=ToGrp:GetUnit(1)
local tgtCoord=grpLeadUnit:GetCoordinate()
local currentCoord=FromGrp:GetCoordinate()
local hdg=UTILS.Round(ToGrp:GetHeading()/100,1)*100
local bearing=UTILS.Round(currentCoord:HeadingTo(tgtCoord),0)
local rangeMetres=tgtCoord:Get2DDistance(currentCoord)
local rangeNM=UTILS.Round(UTILS.MetersToNM(rangeMetres),0)
local aspect=tgtCoord:ToStringAspect(currentCoord)
local alt=UTILS.Round(UTILS.MetersToFeet(grpLeadUnit:GetAltitude())/1000,0)
local track=UTILS.BearingToCardinal(hdg)
if rangeNM>3 then
if aspect==""then
BRAANATO=string.format("%s, BRA, %03d, %d miles, Angels %d, Track %s",GroupWords,bearing,rangeNM,alt,track)
else
BRAANATO=string.format("%s, BRAA, %03d, %d miles, Angels %d, %s, Track %s",GroupWords,bearing,rangeNM,alt,aspect,track)
end
end
return BRAANATO
end
function UTILS.IsInTable(Table,Object,Key)
for key,object in pairs(Table)do
if Key then
if Object[Key]==object[Key]then
return true
end
else
if object==Object then
return true
end
end
end
return false
end
function UTILS.IsAnyInTable(Table,Objects,Key)
for _,Object in pairs(UTILS.EnsureTable(Objects))do
for key,object in pairs(Table)do
if Key then
if Object[Key]==object[Key]then
return true
end
else
if object==Object then
return true
end
end
end
end
return false
end
PROFILER={
ClassName="PROFILER",
Counters={},
dInfo={},
fTime={},
fTimeTotal={},
eventHandler={},
logUnknown=false,
ThreshCPS=0.0,
ThreshTtot=0.005,
fileNamePrefix="MooseProfiler",
fileNameSuffix="txt"
}
function PROFILER.Start(Delay,Duration)
local go=true
if not os then
env.error("ERROR: Profiler needs os to be de-sanitized!")
go=false
end
if not io then
env.error("ERROR: Profiler needs io to be desanitized!")
go=false
end
if not lfs then
env.error("ERROR: Profiler needs lfs to be desanitized!")
go=false
end
if not go then
return
end
if Delay and Delay>0 then
BASE:ScheduleOnce(Delay,PROFILER.Start,0,Duration)
else
PROFILER.TstartGame=timer.getTime()
PROFILER.TstartOS=os.clock()
world.addEventHandler(PROFILER.eventHandler)
env.info('############################   Profiler Started   ############################')
if Duration then
env.info(string.format("- Will be running for %d seconds",Duration))
else
env.info(string.format("- Will be stopped when mission ends"))
end
env.info(string.format("- Calls per second threshold %.3f/sec",PROFILER.ThreshCPS))
env.info(string.format("- Total function time threshold %.3f sec",PROFILER.ThreshTtot))
env.info(string.format("- Output file \"%s\" in your DCS log file folder",PROFILER.getfilename(PROFILER.fileNameSuffix)))
env.info(string.format("- Output file \"%s\" in CSV format",PROFILER.getfilename("csv")))
env.info('###############################################################################')
local duration=Duration or 600
trigger.action.outText("### Profiler running ###",duration)
debug.sethook(PROFILER.hook,"cr")
if Duration then
PROFILER.Stop(Duration)
end
end
end
function PROFILER.Stop(Delay)
if Delay and Delay>0 then
BASE:ScheduleOnce(Delay,PROFILER.Stop)
end
end
function PROFILER.Stop(Delay)
if Delay and Delay>0 then
BASE:ScheduleOnce(Delay,PROFILER.Stop)
else
debug.sethook()
local runTimeGame=timer.getTime()-PROFILER.TstartGame
local runTimeOS=os.clock()-PROFILER.TstartOS
PROFILER.showInfo(runTimeGame,runTimeOS)
end
end
function PROFILER.eventHandler:onEvent(event)
if event.id==world.event.S_EVENT_MISSION_END then
PROFILER.Stop()
end
end
function PROFILER.hook(event)
local f=debug.getinfo(2,"f").func
if event=='call'then
if PROFILER.Counters[f]==nil then
PROFILER.Counters[f]=1
PROFILER.dInfo[f]=debug.getinfo(2,"Sn")
if PROFILER.fTimeTotal[f]==nil then
PROFILER.fTimeTotal[f]=0
end
else
PROFILER.Counters[f]=PROFILER.Counters[f]+1
end
if PROFILER.fTime[f]==nil then
PROFILER.fTime[f]=os.clock()
end
elseif(event=='return')then
if PROFILER.fTime[f]~=nil then
PROFILER.fTimeTotal[f]=PROFILER.fTimeTotal[f]+(os.clock()-PROFILER.fTime[f])
PROFILER.fTime[f]=nil
end
end
end
function PROFILER.getData(func)
local n=PROFILER.dInfo[func]
if n.what=="C"then
return n.name,"?","?",PROFILER.fTimeTotal[func]
end
return n.name,n.short_src,n.linedefined,PROFILER.fTimeTotal[func]
end
function PROFILER._flog(f,txt)
f:write(txt.."\r\n")
end
function PROFILER.showTable(data,f,runTimeGame)
for i=1,#data do
local t=data[i]
local cps=t.count/runTimeGame
local threshCPS=cps>=PROFILER.ThreshCPS
local threshTot=t.tm>=PROFILER.ThreshTtot
if threshCPS and threshTot then
local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call  %s line %s",t.func,t.count,cps,t.tm,t.tm/runTimeGame*100,t.tm/t.count,tostring(t.src),tostring(t.line))
PROFILER._flog(f,text)
end
end
end
function PROFILER.printCSV(data,runTimeGame)
local file=PROFILER.getfilename("csv")
local g=io.open(file,'w')
local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number,"
g:write(text.."\r\n")
for i=1,#data do
local t=data[i]
local cps=t.count/runTimeGame
local txt=string.format("%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,",t.func,t.count,cps,t.tm,t.tm/runTimeGame*100,t.tm/t.count,tostring(t.src),tostring(t.line))
g:write(txt.."\r\n")
end
g:close()
end
function PROFILER.getfilename(ext)
local dir=lfs.writedir()..[[Logs\]]
ext=ext or PROFILER.fileNameSuffix
local file=dir..PROFILER.fileNamePrefix.."."..ext
if not UTILS.FileExists(file)then
return file
end
for i=1,999 do
local file=string.format("%s%s-%03d.%s",dir,PROFILER.fileNamePrefix,i,ext)
if not UTILS.FileExists(file)then
return file
end
end
end
function PROFILER.showInfo(runTimeGame,runTimeOS)
local file=PROFILER.getfilename(PROFILER.fileNameSuffix)
local f=io.open(file,'w')
local Ttot=0
local Calls=0
local t={}
local tcopy=nil
local tserialize=nil
local tforgen=nil
local tpairs=nil
for func,count in pairs(PROFILER.Counters)do
local s,src,line,tm=PROFILER.getData(func)
if PROFILER.logUnknown==true then
if s==nil then s="<Unknown>"end
end
if s~=nil then
local T=
{func=s,
src=src,
line=line,
count=count,
tm=tm,
}
if s=="_copy"then
if tcopy==nil then
tcopy=T
else
tcopy.count=tcopy.count+T.count
tcopy.tm=tcopy.tm+T.tm
end
elseif s=="_Serialize"then
if tserialize==nil then
tserialize=T
else
tserialize.count=tserialize.count+T.count
tserialize.tm=tserialize.tm+T.tm
end
elseif s=="(for generator)"then
if tforgen==nil then
tforgen=T
else
tforgen.count=tforgen.count+T.count
tforgen.tm=tforgen.tm+T.tm
end
elseif s=="pairs"then
if tpairs==nil then
tpairs=T
else
tpairs.count=tpairs.count+T.count
tpairs.tm=tpairs.tm+T.tm
end
else
table.insert(t,T)
end
Ttot=Ttot+tm
Calls=Calls+count
end
end
if tcopy then
table.insert(t,tcopy)
end
if tserialize then
table.insert(t,tserialize)
end
if tforgen then
table.insert(t,tforgen)
end
if tpairs then
table.insert(t,tpairs)
end
env.info('############################   Profiler Stopped   ############################')
env.info(string.format("* Runtime Game     : %s = %d sec",UTILS.SecondsToClock(runTimeGame,true),runTimeGame))
env.info(string.format("* Runtime Real     : %s = %d sec",UTILS.SecondsToClock(runTimeOS,true),runTimeOS))
env.info(string.format("* Function time    : %s = %.1f sec (%.1f percent of runtime game)",UTILS.SecondsToClock(Ttot,true),Ttot,Ttot/runTimeGame*100))
env.info(string.format("* Total functions  : %d",#t))
env.info(string.format("* Total func calls : %d",Calls))
env.info(string.format("* Writing to file  : \"%s\"",file))
env.info(string.format("* Writing to file  : \"%s\"",PROFILER.getfilename("csv")))
env.info("##############################################################################")
table.sort(t,function(a,b)return a.tm>b.tm end)
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER._flog(f,"-------------------------")
PROFILER._flog(f,"---- Profiler Report ----")
PROFILER._flog(f,"-------------------------")
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Runtime Game     : %s = %.1f sec",UTILS.SecondsToClock(runTimeGame,true),runTimeGame))
PROFILER._flog(f,string.format("* Runtime Real     : %s = %.1f sec",UTILS.SecondsToClock(runTimeOS,true),runTimeOS))
PROFILER._flog(f,string.format("* Function time    : %s = %.1f sec (%.1f %% of runtime game)",UTILS.SecondsToClock(Ttot,true),Ttot,Ttot/runTimeGame*100))
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Total functions  = %d",#t))
PROFILER._flog(f,string.format("* Total func calls = %d",Calls))
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Calls per second threshold = %.3f/sec",PROFILER.ThreshCPS))
PROFILER._flog(f,string.format("* Total func time threshold  = %.3f sec",PROFILER.ThreshTtot))
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER.showTable(t,f,runTimeGame)
table.sort(t,function(a,b)return a.tm/a.count>b.tm/b.count end)
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER._flog(f,"--------------------------------------")
PROFILER._flog(f,"---- Data Sorted by Time per Call ----")
PROFILER._flog(f,"--------------------------------------")
PROFILER._flog(f,"")
PROFILER.showTable(t,f,runTimeGame)
table.sort(t,function(a,b)return a.count>b.count end)
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER._flog(f,"------------------------------------")
PROFILER._flog(f,"---- Data Sorted by Total Calls ----")
PROFILER._flog(f,"------------------------------------")
PROFILER._flog(f,"")
PROFILER.showTable(t,f,runTimeGame)
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
f:close()
PROFILER.printCSV(t,runTimeGame)
end
TEMPLATE={
ClassName="TEMPLATE",
Ground={},
Naval={},
Airplane={},
Helicopter={},
}
TEMPLATE.TypeGround={
InfantryAK="Infantry AK",
ParatrooperAKS74="Paratrooper AKS-74",
ParatrooperRPG16="Paratrooper RPG-16",
SoldierWWIIUS="soldier_wwii_us",
InfantryM248="Infantry M249",
SoldierM4="Soldier M4",
}
TEMPLATE.TypeNaval={
Ticonderoga="TICONDEROG",
}
TEMPLATE.TypeAirplane={
A10C="A-10C",
}
TEMPLATE.TypeHelicopter={
AH1W="AH-1W",
}
function TEMPLATE.GetGround(TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
TypeName=TypeName or TEMPLATE.TypeGround.SoldierM4
GroupName=GroupName or"Ground-1"
CountryID=CountryID or country.id.USA
Vec3=Vec3 or{x=0,y=0,z=0}
Nunits=Nunits or 1
Radius=Radius or 50
local template=UTILS.DeepCopy(TEMPLATE.GenericGround)
template.name=GroupName
template.CountryID=CountryID
template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
template.CategoryID=Unit.Category.GROUND_UNIT
template.units[1].type=TypeName
template.units[1].name=GroupName.."-1"
if Vec3 then
TEMPLATE.SetPositionFromVec3(template,Vec3)
end
TEMPLATE.SetUnits(template,Nunits,COORDINATE:NewFromVec3(Vec3),Radius)
return template
end
function TEMPLATE.GetNaval(TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
TypeName=TypeName or TEMPLATE.TypeNaval.Ticonderoga
GroupName=GroupName or"Naval-1"
CountryID=CountryID or country.id.USA
Vec3=Vec3 or{x=0,y=0,z=0}
Nunits=Nunits or 1
Radius=Radius or 500
local template=UTILS.DeepCopy(TEMPLATE.GenericNaval)
template.name=GroupName
template.CountryID=CountryID
template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
template.CategoryID=Unit.Category.SHIP
template.units[1].type=TypeName
template.units[1].name=GroupName.."-1"
if Vec3 then
TEMPLATE.SetPositionFromVec3(template,Vec3)
end
TEMPLATE.SetUnits(template,Nunits,COORDINATE:NewFromVec3(Vec3),Radius)
return template
end
function TEMPLATE.GetAirplane(TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
TypeName=TypeName or TEMPLATE.TypeAirplane.A10C
GroupName=GroupName or"Airplane-1"
CountryID=CountryID or country.id.USA
Vec3=Vec3 or{x=0,y=1000,z=0}
Nunits=Nunits or 1
Radius=Radius or 100
local template=TEMPLATE._GetAircraft(true,TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
return template
end
function TEMPLATE.GetHelicopter(TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
TypeName=TypeName or TEMPLATE.TypeHelicopter.AH1W
GroupName=GroupName or"Helicopter-1"
CountryID=CountryID or country.id.USA
Vec3=Vec3 or{x=0,y=500,z=0}
Nunits=Nunits or 1
Radius=Radius or 100
Nunits=math.min(Nunits,4)
local template=TEMPLATE._GetAircraft(false,TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
return template
end
function TEMPLATE._GetAircraft(Airplane,TypeName,GroupName,CountryID,Vec3,Nunits,Radius)
TypeName=TypeName
GroupName=GroupName or"Aircraft-1"
CountryID=CountryID or country.id.USA
Vec3=Vec3 or{x=0,y=0,z=0}
Nunits=Nunits or 1
Radius=Radius or 100
local template=UTILS.DeepCopy(TEMPLATE.GenericAircraft)
template.name=GroupName
template.CountryID=CountryID
template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
if Airplane then
template.CategoryID=Unit.Category.AIRPLANE
else
template.CategoryID=Unit.Category.HELICOPTER
end
template.units[1].type=TypeName
template.units[1].name=GroupName.."-1"
if Vec3 then
TEMPLATE.SetPositionFromVec3(template,Vec3)
end
TEMPLATE.SetUnits(template,Nunits,COORDINATE:NewFromVec3(Vec3),Radius)
return template
end
function TEMPLATE.SetPositionFromVec2(Template,Vec2)
Template.x=Vec2.x
Template.y=Vec2.y
for _,unit in pairs(Template.units)do
unit.x=Vec2.x
unit.y=Vec2.y
end
Template.route.points[1].x=Vec2.x
Template.route.points[1].y=Vec2.y
Template.route.points[1].alt=0
end
function TEMPLATE.SetPositionFromVec3(Template,Vec3)
local Vec2={x=Vec3.x,y=Vec3.z}
TEMPLATE.SetPositionFromVec2(Template,Vec2)
end
function TEMPLATE.SetUnits(Template,N,Coordinate,Radius)
local units=Template.units
local unit1=units[1]
local Vec3=Coordinate:GetVec3()
unit1.x=Vec3.x
unit1.y=Vec3.z
unit1.alt=Vec3.y
for i=2,N do
units[i]=UTILS.DeepCopy(unit1)
end
for i=1,N do
local unit=units[i]
unit.name=string.format("%s-%d",Template.name,i)
if i>1 then
local vec2=Coordinate:GetRandomCoordinateInRadius(Radius,5):GetVec2()
unit.x=vec2.x
unit.y=vec2.y
unit.alt=unit1.alt
end
end
end
function TEMPLATE.SetAirbase(Template,AirBase,ParkingSpots,EngineOn)
local AirbaseID=AirBase:GetID()
local point=Template.route.points[1]
if AirBase:IsAirdrome()then
point.airdromeId=AirbaseID
else
point.helipadId=AirbaseID
point.linkUnit=AirbaseID
end
if EngineOn then
point.action=COORDINATE.WaypointAction.FromParkingAreaHot
point.type=COORDINATE.WaypointType.TakeOffParkingHot
else
point.action=COORDINATE.WaypointAction.FromParkingArea
point.type=COORDINATE.WaypointType.TakeOffParking
end
for i,unit in ipairs(Template.units)do
unit.parking_id=ParkingSpots[i]
end
end
function TEMPLATE.AddWaypoint(Template,Waypoint)
table.insert(Template.route.points,Waypoint)
end
TEMPLATE.GenericGround=
{
["visible"]=false,
["tasks"]={},
["uncontrollable"]=false,
["task"]="Ground Nothing",
["route"]=
{
["spans"]={},
["points"]=
{
[1]=
{
["alt"]=0,
["type"]="Turning Point",
["ETA"]=0,
["alt_type"]="BARO",
["formation_template"]="",
["y"]=0,
["x"]=0,
["ETA_locked"]=true,
["speed"]=0,
["action"]="Off Road",
["task"]=
{
["id"]="ComboTask",
["params"]=
{
["tasks"]=
{
},
},
},
["speed_locked"]=true,
},
},
},
["groupId"]=nil,
["hidden"]=false,
["units"]=
{
[1]=
{
["transportable"]=
{
["randomTransportable"]=false,
},
["skill"]="Average",
["type"]="Infantry AK",
["unitId"]=nil,
["y"]=0,
["x"]=0,
["name"]="Infantry AK-47 Rus",
["heading"]=0,
["playerCanDrive"]=false,
},
},
["y"]=0,
["x"]=0,
["name"]="Infantry AK-47 Rus",
["start_time"]=0,
}
TEMPLATE.GenericNaval=
{
["visible"]=false,
["tasks"]={},
["uncontrollable"]=false,
["route"]=
{
["points"]=
{
[1]=
{
["alt"]=0,
["type"]="Turning Point",
["ETA"]=0,
["alt_type"]="BARO",
["formation_template"]="",
["y"]=0,
["x"]=0,
["ETA_locked"]=true,
["speed"]=0,
["action"]="Turning Point",
["task"]=
{
["id"]="ComboTask",
["params"]=
{
["tasks"]=
{
},
},
},
["speed_locked"]=true,
},
},
},
["groupId"]=nil,
["hidden"]=false,
["units"]=
{
[1]=
{
["transportable"]=
{
["randomTransportable"]=false,
},
["skill"]="Average",
["type"]="TICONDEROG",
["unitId"]=nil,
["y"]=0,
["x"]=0,
["name"]="Naval-1-1",
["heading"]=0,
["modulation"]=0,
["frequency"]=127500000,
},
},
["y"]=0,
["x"]=0,
["name"]="Naval-1",
["start_time"]=0,
}
TEMPLATE.GenericAircraft=
{
["groupId"]=nil,
["name"]="Rotary-1",
["uncontrolled"]=false,
["hidden"]=false,
["task"]="Nothing",
["y"]=0,
["x"]=0,
["start_time"]=0,
["communication"]=true,
["radioSet"]=false,
["frequency"]=127.5,
["modulation"]=0,
["taskSelected"]=true,
["tasks"]={},
["route"]=
{
["points"]=
{
[1]=
{
["y"]=0,
["x"]=0,
["alt"]=1000,
["alt_type"]="BARO",
["action"]="Turning Point",
["type"]="Turning Point",
["airdromeId"]=nil,
["task"]=
{
["id"]="ComboTask",
["params"]=
{
["tasks"]={},
},
},
["ETA"]=0,
["ETA_locked"]=true,
["speed"]=100,
["speed_locked"]=true,
["formation_template"]="",
},
},
},
["units"]=
{
[1]=
{
["name"]="Rotary-1-1",
["unitId"]=nil,
["type"]="AH-1W",
["onboard_num"]="050",
["livery_id"]="USA X Black",
["skill"]="High",
["ropeLength"]=15,
["speed"]=0,
["x"]=0,
["y"]=0,
["alt"]=10,
["alt_type"]="BARO",
["heading"]=0,
["psi"]=0,
["parking"]=nil,
["parking_id"]=nil,
["payload"]=
{
["pylons"]={},
["fuel"]="1250.0",
["flare"]=30,
["chaff"]=30,
["gun"]=100,
},
["callsign"]=
{
[1]=2,
[2]=1,
[3]=1,
["name"]="Springfield11",
},
},
},
}
STTS={
ClassName="STTS",
DIRECTORY="",
SRS_PORT=5002,
GOOGLE_CREDENTIALS="C:\\Users\\Ciaran\\Downloads\\googletts.json",
EXECUTABLE="DCS-SR-ExternalAudio.exe"
}
STTS.DIRECTORY="D:/DCS/_SRS"
STTS.SRS_PORT=5002
STTS.GOOGLE_CREDENTIALS="C:\\Users\\Ciaran\\Downloads\\googletts.json"
STTS.EXECUTABLE="DCS-SR-ExternalAudio.exe"
function STTS.uuid()
local random=math.random
local template='yxxx-xxxxxxxxxxxx'
return string.gsub(template,'[xy]',function(c)
local v=(c=='x')and random(0,0xf)or random(8,0xb)
return string.format('%x',v)
end)
end
function STTS.round(x,n)
n=math.pow(10,n or 0)
x=x*n
if x>=0 then
x=math.floor(x+0.5)
else
x=math.ceil(x-0.5)
end
return x/n
end
function STTS.getSpeechTime(length,speed,isGoogle)
local maxRateRatio=3
speed=speed or 1.0
isGoogle=isGoogle or false
local speedFactor=1.0
if isGoogle then
speedFactor=speed
else
if speed~=0 then
speedFactor=math.abs(speed)*(maxRateRatio-1)/10+1
end
if speed<0 then
speedFactor=1/speedFactor
end
end
local wpm=math.ceil(100*speedFactor)
local cps=math.floor((wpm*5)/60)
if type(length)=="string"then
length=string.len(length)
end
return length/cps
end
function STTS.TextToSpeech(message,freqs,modulations,volume,name,coalition,point,speed,gender,culture,voice,googleTTS)
if os==nil or io==nil then
env.info("[DCS-STTS] LUA modules os or io are sanitized. skipping. ")
return
end
speed=speed or 1
gender=gender or"female"
culture=culture or""
voice=voice or""
coalition=coalition or"0"
name=name or"ROBOT"
volume=1
speed=1
message=message:gsub("\"","\\\"")
local cmd=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h",STTS.DIRECTORY,STTS.EXECUTABLE,freqs or"305",modulations or"AM",coalition,STTS.SRS_PORT,name)
if voice~=""then
cmd=cmd..string.format(" -V \"%s\"",voice)
else
if culture~=""then
cmd=cmd..string.format(" -l %s",culture)
end
if gender~=""then
cmd=cmd..string.format(" -g %s",gender)
end
end
if googleTTS==true then
cmd=cmd..string.format(" -G \"%s\"",STTS.GOOGLE_CREDENTIALS)
end
if speed~=1 then
cmd=cmd..string.format(" -s %s",speed)
end
if volume~=1.0 then
cmd=cmd..string.format(" -v %s",volume)
end
if point and type(point)=="table"and point.x then
local lat,lon,alt=coord.LOtoLL(point)
lat=STTS.round(lat,4)
lon=STTS.round(lon,4)
alt=math.floor(alt)
cmd=cmd..string.format(" -L %s -O %s -A %s",lat,lon,alt)
end
cmd=cmd..string.format(" -t \"%s\"",message)
if string.len(cmd)>255 then
local filename=os.getenv('TMP').."\\DCS_STTS-"..STTS.uuid()..".bat"
local script=io.open(filename,"w+")
script:write(cmd.." && exit")
script:close()
cmd=string.format("\"%s\"",filename)
timer.scheduleFunction(os.remove,filename,timer.getTime()+1)
end
if string.len(cmd)>255 then
env.info("[DCS-STTS] - cmd string too long")
env.info("[DCS-STTS] TextToSpeech Command :\n"..cmd.."\n")
end
os.execute(cmd)
return STTS.getSpeechTime(message,speed,googleTTS)
end
function STTS.PlayMP3(pathToMP3,freqs,modulations,volume,name,coalition,point)
local cmd=string.format("start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h",STTS.DIRECTORY,STTS.EXECUTABLE,pathToMP3,freqs or"305",modulations or"AM",coalition or"0",STTS.SRS_PORT,name or"ROBOT",volume or"1")
if point and type(point)=="table"and point.x then
local lat,lon,alt=coord.LOtoLL(point)
lat=STTS.round(lat,4)
lon=STTS.round(lon,4)
alt=math.floor(alt)
cmd=cmd..string.format(" -L %s -O %s -A %s",lat,lon,alt)
end
env.info("[DCS-STTS] MP3/OGG Command :\n"..cmd.."\n")
os.execute(cmd)
end
do
FIFO={
ClassName="FIFO",
lid="",
version="0.0.5",
counter=0,
pointer=0,
stackbypointer={},
stackbyid={}
}
function FIFO:New()
local self=BASE:Inherit(self,BASE:New())
self.pointer=0
self.counter=0
self.stackbypointer={}
self.stackbyid={}
self.uniquecounter=0
self.lid=string.format("%s (%s) | ","FiFo",self.version)
self:T(self.lid.."Created.")
return self
end
function FIFO:Clear()
self:T(self.lid.."Clear")
self.pointer=0
self.counter=0
self.stackbypointer=nil
self.stackbyid=nil
self.stackbypointer={}
self.stackbyid={}
self.uniquecounter=0
return self
end
function FIFO:Push(Object,UniqueID)
self:T(self.lid.."Push")
self:T({Object,UniqueID})
self.pointer=self.pointer+1
self.counter=self.counter+1
local uniID=UniqueID
if not UniqueID then
self.uniquecounter=self.uniquecounter+1
uniID=self.uniquecounter
end
self.stackbyid[uniID]={pointer=self.pointer,data=Object,uniqueID=uniID}
self.stackbypointer[self.pointer]={pointer=self.pointer,data=Object,uniqueID=uniID}
return self
end
function FIFO:Pull()
self:T(self.lid.."Pull")
if self.counter==0 then return nil end
local object=self.stackbypointer[1].data
self.stackbypointer[1]=nil
self.counter=self.counter-1
self:Flatten()
return object
end
function FIFO:PullByPointer(Pointer)
self:T(self.lid.."PullByPointer "..tostring(Pointer))
if self.counter==0 then return nil end
local object=self.stackbypointer[Pointer]
self.stackbypointer[Pointer]=nil
if object then self.stackbyid[object.uniqueID]=nil end
self.counter=self.counter-1
self:Flatten()
if object then
return object.data
else
return nil
end
end
function FIFO:ReadByPointer(Pointer)
self:T(self.lid.."ReadByPointer "..tostring(Pointer))
if self.counter==0 or not Pointer or not self.stackbypointer[Pointer]then return nil end
local object=self.stackbypointer[Pointer]
if object then
return object.data
else
return nil
end
end
function FIFO:ReadByID(UniqueID)
self:T(self.lid.."ReadByID "..tostring(UniqueID))
if self.counter==0 or not UniqueID or not self.stackbyid[UniqueID]then return nil end
local object=self.stackbyid[UniqueID]
if object then
return object.data
else
return nil
end
end
function FIFO:PullByID(UniqueID)
self:T(self.lid.."PullByID "..tostring(UniqueID))
if self.counter==0 then return nil end
local object=self.stackbyid[UniqueID]
if object then
return self:PullByPointer(object.pointer)
else
return nil
end
end
function FIFO:Flatten()
self:T(self.lid.."Flatten")
local pointerstack={}
local idstack={}
local counter=0
for _ID,_entry in pairs(self.stackbypointer)do
counter=counter+1
pointerstack[counter]={pointer=counter,data=_entry.data,uniqueID=_entry.uniqueID}
end
for _ID,_entry in pairs(pointerstack)do
idstack[_entry.uniqueID]={pointer=_entry.pointer,data=_entry.data,uniqueID=_entry.uniqueID}
end
self.stackbypointer=nil
self.stackbypointer=pointerstack
self.stackbyid=nil
self.stackbyid=idstack
self.counter=counter
self.pointer=counter
return self
end
function FIFO:IsEmpty()
self:T(self.lid.."IsEmpty")
return self.counter==0 and true or false
end
function FIFO:GetSize()
self:T(self.lid.."GetSize")
return self.counter
end
function FIFO:Count()
self:T(self.lid.."Count")
return self.counter
end
function FIFO:IsNotEmpty()
self:T(self.lid.."IsNotEmpty")
return not self:IsEmpty()
end
function FIFO:GetPointerStack()
self:T(self.lid.."GetPointerStack")
return self.stackbypointer
end
function FIFO:HasUniqueID(UniqueID)
self:T(self.lid.."HasUniqueID")
if self.stackbyid[UniqueID]~=nil then
return true
else
return false
end
end
function FIFO:GetIDStack()
self:T(self.lid.."GetIDStack")
return self.stackbyid
end
function FIFO:GetIDStackSorted()
self:T(self.lid.."GetIDStackSorted")
local stack=self:GetIDStack()
local idstack={}
for _id,_entry in pairs(stack)do
idstack[#idstack+1]=_id
self:T({"pre",_id})
end
local function sortID(a,b)
return a<b
end
table.sort(idstack)
return idstack
end
function FIFO:GetDataTable()
self:T(self.lid.."GetDataTable")
local datatable={}
for _,_entry in pairs(self.stackbypointer)do
datatable[#datatable+1]=_entry.data
end
return datatable
end
function FIFO:GetSortedDataTable()
self:T(self.lid.."GetSortedDataTable")
local datatable={}
local idtablesorted=self:GetIDStackSorted()
for _,_entry in pairs(idtablesorted)do
datatable[#datatable+1]=self:ReadByID(_entry)
end
return datatable
end
function FIFO:ForEach(IteratorFunction,Arg,Function,FunctionArguments)
self:T(self.lid.."ForEach")
local Set=self:GetPointerStack()or{}
Arg=Arg or{}
local function CoRoutine()
local Count=0
for ObjectID,ObjectData in pairs(Set)do
local Object=ObjectData.data
self:T({Object})
if Function then
if Function(unpack(FunctionArguments or{}),Object)==true then
IteratorFunction(Object,unpack(Arg))
end
else
IteratorFunction(Object,unpack(Arg))
end
Count=Count+1
end
return true
end
local co=CoRoutine
local function Schedule()
local status,res=co()
self:T({status,res})
if status==false then
error(res)
end
if res==false then
return true
end
return false
end
Schedule()
return self
end
function FIFO:Flush()
self:T(self.lid.."FiFo Flush")
self:I("FIFO Flushing Stack by Pointer")
for _id,_data in pairs(self.stackbypointer)do
local data=_data
self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID)))
end
self:I("FIFO Flushing Stack by ID")
for _id,_data in pairs(self.stackbyid)do
local data=_data
self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID)))
end
self:I("Counter = "..self.counter)
self:I("Pointer = "..self.pointer)
return self
end
end
do
LIFO={
ClassName="LIFO",
lid="",
version="0.0.5",
counter=0,
pointer=0,
stackbypointer={},
stackbyid={}
}
function LIFO:New()
local self=BASE:Inherit(self,BASE:New())
self.pointer=0
self.counter=0
self.uniquecounter=0
self.stackbypointer={}
self.stackbyid={}
self.lid=string.format("%s (%s) | ","LiFo",self.version)
self:T(self.lid.."Created.")
return self
end
function LIFO:Clear()
self:T(self.lid.."Clear")
self.pointer=0
self.counter=0
self.stackbypointer=nil
self.stackbyid=nil
self.stackbypointer={}
self.stackbyid={}
self.uniquecounter=0
return self
end
function LIFO:Push(Object,UniqueID)
self:T(self.lid.."Push")
self:T({Object,UniqueID})
self.pointer=self.pointer+1
self.counter=self.counter+1
local uniID=UniqueID
if not UniqueID then
self.uniquecounter=self.uniquecounter+1
uniID=self.uniquecounter
end
self.stackbyid[uniID]={pointer=self.pointer,data=Object,uniqueID=uniID}
self.stackbypointer[self.pointer]={pointer=self.pointer,data=Object,uniqueID=uniID}
return self
end
function LIFO:Pull()
self:T(self.lid.."Pull")
if self.counter==0 then return nil end
local object=self.stackbypointer[self.pointer].data
self.stackbypointer[self.pointer]=nil
self.counter=self.counter-1
self.pointer=self.pointer-1
self:Flatten()
return object
end
function LIFO:PullByPointer(Pointer)
self:T(self.lid.."PullByPointer "..tostring(Pointer))
if self.counter==0 then return nil end
local object=self.stackbypointer[Pointer]
self.stackbypointer[Pointer]=nil
if object then self.stackbyid[object.uniqueID]=nil end
self.counter=self.counter-1
self:Flatten()
if object then
return object.data
else
return nil
end
end
function LIFO:ReadByPointer(Pointer)
self:T(self.lid.."ReadByPointer "..tostring(Pointer))
if self.counter==0 or not Pointer or not self.stackbypointer[Pointer]then return nil end
local object=self.stackbypointer[Pointer]
if object then
return object.data
else
return nil
end
end
function LIFO:ReadByID(UniqueID)
self:T(self.lid.."ReadByID "..tostring(UniqueID))
if self.counter==0 or not UniqueID or not self.stackbyid[UniqueID]then return nil end
local object=self.stackbyid[UniqueID]
if object then
return object.data
else
return nil
end
end
function LIFO:PullByID(UniqueID)
self:T(self.lid.."PullByID "..tostring(UniqueID))
if self.counter==0 then return nil end
local object=self.stackbyid[UniqueID]
if object then
return self:PullByPointer(object.pointer)
else
return nil
end
end
function LIFO:Flatten()
self:T(self.lid.."Flatten")
local pointerstack={}
local idstack={}
local counter=0
for _ID,_entry in pairs(self.stackbypointer)do
counter=counter+1
pointerstack[counter]={pointer=counter,data=_entry.data,uniqueID=_entry.uniqueID}
end
for _ID,_entry in pairs(pointerstack)do
idstack[_entry.uniqueID]={pointer=_entry.pointer,data=_entry.data,uniqueID=_entry.uniqueID}
end
self.stackbypointer=nil
self.stackbypointer=pointerstack
self.stackbyid=nil
self.stackbyid=idstack
self.counter=counter
self.pointer=counter
return self
end
function LIFO:IsEmpty()
self:T(self.lid.."IsEmpty")
return self.counter==0 and true or false
end
function LIFO:GetSize()
self:T(self.lid.."GetSize")
return self.counter
end
function LIFO:Count()
self:T(self.lid.."Count")
return self.counter
end
function LIFO:IsNotEmpty()
self:T(self.lid.."IsNotEmpty")
return not self:IsEmpty()
end
function LIFO:GetPointerStack()
self:T(self.lid.."GetPointerStack")
return self.stackbypointer
end
function LIFO:GetIDStack()
self:T(self.lid.."GetIDStack")
return self.stackbyid
end
function LIFO:GetIDStackSorted()
self:T(self.lid.."GetIDStackSorted")
local stack=self:GetIDStack()
local idstack={}
for _id,_entry in pairs(stack)do
idstack[#idstack+1]=_id
self:T({"pre",_id})
end
local function sortID(a,b)
return a<b
end
table.sort(idstack)
return idstack
end
function LIFO:HasUniqueID(UniqueID)
self:T(self.lid.."HasUniqueID")
return self.stackbyid[UniqueID]and true or false
end
function LIFO:Flush()
self:T(self.lid.."FiFo Flush")
self:I("LIFO Flushing Stack by Pointer")
for _id,_data in pairs(self.stackbypointer)do
local data=_data
self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID)))
end
self:I("LIFO Flushing Stack by ID")
for _id,_data in pairs(self.stackbyid)do
local data=_data
self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID)))
end
self:I("Counter = "..self.counter)
self:I("Pointer = "..self.pointer)
return self
end
function LIFO:GetDataTable()
self:T(self.lid.."GetDataTable")
local datatable={}
for _,_entry in pairs(self.stackbypointer)do
datatable[#datatable+1]=_entry.data
end
return datatable
end
function LIFO:GetSortedDataTable()
self:T(self.lid.."GetSortedDataTable")
local datatable={}
local idtablesorted=self:GetIDStackSorted()
for _,_entry in pairs(idtablesorted)do
datatable[#datatable+1]=self:ReadByID(_entry)
end
return datatable
end
function LIFO:ForEach(IteratorFunction,Arg,Function,FunctionArguments)
self:T(self.lid.."ForEach")
local Set=self:GetPointerStack()or{}
Arg=Arg or{}
local function CoRoutine()
local Count=0
for ObjectID,ObjectData in pairs(Set)do
local Object=ObjectData.data
self:T({Object})
if Function then
if Function(unpack(FunctionArguments or{}),Object)==true then
IteratorFunction(Object,unpack(Arg))
end
else
IteratorFunction(Object,unpack(Arg))
end
Count=Count+1
end
return true
end
local co=CoRoutine
local function Schedule()
local status,res=co()
self:T({status,res})
if status==false then
error(res)
end
if res==false then
return true
end
return false
end
Schedule()
return self
end
end
SOCKET={
ClassName="SOCKET",
verbose=0,
lid=nil,
}
SOCKET.DataType={
TEXT="moose_text",
BOMBRESULT="moose_bomb_result",
STRAFERESULT="moose_strafe_result",
LSOGRADE="moose_lso_grade",
}
SOCKET.version="0.2.0"
function SOCKET:New(Port,Host)
local self=BASE:Inherit(self,FSM:New())
package.path=package.path..";.\\LuaSocket\\?.lua;"
package.cpath=package.cpath..";.\\LuaSocket\\?.dll;"
self.socket=require("socket")
self.port=Port or 10042
self.host=Host or"127.0.0.1"
self.json=loadfile("Scripts\\JSON.lua")()
self.UDPSendSocket=self.socket.udp()
self.UDPSendSocket:settimeout(0)
return self
end
function SOCKET:SetPort(Port)
self.port=Port or 10042
end
function SOCKET:SetHost(Host)
self.host=Host or"127.0.0.1"
end
function SOCKET:SendTable(Table)
Table.server_name=BASE.ServerName or"Unknown"
local json=self.json:encode(Table)
self:T("Json table:")
self:T(json)
self.socket.try(self.UDPSendSocket:sendto(json,self.host,self.port))
return self
end
function SOCKET:SendText(Text)
local message={}
message.command=SOCKET.DataType.TEXT
message.text=Text
self:SendTable(message)
return self
end
local _TraceOnOff=true
local _TraceLevel=1
local _TraceAll=false
local _TraceClass={}
local _TraceClassMethod={}
local _ClassID=0
BASE={
ClassName="BASE",
ClassID=0,
Events={},
States={},
Debug=debug,
Scheduler=nil,
}
BASE.__={}
BASE._={
Schedules={},
}
FORMATION={
Cone="Cone",
Vee="Vee",
}
function BASE:New()
local self=UTILS.DeepCopy(self)
_ClassID=_ClassID+1
self.ClassID=_ClassID
return self
end
function BASE:Inherit(Child,Parent)
local Child=routines.utils.deepCopy(Child)
if Child~=nil then
if rawget(Child,"__")then
setmetatable(Child,{__index=Child.__})
setmetatable(Child.__,{__index=Parent})
else
setmetatable(Child,{__index=Parent})
end
end
return Child
end
local function getParent(Child)
local Parent=nil
if Child.ClassName=='BASE'then
Parent=nil
else
if rawget(Child,"__")then
Parent=getmetatable(Child.__).__index
else
Parent=getmetatable(Child).__index
end
end
return Parent
end
function BASE:GetParent(Child,FromClass)
local Parent
if Child.ClassName=='BASE'then
Parent=nil
else
if FromClass then
while(Child.ClassName~="BASE"and Child.ClassName~=FromClass.ClassName)do
Child=getParent(Child)
end
end
if Child.ClassName=='BASE'then
Parent=nil
else
Parent=getParent(Child)
end
end
return Parent
end
function BASE:IsInstanceOf(ClassName)
if type(ClassName)~='string'then
if type(ClassName)=='table'and ClassName.ClassName~=nil then
ClassName=ClassName.ClassName
else
local err_str='className parameter should be a string; parameter received: '..type(ClassName)
self:E(err_str)
return false
end
end
ClassName=string.upper(ClassName)
if string.upper(self.ClassName)==ClassName then
return true
end
local Parent=getParent(self)
while Parent do
if string.upper(Parent.ClassName)==ClassName then
return true
end
Parent=getParent(Parent)
end
return false
end
function BASE:GetClassNameAndID()
return string.format('%s#%09d',self.ClassName,self.ClassID)
end
function BASE:GetClassName()
return self.ClassName
end
function BASE:GetClassID()
return self.ClassID
end
do
function BASE:EventDispatcher()
return _EVENTDISPATCHER
end
function BASE:GetEventPriority()
return self._.EventPriority or 5
end
function BASE:SetEventPriority(EventPriority)
self._.EventPriority=EventPriority
end
function BASE:EventRemoveAll()
self:EventDispatcher():RemoveAll(self)
return self
end
function BASE:HandleEvent(EventID,EventFunction)
self:EventDispatcher():OnEventGeneric(EventFunction,self,EventID)
return self
end
function BASE:UnHandleEvent(EventID)
self:EventDispatcher():RemoveEvent(self,EventID)
return self
end
end
function BASE:CreateEventBirth(EventTime,Initiator,IniUnitName,place,subplace)
self:F({EventTime,Initiator,IniUnitName,place,subplace})
local Event={
id=world.event.S_EVENT_BIRTH,
time=EventTime,
initiator=Initiator,
IniUnitName=IniUnitName,
place=place,
subplace=subplace,
}
world.onEvent(Event)
end
function BASE:CreateEventCrash(EventTime,Initiator,IniObjectCategory)
self:F({EventTime,Initiator})
local Event={
id=world.event.S_EVENT_CRASH,
time=EventTime,
initiator=Initiator,
IniObjectCategory=IniObjectCategory,
}
world.onEvent(Event)
end
function BASE:CreateEventUnitLost(EventTime,Initiator)
self:F({EventTime,Initiator})
local Event={
id=world.event.S_EVENT_UNIT_LOST,
time=EventTime,
initiator=Initiator,
}
world.onEvent(Event)
end
function BASE:CreateEventDead(EventTime,Initiator,IniObjectCategory)
self:F({EventTime,Initiator,IniObjectCategory})
local Event={
id=world.event.S_EVENT_DEAD,
time=EventTime,
initiator=Initiator,
IniObjectCategory=IniObjectCategory,
}
world.onEvent(Event)
end
function BASE:CreateEventRemoveUnit(EventTime,Initiator)
self:F({EventTime,Initiator})
local Event={
id=EVENTS.RemoveUnit,
time=EventTime,
initiator=Initiator,
}
world.onEvent(Event)
end
function BASE:CreateEventTakeoff(EventTime,Initiator)
self:F({EventTime,Initiator})
local Event={
id=world.event.S_EVENT_TAKEOFF,
time=EventTime,
initiator=Initiator,
}
world.onEvent(Event)
end
function BASE:CreateEventPlayerEnterAircraft(PlayerUnit)
self:F({PlayerUnit})
local Event={
id=EVENTS.PlayerEnterAircraft,
time=timer.getTime(),
initiator=PlayerUnit:GetDCSObject()
}
world.onEvent(Event)
end
function BASE:onEvent(event)
if self then
for EventID,EventObject in pairs(self.Events)do
if EventObject.EventEnabled then
if event.id==EventObject.Event then
if self==EventObject.Self then
if event.initiator and event.initiator:isExist()then
event.IniUnitName=event.initiator:getName()
end
if event.target and event.target:isExist()then
event.TgtUnitName=event.target:getName()
end
end
end
end
end
end
end
do
function BASE:ScheduleOnce(Start,SchedulerFunction,...)
local ObjectName="-"
ObjectName=self.ClassName..self.ClassID
self:F3({"ScheduleOnce: ",ObjectName,Start})
if not self.Scheduler then
self.Scheduler=SCHEDULER:New(self)
end
local ScheduleID=self.Scheduler:Schedule(nil,SchedulerFunction,{...},Start)
self._.Schedules[#self._.Schedules+1]=ScheduleID
return self._.Schedules[#self._.Schedules]
end
function BASE:ScheduleRepeat(Start,Repeat,RandomizeFactor,Stop,SchedulerFunction,...)
self:F2({Start})
self:T3({...})
local ObjectName="-"
ObjectName=self.ClassName..self.ClassID
self:F3({"ScheduleRepeat: ",ObjectName,Start,Repeat,RandomizeFactor,Stop})
if not self.Scheduler then
self.Scheduler=SCHEDULER:New(self)
end
local ScheduleID=self.Scheduler:Schedule(
nil,
SchedulerFunction,
{...},
Start,
Repeat,
RandomizeFactor,
Stop,
4
)
self._.Schedules[#self._.Schedules+1]=ScheduleID
return self._.Schedules[#self._.Schedules]
end
function BASE:ScheduleStop(SchedulerID)
self:F3({"ScheduleStop:"})
if self.Scheduler then
_SCHEDULEDISPATCHER:Stop(self.Scheduler,SchedulerID)
end
end
end
function BASE:SetState(Object,Key,Value)
local ClassNameAndID=Object:GetClassNameAndID()
self.States[ClassNameAndID]=self.States[ClassNameAndID]or{}
self.States[ClassNameAndID][Key]=Value
return self.States[ClassNameAndID][Key]
end
function BASE:GetState(Object,Key)
local ClassNameAndID=Object:GetClassNameAndID()
if self.States[ClassNameAndID]then
local Value=self.States[ClassNameAndID][Key]or false
return Value
end
return nil
end
function BASE:ClearState(Object,StateName)
local ClassNameAndID=Object:GetClassNameAndID()
if self.States[ClassNameAndID]then
self.States[ClassNameAndID][StateName]=nil
end
end
function BASE:TraceOn()
self:TraceOnOff(true)
end
function BASE:TraceOff()
self:TraceOnOff(false)
end
function BASE:TraceOnOff(TraceOnOff)
if TraceOnOff==false then
self:I("Tracing in MOOSE is OFF")
_TraceOnOff=false
else
self:I("Tracing in MOOSE is ON")
_TraceOnOff=true
end
end
function BASE:IsTrace()
if BASE.Debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then
return true
else
return false
end
end
function BASE:TraceLevel(Level)
_TraceLevel=Level or 1
self:I("Tracing level ".._TraceLevel)
end
function BASE:TraceAll(TraceAll)
if TraceAll==false then
_TraceAll=false
else
_TraceAll=true
end
if _TraceAll then
self:I("Tracing all methods in MOOSE ")
else
self:I("Switched off tracing all methods in MOOSE")
end
end
function BASE:TraceClass(Class)
_TraceClass[Class]=true
_TraceClassMethod[Class]={}
self:I("Tracing class "..Class)
end
function BASE:TraceClassMethod(Class,Method)
if not _TraceClassMethod[Class]then
_TraceClassMethod[Class]={}
_TraceClassMethod[Class].Method={}
end
_TraceClassMethod[Class].Method[Method]=true
self:I("Tracing method "..Method.." of class "..Class)
end
function BASE:_F(Arguments,DebugInfoCurrentParam,DebugInfoFromParam)
if BASE.Debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then
local DebugInfoCurrent=DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=DebugInfoFromParam and DebugInfoFromParam or BASE.Debug.getinfo(3,"l")
local Function="function"
if DebugInfoCurrent.name then
Function=DebugInfoCurrent.name
end
if _TraceAll==true or _TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName].Method[Function]then
local LineCurrent=0
if DebugInfoCurrent.currentline then
LineCurrent=DebugInfoCurrent.currentline
end
local LineFrom=0
if DebugInfoFrom then
LineFrom=DebugInfoFrom.currentline
end
env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s(%s)",LineCurrent,LineFrom,"F",self.ClassName,self.ClassID,Function,routines.utils.oneLineSerialize(Arguments)))
end
end
end
function BASE:F(Arguments)
if BASE.Debug and _TraceOnOff then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
if _TraceLevel>=1 then
self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom)
end
end
end
function BASE:F2(Arguments)
if BASE.Debug and _TraceOnOff then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
if _TraceLevel>=2 then
self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom)
end
end
end
function BASE:F3(Arguments)
if BASE.Debug and _TraceOnOff then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
if _TraceLevel>=3 then
self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom)
end
end
end
function BASE:_T(Arguments,DebugInfoCurrentParam,DebugInfoFromParam)
if BASE.Debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then
local DebugInfoCurrent=DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=DebugInfoFromParam and DebugInfoFromParam or BASE.Debug.getinfo(3,"l")
local Function="function"
if DebugInfoCurrent.name then
Function=DebugInfoCurrent.name
end
if _TraceAll==true or _TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName].Method[Function]then
local LineCurrent=0
if DebugInfoCurrent.currentline then
LineCurrent=DebugInfoCurrent.currentline
end
local LineFrom=0
if DebugInfoFrom then
LineFrom=DebugInfoFrom.currentline
end
env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s",LineCurrent,LineFrom,"T",self.ClassName,self.ClassID,routines.utils.oneLineSerialize(Arguments)))
end
end
end
function BASE:T(Arguments)
if BASE.Debug and _TraceOnOff then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
if _TraceLevel>=1 then
self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom)
end
end
end
function BASE:T2(Arguments)
if BASE.Debug and _TraceOnOff then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
if _TraceLevel>=2 then
self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom)
end
end
end
function BASE:T3(Arguments)
if BASE.Debug and _TraceOnOff then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
if _TraceLevel>=3 then
self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom)
end
end
end
function BASE:E(Arguments)
if BASE.Debug then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
local Function="function"
if DebugInfoCurrent.name then
Function=DebugInfoCurrent.name
end
local LineCurrent=DebugInfoCurrent.currentline
local LineFrom=-1
if DebugInfoFrom then
LineFrom=DebugInfoFrom.currentline
end
env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s(%s)",LineCurrent,LineFrom,"E",self.ClassName,self.ClassID,Function,routines.utils.oneLineSerialize(Arguments)))
else
env.info(string.format("%1s:%30s%05d(%s)","E",self.ClassName,self.ClassID,routines.utils.oneLineSerialize(Arguments)))
end
end
function BASE:I(Arguments)
if BASE.Debug then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
local Function="function"
if DebugInfoCurrent.name then
Function=DebugInfoCurrent.name
end
local LineCurrent=DebugInfoCurrent.currentline
local LineFrom=-1
if DebugInfoFrom then
LineFrom=DebugInfoFrom.currentline
end
env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s(%s)",LineCurrent,LineFrom,"I",self.ClassName,self.ClassID,Function,routines.utils.oneLineSerialize(Arguments)))
else
env.info(string.format("%1s:%30s%05d(%s)","I",self.ClassName,self.ClassID,routines.utils.oneLineSerialize(Arguments)))
end
end
ASTAR={
ClassName="ASTAR",
Debug=nil,
lid=nil,
nodes={},
counter=1,
Nnodes=0,
ncost=0,
ncostcache=0,
nvalid=0,
nvalidcache=0,
}
ASTAR.INF=1/0
ASTAR.version="0.4.0"
function ASTAR:New()
local self=BASE:Inherit(self,BASE:New())
self.lid="ASTAR | "
return self
end
function ASTAR:SetStartCoordinate(Coordinate)
self.startCoord=Coordinate
return self
end
function ASTAR:SetEndCoordinate(Coordinate)
self.endCoord=Coordinate
return self
end
function ASTAR:GetNodeFromCoordinate(Coordinate)
local node={}
node.coordinate=Coordinate
node.surfacetype=Coordinate:GetSurfaceType()
node.id=self.counter
node.valid={}
node.cost={}
self.counter=self.counter+1
return node
end
function ASTAR:AddNode(Node)
self.nodes[Node.id]=Node
self.Nnodes=self.Nnodes+1
return self
end
function ASTAR:AddNodeFromCoordinate(Coordinate)
local node=self:GetNodeFromCoordinate(Coordinate)
self:AddNode(node)
return node
end
function ASTAR:CheckValidSurfaceType(Node,SurfaceTypes)
if SurfaceTypes then
if type(SurfaceTypes)~="table"then
SurfaceTypes={SurfaceTypes}
end
for _,surface in pairs(SurfaceTypes)do
if surface==Node.surfacetype then
return true
end
end
return false
else
return true
end
end
function ASTAR:SetValidNeighbourFunction(NeighbourFunction,...)
self.ValidNeighbourFunc=NeighbourFunction
self.ValidNeighbourArg={}
if arg then
self.ValidNeighbourArg=arg
end
return self
end
function ASTAR:SetValidNeighbourLoS(CorridorWidth)
self:SetValidNeighbourFunction(ASTAR.LoS,CorridorWidth)
return self
end
function ASTAR:SetValidNeighbourDistance(MaxDistance)
self:SetValidNeighbourFunction(ASTAR.DistMax,MaxDistance)
return self
end
function ASTAR:SetValidNeighbourRoad(MaxDistance)
self:SetValidNeighbourFunction(ASTAR.Road,MaxDistance)
return self
end
function ASTAR:SetCostFunction(CostFunction,...)
self.CostFunc=CostFunction
self.CostArg={}
if arg then
self.CostArg=arg
end
return self
end
function ASTAR:SetCostDist2D()
self:SetCostFunction(ASTAR.Dist2D)
return self
end
function ASTAR:SetCostDist3D()
self:SetCostFunction(ASTAR.Dist3D)
return self
end
function ASTAR:SetCostRoad()
self:SetCostFunction(ASTAR)
return self
end
function ASTAR:CreateGrid(ValidSurfaceTypes,BoxHY,SpaceX,deltaX,deltaY,MarkGrid)
local Dz=SpaceX or 10000
local Dx=BoxHY and BoxHY/2 or 20000
local dz=deltaX or 2000
local dx=deltaY or dz
local angle=self.startCoord:HeadingTo(self.endCoord)
local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz
local co=COORDINATE:New(0,0,0)
local do1=co:Get2DDistance(self.startCoord)
local ho1=co:HeadingTo(self.startCoord)
local xmin=-Dx
local zmin=-Dz
local nz=dist/dz+1
local nx=2*Dx/dx+1
local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes",nx,nz,nx*nz)
self:T(self.lid..text)
for i=1,nx do
local x=xmin+dx*(i-1)
for j=1,nz do
local z=zmin+dz*(j-1)
local vec3=UTILS.Rotate2D({x=x,y=0,z=z},angle)
local c=COORDINATE:New(vec3.z,vec3.y,vec3.x):Translate(do1,ho1,true)
local node=self:GetNodeFromCoordinate(c)
if self:CheckValidSurfaceType(node,ValidSurfaceTypes)then
if MarkGrid then
c:MarkToAll(string.format("i=%d, j=%d surface=%d",i,j,node.surfacetype))
end
self:AddNode(node)
end
end
end
local text=string.format("Done building grid!")
self:T2(self.lid..text)
return self
end
function ASTAR.LoS(nodeA,nodeB,corridor)
local offset=1
local dx=corridor and corridor/2 or nil
local dy=dx
local cA=nodeA.coordinate:GetVec3()
local cB=nodeB.coordinate:GetVec3()
cA.y=offset
cB.y=offset
local los=land.isVisible(cA,cB)
if los and corridor then
local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate)
local Ap=UTILS.VecTranslate(cA,dx,heading+90)
local Bp=UTILS.VecTranslate(cB,dx,heading+90)
los=land.isVisible(Ap,Bp)
if los then
local Am=UTILS.VecTranslate(cA,dx,heading-90)
local Bm=UTILS.VecTranslate(cB,dx,heading-90)
los=land.isVisible(Am,Bm)
end
end
return los
end
function ASTAR.Road(nodeA,nodeB)
local path=land.findPathOnRoads("roads",nodeA.coordinate.x,nodeA.coordinate.z,nodeB.coordinate.x,nodeB.coordinate.z)
if path then
return true
else
return false
end
end
function ASTAR.DistMax(nodeA,nodeB,distmax)
distmax=distmax or 2000
local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate)
return dist<=distmax
end
function ASTAR.Dist2D(nodeA,nodeB)
local dist=nodeA.coordinate:Get2DDistance(nodeB)
return dist
end
function ASTAR.Dist3D(nodeA,nodeB)
local dist=nodeA.coordinate:Get3DDistance(nodeB.coordinate)
return dist
end
function ASTAR.DistRoad(nodeA,nodeB)
local path=land.findPathOnRoads("roads",nodeA.coordinate.x,nodeA.coordinate.z,nodeB.coordinate.x,nodeB.coordinate.z)
if path then
local dist=0
for i=2,#path do
local b=path[i]
local a=path[i-1]
dist=dist+UTILS.VecDist2D(a,b)
end
return dist
end
return math.huge
end
function ASTAR:FindClosestNode(Coordinate)
local distMin=math.huge
local closeNode=nil
for _,_node in pairs(self.nodes)do
local node=_node
local dist=node.coordinate:Get2DDistance(Coordinate)
if dist<distMin then
distMin=dist
closeNode=node
end
end
return closeNode,distMin
end
function ASTAR:FindStartNode()
local node,dist=self:FindClosestNode(self.startCoord)
self.startNode=node
if dist>1000 then
self:T(self.lid.."Adding start node to node grid!")
self:AddNode(node)
end
return self
end
function ASTAR:FindEndNode()
local node,dist=self:FindClosestNode(self.endCoord)
self.endNode=node
if dist>1000 then
self:T(self.lid.."Adding end node to node grid!")
self:AddNode(node)
end
return self
end
function ASTAR:GetPath(ExcludeStartNode,ExcludeEndNode)
self:FindStartNode()
self:FindEndNode()
local nodes=self.nodes
local start=self.startNode
local goal=self.endNode
local openset={}
local closedset={}
local came_from={}
local g_score={}
local f_score={}
openset[start.id]=true
local Nopen=1
g_score[start.id]=0
f_score[start.id]=g_score[start.id]+self:_HeuristicCost(start,goal)
local T0=timer.getAbsTime()
local text=string.format("Starting A* pathfinding with %d Nodes",self.Nnodes)
self:T(self.lid..text)
local Tstart=UTILS.GetOSTime()
while Nopen>0 do
local current=self:_LowestFscore(openset,f_score)
if current.id==goal.id then
local path=self:_UnwindPath({},came_from,goal)
if not ExcludeEndNode then
table.insert(path,goal)
end
if ExcludeStartNode then
table.remove(path,1)
end
local Tstop=UTILS.GetOSTime()
local dT=nil
if Tstart and Tstop then
dT=Tstop-Tstart
end
local text=string.format("Found path with %d nodes (%d total)",#path,self.Nnodes)
if dT then
text=text..string.format(", OS Time %.6f sec",dT)
end
text=text..string.format(", Nvalid=%d [%d cached]",self.nvalid,self.nvalidcache)
text=text..string.format(", Ncost=%d [%d cached]",self.ncost,self.ncostcache)
self:T(self.lid..text)
return path
end
openset[current.id]=nil
Nopen=Nopen-1
closedset[current.id]=true
local neighbors=self:_NeighbourNodes(current,nodes)
for _,neighbor in pairs(neighbors)do
if self:_NotIn(closedset,neighbor.id)then
local tentative_g_score=g_score[current.id]+self:_DistNodes(current,neighbor)
if self:_NotIn(openset,neighbor.id)or tentative_g_score<g_score[neighbor.id]then
came_from[neighbor]=current
g_score[neighbor.id]=tentative_g_score
f_score[neighbor.id]=g_score[neighbor.id]+self:_HeuristicCost(neighbor,goal)
if self:_NotIn(openset,neighbor.id)then
openset[neighbor.id]=true
Nopen=Nopen+1
end
end
end
end
end
local text=string.format("WARNING: Could NOT find valid path!")
self:E(self.lid..text)
MESSAGE:New(text,60,"ASTAR"):ToAllIf(self.Debug)
return nil
end
function ASTAR:_HeuristicCost(nodeA,nodeB)
self.ncost=self.ncost+1
local cost=nodeA.cost[nodeB.id]
if cost~=nil then
self.ncostcache=self.ncostcache+1
return cost
end
local cost=nil
if self.CostFunc then
cost=self.CostFunc(nodeA,nodeB,unpack(self.CostArg))
else
cost=self:_DistNodes(nodeA,nodeB)
end
nodeA.cost[nodeB.id]=cost
nodeB.cost[nodeA.id]=cost
return cost
end
function ASTAR:_IsValidNeighbour(node,neighbor)
self.nvalid=self.nvalid+1
local valid=node.valid[neighbor.id]
if valid~=nil then
self.nvalidcache=self.nvalidcache+1
return valid
end
local valid=nil
if self.ValidNeighbourFunc then
valid=self.ValidNeighbourFunc(node,neighbor,unpack(self.ValidNeighbourArg))
else
valid=true
end
node.valid[neighbor.id]=valid
neighbor.valid[node.id]=valid
return valid
end
function ASTAR:_DistNodes(nodeA,nodeB)
return nodeA.coordinate:Get2DDistance(nodeB.coordinate)
end
function ASTAR:_LowestFscore(set,f_score)
local lowest,bestNode=ASTAR.INF,nil
for nid,node in pairs(set)do
local score=f_score[nid]
if score<lowest then
lowest,bestNode=score,nid
end
end
return self.nodes[bestNode]
end
function ASTAR:_NeighbourNodes(theNode,nodes)
local neighbors={}
for _,node in pairs(nodes)do
if theNode.id~=node.id then
local isvalid=self:_IsValidNeighbour(theNode,node)
if isvalid then
table.insert(neighbors,node)
end
end
end
return neighbors
end
function ASTAR:_NotIn(set,theNode)
return set[theNode]==nil
end
function ASTAR:_UnwindPath(flat_path,map,current_node)
if map[current_node]then
table.insert(flat_path,1,map[current_node])
return self:_UnwindPath(flat_path,map,map[current_node])
else
return flat_path
end
end
BEACON={
ClassName="BEACON",
Positionable=nil,
name=nil,
}
BEACON.Type={
NULL=0,
VOR=1,
DME=2,
VOR_DME=3,
TACAN=4,
VORTAC=5,
RSBN=128,
BROADCAST_STATION=1024,
HOMER=8,
AIRPORT_HOMER=4104,
AIRPORT_HOMER_WITH_MARKER=4136,
ILS_FAR_HOMER=16408,
ILS_NEAR_HOMER=16424,
ILS_LOCALIZER=16640,
ILS_GLIDESLOPE=16896,
PRMG_LOCALIZER=33024,
PRMG_GLIDESLOPE=33280,
ICLS=131584,
ICLS_LOCALIZER=131328,
ICLS_GLIDESLOPE=131584,
NAUTICAL_HOMER=65536,
}
BEACON.System={
PAR_10=1,
RSBN_5=2,
TACAN=3,
TACAN_TANKER_X=4,
TACAN_TANKER_Y=5,
VOR=6,
ILS_LOCALIZER=7,
ILS_GLIDESLOPE=8,
PRMG_LOCALIZER=9,
PRMG_GLIDESLOPE=10,
BROADCAST_STATION=11,
VORTAC=12,
TACAN_AA_MODE_X=13,
TACAN_AA_MODE_Y=14,
VORDME=15,
ICLS_LOCALIZER=16,
ICLS_GLIDESLOPE=17,
}
function BEACON:New(Positionable)
local self=BASE:Inherit(self,BASE:New())
self:F(Positionable)
if Positionable:GetPointVec2()then
self.Positionable=Positionable
self.name=Positionable:GetName()
self:I(string.format("New BEACON %s",tostring(self.name)))
return self
end
self:E({"The passed positionable is invalid, no BEACON created",Positionable})
return nil
end
function BEACON:ActivateTACAN(Channel,Mode,Message,Bearing,Duration)
self:T({channel=Channel,mode=Mode,callsign=Message,bearing=Bearing,duration=Duration})
Mode=Mode or"Y"
local Frequency=UTILS.TACANToFrequency(Channel,Mode)
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
return self
end
local Type=BEACON.Type.TACAN
local System=BEACON.System.TACAN
local AA=self.Positionable:IsAir()
if AA then
System=5
if Mode=="X"then
System=BEACON.System.TACAN_TANKER_X
else
System=BEACON.System.TACAN_TANKER_Y
end
end
local UnitID=self.Positionable:GetID()
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!",tostring(self.name),Channel,Mode,Message,tostring(Bearing),tostring(Duration))})
self.Positionable:CommandActivateBeacon(Type,System,Frequency,UnitID,Channel,Mode,AA,Message,Bearing)
if Duration then
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
function BEACON:ActivateICLS(Channel,Callsign,Duration)
self:F({Channel=Channel,Callsign=Callsign,Duration=Duration})
local UnitID=self.Positionable:GetID()
self:T2({"ICLS BEACON started!"})
self.Positionable:CommandActivateICLS(Channel,UnitID,Callsign)
if Duration then
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
function BEACON:ActivateLink4(Frequency,Morse,Duration)
self:F({Frequency=Frequency,Morse=Morse,Duration=Duration})
local UnitID=self.Positionable:GetID()
self:T2({"LINK4 BEACON started!"})
self.Positionable:CommandActivateLink4(Frequency,UnitID,Morse)
if Duration then
self.Positionable:CommandDeactivateLink4(Duration)
end
return self
end
function BEACON:AATACAN(TACANChannel,Message,Bearing,BeaconDuration)
self:F({TACANChannel,Message,Bearing,BeaconDuration})
local IsValid=true
if not self.Positionable:IsAir()then
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting",self.Positionable})
IsValid=false
end
local Frequency=self:_TACANToFrequency(TACANChannel,"Y")
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
IsValid=false
end
local System
if Bearing then
System=BEACON.System.TACAN_TANKER_Y
else
System=BEACON.System.TACAN_AA_MODE_Y
end
if IsValid then
self:T2({"AA TACAN BEACON started !"})
self.Positionable:SetCommand({
id="ActivateBeacon",
params={
type=BEACON.Type.TACAN,
system=System,
callsign=Message,
AA=true,
frequency=Frequency,
bearing=Bearing,
modeChannel="Y",
}
})
if BeaconDuration then
SCHEDULER:New(nil,
function()
self:StopAATACAN()
end,{},BeaconDuration)
end
end
return self
end
function BEACON:StopAATACAN()
self:F()
if not self.Positionable then
self:E({"Start the beacon first before stoping it !"})
else
self.Positionable:SetCommand({
id='DeactivateBeacon',
params={
}
})
end
end
function BEACON:RadioBeacon(FileName,Frequency,Modulation,Power,BeaconDuration)
self:F({FileName,Frequency,Modulation,Power,BeaconDuration})
local IsValid=false
if type(FileName)=="string"then
if FileName:find(".ogg")or FileName:find(".wav")then
if not FileName:find("l10n/DEFAULT/")then
FileName="l10n/DEFAULT/"..FileName
end
IsValid=true
end
end
if not IsValid then
self:E({"File name invalid. Maybe something wrong with the extension ? ",FileName})
end
if type(Frequency)~="number"and IsValid then
self:E({"Frequency invalid. ",Frequency})
IsValid=false
end
Frequency=Frequency*1000000
if Modulation~=radio.modulation.AM and Modulation~=radio.modulation.FM and IsValid then
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.",Modulation})
IsValid=false
end
if type(Power)~="number"and IsValid then
self:E({"Power is invalid. ",Power})
IsValid=false
end
Power=math.floor(math.abs(Power))
if IsValid then
self:T2({"Activating Beacon on ",Frequency,Modulation})
trigger.action.radioTransmission(FileName,self.Positionable:GetPositionVec3(),Modulation,true,Frequency,Power,tostring(self.ID))
if BeaconDuration then
SCHEDULER:New(nil,
function()
self:StopRadioBeacon()
end,{},BeaconDuration)
end
end
end
function BEACON:StopRadioBeacon()
self:F()
trigger.action.stopRadioTransmission(tostring(self.ID))
return self
end
function BEACON:_TACANToFrequency(TACANChannel,TACANMode)
self:F3({TACANChannel,TACANMode})
if type(TACANChannel)~="number"then
if TACANMode~="X"and TACANMode~="Y"then
return nil
end
end
local A=1151
local B=64
if TACANChannel<64 then
B=1
end
if TACANMode=='Y'then
A=1025
if TACANChannel<64 then
A=1088
end
else
if TACANChannel<64 then
A=962
end
end
return(A+TACANChannel-B)*1000000
end
CONDITION={
ClassName="CONDITION",
lid=nil,
functionsGen={},
functionsAny={},
functionsAll={},
functionCounter=0,
defaultPersist=false,
}
CONDITION.version="0.3.0"
function CONDITION:New(Name)
local self=BASE:Inherit(self,BASE:New())
self.name=Name or"Condition X"
self:SetNoneResult(false)
self.lid=string.format("%s | ",self.name)
return self
end
function CONDITION:SetAny(Any)
self.isAny=Any
return self
end
function CONDITION:SetNegateResult(Negate)
self.negateResult=Negate
return self
end
function CONDITION:SetNoneResult(ReturnValue)
if not ReturnValue then
self.noneResult=false
else
self.noneResult=true
end
return self
end
function CONDITION:SetDefaultPersistence(IsPersistent)
self.defaultPersist=IsPersistent
return self
end
function CONDITION:AddFunction(Function,...)
local condition=self:_CreateCondition(0,Function,...)
table.insert(self.functionsGen,condition)
return condition
end
function CONDITION:AddFunctionAny(Function,...)
local condition=self:_CreateCondition(1,Function,...)
table.insert(self.functionsAny,condition)
return condition
end
function CONDITION:AddFunctionAll(Function,...)
local condition=self:_CreateCondition(2,Function,...)
table.insert(self.functionsAll,condition)
return condition
end
function CONDITION:RemoveFunction(ConditionFunction)
if ConditionFunction then
local data=nil
if ConditionFunction.type==0 then
data=self.functionsGen
elseif ConditionFunction.type==1 then
data=self.functionsAny
elseif ConditionFunction.type==2 then
data=self.functionsAll
end
if data then
for i=#data,1,-1 do
local cf=data[i]
if cf.uid==ConditionFunction.uid then
self:T(self.lid..string.format("Removed ConditionFunction UID=%d",cf.uid))
table.remove(data,i)
return self
end
end
end
end
return self
end
function CONDITION:RemoveNonPersistant()
for i=#self.functionsGen,1,-1 do
local cf=self.functionsGen[i]
if not cf.persistence then
table.remove(self.functionsGen,i)
end
end
for i=#self.functionsAll,1,-1 do
local cf=self.functionsAll[i]
if not cf.persistence then
table.remove(self.functionsAll,i)
end
end
for i=#self.functionsAny,1,-1 do
local cf=self.functionsAny[i]
if not cf.persistence then
table.remove(self.functionsAny,i)
end
end
return self
end
function CONDITION:Evaluate(AnyTrue)
if#self.functionsAll+#self.functionsAny+#self.functionsAll==0 then
return self.noneResult
end
local evalAny=self.isAny
if AnyTrue~=nil then
evalAny=AnyTrue
end
local isGen=nil
if evalAny then
isGen=self:_EvalConditionsAny(self.functionsGen)
else
isGen=self:_EvalConditionsAll(self.functionsGen)
end
local isAny=self:_EvalConditionsAny(self.functionsAny)
local isAll=self:_EvalConditionsAll(self.functionsAll)
local result=isGen and isAny and isAll
if self.negateResult then
result=not result
end
self:T(self.lid..string.format("Evaluate: isGen=%s, isAny=%s, isAll=%s (negate=%s) ==> result=%s",tostring(isGen),tostring(isAny),tostring(isAll),tostring(self.negateResult),tostring(result)))
return result
end
function CONDITION:_EvalConditionsAll(functions)
local gotone=false
for _,_condition in pairs(functions or{})do
local condition=_condition
gotone=true
local istrue=condition.func(unpack(condition.arg))
if not istrue then
return false
end
end
return true
end
function CONDITION:_EvalConditionsAny(functions)
local gotone=false
for _,_condition in pairs(functions or{})do
local condition=_condition
gotone=true
local istrue=condition.func(unpack(condition.arg))
if istrue then
return true
end
end
if gotone then
return false
else
return true
end
end
function CONDITION:_CreateCondition(Ftype,Function,...)
self.functionCounter=self.functionCounter+1
local condition={}
condition.uid=self.functionCounter
condition.type=Ftype or 0
condition.persistence=self.defaultPersist
condition.func=Function
condition.arg={}
if arg then
condition.arg=arg
end
return condition
end
function CONDITION.IsTimeGreater(Time,Absolute)
local Tnow=nil
if Absolute then
Tnow=timer.getAbsTime()
else
Tnow=timer.getTime()
end
if Tnow>Time then
return true
else
return false
end
return nil
end
function CONDITION.IsRandomSuccess(Probability)
Probability=Probability or 50
math.random()
math.random()
math.random()
local N=math.random()*100
if N<Probability then
return true
else
return false
end
end
function CONDITION.ReturnTrue()
return true
end
function CONDITION.ReturnFalse()
return false
end
do
USERFLAG={
ClassName="USERFLAG",
UserFlagName=nil,
}
function USERFLAG:New(UserFlagName)
local self=BASE:Inherit(self,BASE:New())
self.UserFlagName=UserFlagName
return self
end
function USERFLAG:GetName()
return self.UserFlagName
end
function USERFLAG:Set(Number,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,USERFLAG.Set,self,Number)
else
trigger.action.setUserFlag(self.UserFlagName,Number)
end
return self
end
function USERFLAG:Get()
return trigger.misc.getUserFlag(self.UserFlagName)
end
function USERFLAG:Is(Number)
return trigger.misc.getUserFlag(self.UserFlagName)==Number
end
end
REPORT={
ClassName="REPORT",
Title="",
}
function REPORT:New(Title)
local self=BASE:Inherit(self,BASE:New())
self.Report={}
self:SetTitle(Title or"")
self:SetIndent(3)
return self
end
function REPORT:HasText()
return#self.Report>0
end
function REPORT:SetIndent(Indent)
self.Indent=Indent
return self
end
function REPORT:Add(Text)
self.Report[#self.Report+1]=Text
return self
end
function REPORT:AddIndent(Text,Separator)
self.Report[#self.Report+1]=((Separator and Separator..string.rep(" ",self.Indent-1))or string.rep(" ",self.Indent))..Text:gsub("\n","\n"..string.rep(" ",self.Indent))
return self
end
function REPORT:Text(Delimiter)
Delimiter=Delimiter or"\n"
local ReportText=(self.Title~=""and self.Title..Delimiter or self.Title)..table.concat(self.Report,Delimiter)or""
return ReportText
end
function REPORT:SetTitle(Title)
self.Title=Title
return self
end
function REPORT:GetCount()
return#self.Report
end
SCHEDULER={
ClassName="SCHEDULER",
Schedules={},
MasterObject=nil,
ShowTrace=nil,
}
function SCHEDULER:New(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop)
local self=BASE:Inherit(self,BASE:New())
self:F2({Start,Repeat,RandomizeFactor,Stop})
local ScheduleID=nil
self.MasterObject=MasterObject
self.ShowTrace=false
if SchedulerFunction then
ScheduleID=self:Schedule(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop,3)
end
return self,ScheduleID
end
function SCHEDULER:Schedule(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop,TraceLevel,Fsm)
self:F2({Start,Repeat,RandomizeFactor,Stop})
self:T3({SchedulerArguments})
local ObjectName="-"
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
ObjectName=MasterObject.ClassName..MasterObject.ClassID
end
self:F3({"Schedule :",ObjectName,tostring(MasterObject),Start,Repeat,RandomizeFactor,Stop})
self.MasterObject=MasterObject
local ScheduleID=_SCHEDULEDISPATCHER:AddSchedule(self,
SchedulerFunction,
SchedulerArguments,
Start,
Repeat,
RandomizeFactor,
Stop,
TraceLevel or 3,
Fsm
)
self.Schedules[#self.Schedules+1]=ScheduleID
return ScheduleID
end
function SCHEDULER:Start(ScheduleID)
self:F3({ScheduleID})
self:T(string.format("Starting scheduler ID=%s",tostring(ScheduleID)))
_SCHEDULEDISPATCHER:Start(self,ScheduleID)
end
function SCHEDULER:Stop(ScheduleID)
self:F3({ScheduleID})
self:T(string.format("Stopping scheduler ID=%s",tostring(ScheduleID)))
_SCHEDULEDISPATCHER:Stop(self,ScheduleID)
end
function SCHEDULER:Remove(ScheduleID)
self:F3({ScheduleID})
self:T(string.format("Removing scheduler ID=%s",tostring(ScheduleID)))
_SCHEDULEDISPATCHER:RemoveSchedule(self,ScheduleID)
end
function SCHEDULER:Clear()
self:F3()
self:T(string.format("Clearing scheduler"))
_SCHEDULEDISPATCHER:Clear(self)
end
function SCHEDULER:ShowTrace()
_SCHEDULEDISPATCHER:ShowTrace(self)
end
function SCHEDULER:NoTrace()
_SCHEDULEDISPATCHER:NoTrace(self)
end
SCHEDULEDISPATCHER={
ClassName="SCHEDULEDISPATCHER",
CallID=0,
PersistentSchedulers={},
ObjectSchedulers={},
Schedule=nil,
}
function SCHEDULEDISPATCHER:New()
local self=BASE:Inherit(self,BASE:New())
self:F3()
return self
end
function SCHEDULEDISPATCHER:AddSchedule(Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop,TraceLevel,Fsm)
self:F2({Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop,TraceLevel,Fsm})
self.CallID=self.CallID+1
local CallID=self.CallID.."#"..(Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID()or"")or""
self:T2(string.format("Adding schedule #%d CallID=%s",self.CallID,CallID))
self.PersistentSchedulers=self.PersistentSchedulers or{}
self.ObjectSchedulers=self.ObjectSchedulers or setmetatable({},{__mode="v"})
if Scheduler.MasterObject then
self.ObjectSchedulers[CallID]=Scheduler
self:F3({CallID=CallID,ObjectScheduler=tostring(self.ObjectSchedulers[CallID]),MasterObject=tostring(Scheduler.MasterObject)})
else
self.PersistentSchedulers[CallID]=Scheduler
self:F3({CallID=CallID,PersistentScheduler=self.PersistentSchedulers[CallID]})
end
self.Schedule=self.Schedule or setmetatable({},{__mode="k"})
self.Schedule[Scheduler]=self.Schedule[Scheduler]or{}
self.Schedule[Scheduler][CallID]={}
self.Schedule[Scheduler][CallID].Function=ScheduleFunction
self.Schedule[Scheduler][CallID].Arguments=ScheduleArguments
self.Schedule[Scheduler][CallID].StartTime=timer.getTime()+(Start or 0)
self.Schedule[Scheduler][CallID].Start=Start+0.001
self.Schedule[Scheduler][CallID].Repeat=Repeat or 0
self.Schedule[Scheduler][CallID].Randomize=Randomize or 0
self.Schedule[Scheduler][CallID].Stop=Stop
local Info={}
if debug then
TraceLevel=TraceLevel or 2
Info=debug.getinfo(TraceLevel,"nlS")
local name_fsm=debug.getinfo(TraceLevel-1,"n").name
if name_fsm then
Info.name=name_fsm
end
end
self:T3(self.Schedule[Scheduler][CallID])
self.Schedule[Scheduler][CallID].CallHandler=function(Params)
local CallID=Params.CallID
local Info=Params.Info or{}
local Source=Info.source or"?"
local Line=Info.currentline or"?"
local Name=Info.name or"?"
local ErrorHandler=function(errmsg)
env.info("Error in timer function: "..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
local Scheduler=self.ObjectSchedulers[CallID]
if not Scheduler then
Scheduler=self.PersistentSchedulers[CallID]
end
if Scheduler then
local MasterObject=tostring(Scheduler.MasterObject)
local Schedule=self.Schedule[Scheduler][CallID]
local SchedulerObject=Scheduler.MasterObject
local ShowTrace=Scheduler.ShowTrace
local ScheduleFunction=Schedule.Function
local ScheduleArguments=Schedule.Arguments or{}
local Start=Schedule.Start
local Repeat=Schedule.Repeat or 0
local Randomize=Schedule.Randomize or 0
local Stop=Schedule.Stop or 0
local ScheduleID=Schedule.ScheduleID
local Prefix=(Repeat==0)and"--->"or"+++>"
local Status,Result
if SchedulerObject then
local function Timer()
if ShowTrace then
SchedulerObject:T(Prefix..Name..":"..Line.." ("..Source..")")
end
return ScheduleFunction(SchedulerObject,unpack(ScheduleArguments))
end
Status,Result=xpcall(Timer,ErrorHandler)
else
local function Timer()
if ShowTrace then
self:T(Prefix..Name..":"..Line.." ("..Source..")")
end
return ScheduleFunction(unpack(ScheduleArguments))
end
Status,Result=xpcall(Timer,ErrorHandler)
end
local CurrentTime=timer.getTime()
local StartTime=Schedule.StartTime
self:F3({CallID=CallID,ScheduleID=ScheduleID,Master=MasterObject,CurrentTime=CurrentTime,StartTime=StartTime,Start=Start,Repeat=Repeat,Randomize=Randomize,Stop=Stop})
if Status and((Result==nil)or(Result and Result~=false))then
if Repeat~=0 and((Stop==0)or(Stop~=0 and CurrentTime<=StartTime+Stop))then
local ScheduleTime=CurrentTime+Repeat+math.random(-(Randomize*Repeat/2),(Randomize*Repeat/2))+0.0001
return ScheduleTime
else
self:Stop(Scheduler,CallID)
end
else
self:Stop(Scheduler,CallID)
end
else
self:I("<<<>"..Name..":"..Line.." ("..Source..")")
end
return nil
end
self:Start(Scheduler,CallID,Info)
return CallID
end
function SCHEDULEDISPATCHER:RemoveSchedule(Scheduler,CallID)
self:F2({Remove=CallID,Scheduler=Scheduler})
if CallID then
self:Stop(Scheduler,CallID)
self.Schedule[Scheduler][CallID]=nil
end
end
function SCHEDULEDISPATCHER:Start(Scheduler,CallID,Info)
self:F2({Start=CallID,Scheduler=Scheduler})
if CallID then
local Schedule=self.Schedule[Scheduler][CallID]
if not Schedule.ScheduleID then
local Tnow=timer.getTime()
Schedule.StartTime=Tnow
Schedule.ScheduleID=timer.scheduleFunction(Schedule.CallHandler,{CallID=CallID,Info=Info},Tnow+Schedule.Start)
self:T(string.format("Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s",tostring(CallID),tostring(Schedule.ScheduleID)))
end
else
for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do
self:Start(Scheduler,CallID,Info)
end
end
end
function SCHEDULEDISPATCHER:Stop(Scheduler,CallID)
self:F2({Stop=CallID,Scheduler=Scheduler})
if CallID then
local Schedule=self.Schedule[Scheduler][CallID]
if Schedule.ScheduleID then
self:T(string.format("SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s",tostring(CallID),tostring(Schedule.ScheduleID)))
timer.removeFunction(Schedule.ScheduleID)
Schedule.ScheduleID=nil
else
self:T(string.format("Error no ScheduleID for CallID=%s",tostring(CallID)))
end
else
for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do
self:Stop(Scheduler,CallID)
end
end
end
function SCHEDULEDISPATCHER:Clear(Scheduler)
self:F2({Scheduler=Scheduler})
for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do
self:Stop(Scheduler,CallID)
end
end
function SCHEDULEDISPATCHER:ShowTrace(Scheduler)
self:F2({Scheduler=Scheduler})
Scheduler.ShowTrace=true
end
function SCHEDULEDISPATCHER:NoTrace(Scheduler)
self:F2({Scheduler=Scheduler})
Scheduler.ShowTrace=false
end
EVENT={
ClassName="EVENT",
ClassID=0,
MissionEnd=false,
}
world.event.S_EVENT_NEW_CARGO=world.event.S_EVENT_MAX+1000
world.event.S_EVENT_DELETE_CARGO=world.event.S_EVENT_MAX+1001
world.event.S_EVENT_NEW_ZONE=world.event.S_EVENT_MAX+1002
world.event.S_EVENT_DELETE_ZONE=world.event.S_EVENT_MAX+1003
world.event.S_EVENT_NEW_ZONE_GOAL=world.event.S_EVENT_MAX+1004
world.event.S_EVENT_DELETE_ZONE_GOAL=world.event.S_EVENT_MAX+1005
world.event.S_EVENT_REMOVE_UNIT=world.event.S_EVENT_MAX+1006
world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT=world.event.S_EVENT_MAX+1007
EVENTS={
Shot=world.event.S_EVENT_SHOT,
Hit=world.event.S_EVENT_HIT,
Takeoff=world.event.S_EVENT_TAKEOFF,
Land=world.event.S_EVENT_LAND,
Crash=world.event.S_EVENT_CRASH,
Ejection=world.event.S_EVENT_EJECTION,
Refueling=world.event.S_EVENT_REFUELING,
Dead=world.event.S_EVENT_DEAD,
PilotDead=world.event.S_EVENT_PILOT_DEAD,
BaseCaptured=world.event.S_EVENT_BASE_CAPTURED,
MissionStart=world.event.S_EVENT_MISSION_START,
MissionEnd=world.event.S_EVENT_MISSION_END,
TookControl=world.event.S_EVENT_TOOK_CONTROL,
RefuelingStop=world.event.S_EVENT_REFUELING_STOP,
Birth=world.event.S_EVENT_BIRTH,
HumanFailure=world.event.S_EVENT_HUMAN_FAILURE,
EngineStartup=world.event.S_EVENT_ENGINE_STARTUP,
EngineShutdown=world.event.S_EVENT_ENGINE_SHUTDOWN,
PlayerEnterUnit=world.event.S_EVENT_PLAYER_ENTER_UNIT,
PlayerLeaveUnit=world.event.S_EVENT_PLAYER_LEAVE_UNIT,
PlayerComment=world.event.S_EVENT_PLAYER_COMMENT,
ShootingStart=world.event.S_EVENT_SHOOTING_START,
ShootingEnd=world.event.S_EVENT_SHOOTING_END,
MarkAdded=world.event.S_EVENT_MARK_ADDED,
MarkChange=world.event.S_EVENT_MARK_CHANGE,
MarkRemoved=world.event.S_EVENT_MARK_REMOVED,
NewCargo=world.event.S_EVENT_NEW_CARGO,
DeleteCargo=world.event.S_EVENT_DELETE_CARGO,
NewZone=world.event.S_EVENT_NEW_ZONE,
DeleteZone=world.event.S_EVENT_DELETE_ZONE,
NewZoneGoal=world.event.S_EVENT_NEW_ZONE_GOAL,
DeleteZoneGoal=world.event.S_EVENT_DELETE_ZONE_GOAL,
RemoveUnit=world.event.S_EVENT_REMOVE_UNIT,
PlayerEnterAircraft=world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT,
DetailedFailure=world.event.S_EVENT_DETAILED_FAILURE or-1,
Kill=world.event.S_EVENT_KILL or-1,
Score=world.event.S_EVENT_SCORE or-1,
UnitLost=world.event.S_EVENT_UNIT_LOST or-1,
LandingAfterEjection=world.event.S_EVENT_LANDING_AFTER_EJECTION or-1,
ParatrooperLanding=world.event.S_EVENT_PARATROOPER_LENDING or-1,
DiscardChairAfterEjection=world.event.S_EVENT_DISCARD_CHAIR_AFTER_EJECTION or-1,
WeaponAdd=world.event.S_EVENT_WEAPON_ADD or-1,
TriggerZone=world.event.S_EVENT_TRIGGER_ZONE or-1,
LandingQualityMark=world.event.S_EVENT_LANDING_QUALITY_MARK or-1,
BDA=world.event.S_EVENT_BDA or-1,
AIAbortMission=world.event.S_EVENT_AI_ABORT_MISSION or-1,
DayNight=world.event.S_EVENT_DAYNIGHT or-1,
FlightTime=world.event.S_EVENT_FLIGHT_TIME or-1,
SelfKillPilot=world.event.S_EVENT_PLAYER_SELF_KILL_PILOT or-1,
PlayerCaptureAirfield=world.event.S_EVENT_PLAYER_CAPTURE_AIRFIELD or-1,
EmergencyLanding=world.event.S_EVENT_EMERGENCY_LANDING or-1,
UnitCreateTask=world.event.S_EVENT_UNIT_CREATE_TASK or-1,
UnitDeleteTask=world.event.S_EVENT_UNIT_DELETE_TASK or-1,
SimulationStart=world.event.S_EVENT_SIMULATION_START or-1,
WeaponRearm=world.event.S_EVENT_WEAPON_REARM or-1,
WeaponDrop=world.event.S_EVENT_WEAPON_DROP or-1,
}
local _EVENTMETA={
[world.event.S_EVENT_SHOT]={
Order=1,
Side="I",
Event="OnEventShot",
Text="S_EVENT_SHOT"
},
[world.event.S_EVENT_HIT]={
Order=1,
Side="T",
Event="OnEventHit",
Text="S_EVENT_HIT"
},
[world.event.S_EVENT_TAKEOFF]={
Order=1,
Side="I",
Event="OnEventTakeoff",
Text="S_EVENT_TAKEOFF"
},
[world.event.S_EVENT_LAND]={
Order=1,
Side="I",
Event="OnEventLand",
Text="S_EVENT_LAND"
},
[world.event.S_EVENT_CRASH]={
Order=-1,
Side="I",
Event="OnEventCrash",
Text="S_EVENT_CRASH"
},
[world.event.S_EVENT_EJECTION]={
Order=1,
Side="I",
Event="OnEventEjection",
Text="S_EVENT_EJECTION"
},
[world.event.S_EVENT_REFUELING]={
Order=1,
Side="I",
Event="OnEventRefueling",
Text="S_EVENT_REFUELING"
},
[world.event.S_EVENT_DEAD]={
Order=-1,
Side="I",
Event="OnEventDead",
Text="S_EVENT_DEAD"
},
[world.event.S_EVENT_PILOT_DEAD]={
Order=1,
Side="I",
Event="OnEventPilotDead",
Text="S_EVENT_PILOT_DEAD"
},
[world.event.S_EVENT_BASE_CAPTURED]={
Order=1,
Side="I",
Event="OnEventBaseCaptured",
Text="S_EVENT_BASE_CAPTURED"
},
[world.event.S_EVENT_MISSION_START]={
Order=1,
Side="N",
Event="OnEventMissionStart",
Text="S_EVENT_MISSION_START"
},
[world.event.S_EVENT_MISSION_END]={
Order=1,
Side="N",
Event="OnEventMissionEnd",
Text="S_EVENT_MISSION_END"
},
[world.event.S_EVENT_TOOK_CONTROL]={
Order=1,
Side="N",
Event="OnEventTookControl",
Text="S_EVENT_TOOK_CONTROL"
},
[world.event.S_EVENT_REFUELING_STOP]={
Order=1,
Side="I",
Event="OnEventRefuelingStop",
Text="S_EVENT_REFUELING_STOP"
},
[world.event.S_EVENT_BIRTH]={
Order=1,
Side="I",
Event="OnEventBirth",
Text="S_EVENT_BIRTH"
},
[world.event.S_EVENT_HUMAN_FAILURE]={
Order=1,
Side="I",
Event="OnEventHumanFailure",
Text="S_EVENT_HUMAN_FAILURE"
},
[world.event.S_EVENT_ENGINE_STARTUP]={
Order=1,
Side="I",
Event="OnEventEngineStartup",
Text="S_EVENT_ENGINE_STARTUP"
},
[world.event.S_EVENT_ENGINE_SHUTDOWN]={
Order=1,
Side="I",
Event="OnEventEngineShutdown",
Text="S_EVENT_ENGINE_SHUTDOWN"
},
[world.event.S_EVENT_PLAYER_ENTER_UNIT]={
Order=1,
Side="I",
Event="OnEventPlayerEnterUnit",
Text="S_EVENT_PLAYER_ENTER_UNIT"
},
[world.event.S_EVENT_PLAYER_LEAVE_UNIT]={
Order=-1,
Side="I",
Event="OnEventPlayerLeaveUnit",
Text="S_EVENT_PLAYER_LEAVE_UNIT"
},
[world.event.S_EVENT_PLAYER_COMMENT]={
Order=1,
Side="I",
Event="OnEventPlayerComment",
Text="S_EVENT_PLAYER_COMMENT"
},
[world.event.S_EVENT_SHOOTING_START]={
Order=1,
Side="I",
Event="OnEventShootingStart",
Text="S_EVENT_SHOOTING_START"
},
[world.event.S_EVENT_SHOOTING_END]={
Order=1,
Side="I",
Event="OnEventShootingEnd",
Text="S_EVENT_SHOOTING_END"
},
[world.event.S_EVENT_MARK_ADDED]={
Order=1,
Side="I",
Event="OnEventMarkAdded",
Text="S_EVENT_MARK_ADDED"
},
[world.event.S_EVENT_MARK_CHANGE]={
Order=1,
Side="I",
Event="OnEventMarkChange",
Text="S_EVENT_MARK_CHANGE"
},
[world.event.S_EVENT_MARK_REMOVED]={
Order=1,
Side="I",
Event="OnEventMarkRemoved",
Text="S_EVENT_MARK_REMOVED"
},
[EVENTS.NewCargo]={
Order=1,
Event="OnEventNewCargo",
Text="S_EVENT_NEW_CARGO"
},
[EVENTS.DeleteCargo]={
Order=1,
Event="OnEventDeleteCargo",
Text="S_EVENT_DELETE_CARGO"
},
[EVENTS.NewZone]={
Order=1,
Event="OnEventNewZone",
Text="S_EVENT_NEW_ZONE"
},
[EVENTS.DeleteZone]={
Order=1,
Event="OnEventDeleteZone",
Text="S_EVENT_DELETE_ZONE"
},
[EVENTS.NewZoneGoal]={
Order=1,
Event="OnEventNewZoneGoal",
Text="S_EVENT_NEW_ZONE_GOAL"
},
[EVENTS.DeleteZoneGoal]={
Order=1,
Event="OnEventDeleteZoneGoal",
Text="S_EVENT_DELETE_ZONE_GOAL"
},
[EVENTS.RemoveUnit]={
Order=-1,
Event="OnEventRemoveUnit",
Text="S_EVENT_REMOVE_UNIT"
},
[EVENTS.PlayerEnterAircraft]={
Order=1,
Event="OnEventPlayerEnterAircraft",
Text="S_EVENT_PLAYER_ENTER_AIRCRAFT"
},
[EVENTS.DetailedFailure]={
Order=1,
Event="OnEventDetailedFailure",
Text="S_EVENT_DETAILED_FAILURE"
},
[EVENTS.Kill]={
Order=1,
Event="OnEventKill",
Text="S_EVENT_KILL"
},
[EVENTS.Score]={
Order=1,
Event="OnEventScore",
Text="S_EVENT_SCORE"
},
[EVENTS.UnitLost]={
Order=1,
Event="OnEventUnitLost",
Text="S_EVENT_UNIT_LOST"
},
[EVENTS.LandingAfterEjection]={
Order=1,
Event="OnEventLandingAfterEjection",
Text="S_EVENT_LANDING_AFTER_EJECTION"
},
[EVENTS.ParatrooperLanding]={
Order=1,
Event="OnEventParatrooperLanding",
Text="S_EVENT_PARATROOPER_LENDING"
},
[EVENTS.DiscardChairAfterEjection]={
Order=1,
Event="OnEventDiscardChairAfterEjection",
Text="S_EVENT_DISCARD_CHAIR_AFTER_EJECTION"
},
[EVENTS.WeaponAdd]={
Order=1,
Event="OnEventWeaponAdd",
Text="S_EVENT_WEAPON_ADD"
},
[EVENTS.TriggerZone]={
Order=1,
Event="OnEventTriggerZone",
Text="S_EVENT_TRIGGER_ZONE"
},
[EVENTS.LandingQualityMark]={
Order=1,
Event="OnEventLandingQualityMark",
Text="S_EVENT_LANDING_QUALITYMARK"
},
[EVENTS.BDA]={
Order=1,
Event="OnEventBDA",
Text="S_EVENT_BDA"
},
[EVENTS.AIAbortMission]={
Order=1,
Side="I",
Event="OnEventAIAbortMission",
Text="S_EVENT_AI_ABORT_MISSION"
},
[EVENTS.DayNight]={
Order=1,
Event="OnEventDayNight",
Text="S_EVENT_DAYNIGHT"
},
[EVENTS.FlightTime]={
Order=1,
Event="OnEventFlightTime",
Text="S_EVENT_FLIGHT_TIME"
},
[EVENTS.SelfKillPilot]={
Order=1,
Side="I",
Event="OnEventSelfKillPilot",
Text="S_EVENT_PLAYER_SELF_KILL_PILOT"
},
[EVENTS.PlayerCaptureAirfield]={
Order=1,
Event="OnEventPlayerCaptureAirfield",
Text="S_EVENT_PLAYER_CAPTURE_AIRFIELD"
},
[EVENTS.EmergencyLanding]={
Order=1,
Side="I",
Event="OnEventEmergencyLanding",
Text="S_EVENT_EMERGENCY_LANDING"
},
[EVENTS.UnitCreateTask]={
Order=1,
Event="OnEventUnitCreateTask",
Text="S_EVENT_UNIT_CREATE_TASK"
},
[EVENTS.UnitDeleteTask]={
Order=1,
Event="OnEventUnitDeleteTask",
Text="S_EVENT_UNIT_DELETE_TASK"
},
[EVENTS.SimulationStart]={
Order=1,
Event="OnEventSimulationStart",
Text="S_EVENT_SIMULATION_START"
},
[EVENTS.WeaponRearm]={
Order=1,
Side="I",
Event="OnEventWeaponRearm",
Text="S_EVENT_WEAPON_REARM"
},
[EVENTS.WeaponDrop]={
Order=1,
Side="I",
Event="OnEventWeaponDrop",
Text="S_EVENT_WEAPON_DROP"
},
}
function EVENT:New()
local self=BASE:Inherit(self,BASE:New())
self.EventHandler=world.addEventHandler(self)
return self
end
function EVENT:Init(EventID,EventClass)
self:F3({_EVENTMETA[EventID].Text,EventClass})
if not self.Events[EventID]then
self.Events[EventID]={}
end
local EventPriority=EventClass:GetEventPriority()
if not self.Events[EventID][EventPriority]then
self.Events[EventID][EventPriority]=setmetatable({},{__mode="k"})
end
if not self.Events[EventID][EventPriority][EventClass]then
self.Events[EventID][EventPriority][EventClass]={}
end
return self.Events[EventID][EventPriority][EventClass]
end
function EVENT:RemoveEvent(EventClass,EventID)
self:F2({"Removing subscription for class: ",EventClass:GetClassNameAndID()})
local EventPriority=EventClass:GetEventPriority()
self.Events=self.Events or{}
self.Events[EventID]=self.Events[EventID]or{}
self.Events[EventID][EventPriority]=self.Events[EventID][EventPriority]or{}
self.Events[EventID][EventPriority][EventClass]=nil
return self
end
function EVENT:Reset(EventObject)
self:F({"Resetting subscriptions for class: ",EventObject:GetClassNameAndID()})
local EventPriority=EventObject:GetEventPriority()
for EventID,EventData in pairs(self.Events)do
if self.EventsDead then
if self.EventsDead[EventID]then
if self.EventsDead[EventID][EventPriority]then
if self.EventsDead[EventID][EventPriority][EventObject]then
self.Events[EventID][EventPriority][EventObject]=self.EventsDead[EventID][EventPriority][EventObject]
end
end
end
end
end
end
function EVENT:RemoveAll(EventClass)
local EventClassName=EventClass:GetClassNameAndID()
local EventPriority=EventClass:GetEventPriority()
for EventID,EventData in pairs(self.Events)do
self.Events[EventID][EventPriority][EventClass]=nil
end
return self
end
function EVENT:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EventID)
self:F2(EventTemplate.name)
for EventUnitID,EventUnit in pairs(EventTemplate.units)do
self:OnEventForUnit(EventUnit.name,EventFunction,EventClass,EventID)
end
return self
end
function EVENT:OnEventGeneric(EventFunction,EventClass,EventID)
self:F2({EventID,EventClass,EventFunction})
local EventData=self:Init(EventID,EventClass)
EventData.EventFunction=EventFunction
return self
end
function EVENT:OnEventForUnit(UnitName,EventFunction,EventClass,EventID)
self:F2(UnitName)
local EventData=self:Init(EventID,EventClass)
EventData.EventUnit=true
EventData.EventFunction=EventFunction
return self
end
function EVENT:OnEventForGroup(GroupName,EventFunction,EventClass,EventID,...)
local Event=self:Init(EventID,EventClass)
Event.EventGroup=true
Event.EventFunction=EventFunction
Event.Params=arg
return self
end
do
function EVENT:OnBirthForTemplate(EventTemplate,EventFunction,EventClass)
self:F2(EventTemplate.name)
self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Birth)
return self
end
end
do
function EVENT:OnCrashForTemplate(EventTemplate,EventFunction,EventClass)
self:F2(EventTemplate.name)
self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Crash)
return self
end
end
do
function EVENT:OnDeadForTemplate(EventTemplate,EventFunction,EventClass)
self:F2(EventTemplate.name)
self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Dead)
return self
end
end
do
function EVENT:OnLandForTemplate(EventTemplate,EventFunction,EventClass)
self:F2(EventTemplate.name)
self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Land)
return self
end
end
do
function EVENT:OnTakeOffForTemplate(EventTemplate,EventFunction,EventClass)
self:F2(EventTemplate.name)
self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Takeoff)
return self
end
end
do
function EVENT:OnEngineShutDownForTemplate(EventTemplate,EventFunction,EventClass)
self:F2(EventTemplate.name)
self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.EngineShutdown)
return self
end
end
do
function EVENT:CreateEventNewCargo(Cargo)
self:F({Cargo})
local Event={
id=EVENTS.NewCargo,
time=timer.getTime(),
cargo=Cargo,
}
world.onEvent(Event)
end
function EVENT:CreateEventDeleteCargo(Cargo)
self:F({Cargo})
local Event={
id=EVENTS.DeleteCargo,
time=timer.getTime(),
cargo=Cargo,
}
world.onEvent(Event)
end
function EVENT:CreateEventNewZone(Zone)
self:F({Zone})
local Event={
id=EVENTS.NewZone,
time=timer.getTime(),
zone=Zone,
}
world.onEvent(Event)
end
function EVENT:CreateEventDeleteZone(Zone)
self:F({Zone})
local Event={
id=EVENTS.DeleteZone,
time=timer.getTime(),
zone=Zone,
}
world.onEvent(Event)
end
function EVENT:CreateEventNewZoneGoal(ZoneGoal)
self:F({ZoneGoal})
local Event={
id=EVENTS.NewZoneGoal,
time=timer.getTime(),
ZoneGoal=ZoneGoal,
}
world.onEvent(Event)
end
function EVENT:CreateEventDeleteZoneGoal(ZoneGoal)
self:F({ZoneGoal})
local Event={
id=EVENTS.DeleteZoneGoal,
time=timer.getTime(),
ZoneGoal=ZoneGoal,
}
world.onEvent(Event)
end
function EVENT:CreateEventPlayerEnterUnit(PlayerUnit)
self:F({PlayerUnit})
local Event={
id=EVENTS.PlayerEnterUnit,
time=timer.getTime(),
initiator=PlayerUnit:GetDCSObject()
}
world.onEvent(Event)
end
function EVENT:CreateEventPlayerEnterAircraft(PlayerUnit)
self:F({PlayerUnit})
local Event={
id=EVENTS.PlayerEnterAircraft,
time=timer.getTime(),
initiator=PlayerUnit:GetDCSObject()
}
world.onEvent(Event)
end
end
function EVENT:onEvent(Event)
local ErrorHandler=function(errmsg)
env.info("Error in SCHEDULER function:"..errmsg)
if BASE.Debug~=nil then
env.info(debug.traceback())
end
return errmsg
end
local EventMeta=_EVENTMETA[Event.id]
if EventMeta then
if self and self.Events and self.Events[Event.id]and self.MissionEnd==false and(Event.initiator~=nil or(Event.initiator==nil and Event.id~=EVENTS.PlayerLeaveUnit))then
if Event.id and Event.id==EVENTS.MissionEnd then
self.MissionEnd=true
end
if Event.initiator then
Event.IniObjectCategory=Event.initiator:getCategory()
if Event.IniObjectCategory==Object.Category.STATIC then
if Event.id==31 then
Event.IniDCSUnit=Event.initiator
local ID=Event.initiator.id_
Event.IniDCSUnitName=string.format("Ejected Pilot ID %s",tostring(ID))
Event.IniUnitName=Event.IniDCSUnitName
Event.IniCoalition=0
Event.IniCategory=0
Event.IniTypeName="Ejected Pilot"
elseif Event.id==33 then
Event.IniDCSUnit=Event.initiator
local ID=Event.initiator.id_
Event.IniDCSUnitName=string.format("Ejection Seat ID %s",tostring(ID))
Event.IniUnitName=Event.IniDCSUnitName
Event.IniCoalition=0
Event.IniCategory=0
Event.IniTypeName="Ejection Seat"
else
Event.IniDCSUnit=Event.initiator
Event.IniDCSUnitName=Event.IniDCSUnit:getName()
Event.IniUnitName=Event.IniDCSUnitName
Event.IniUnit=STATIC:FindByName(Event.IniDCSUnitName,false)
Event.IniCoalition=Event.IniDCSUnit:getCoalition()
Event.IniCategory=Event.IniDCSUnit:getDesc().category
Event.IniTypeName=Event.IniDCSUnit:getTypeName()
end
local Unit=UNIT:FindByName(Event.IniDCSUnitName)
if Unit then
Event.IniObjectCategory=Object.Category.UNIT
end
end
if Event.IniObjectCategory==Object.Category.UNIT then
Event.IniDCSUnit=Event.initiator
Event.IniDCSUnitName=Event.IniDCSUnit:getName()
Event.IniUnitName=Event.IniDCSUnitName
Event.IniDCSGroup=Event.IniDCSUnit:getGroup()
Event.IniUnit=UNIT:FindByName(Event.IniDCSUnitName)
if not Event.IniUnit then
Event.IniUnit=CLIENT:FindByName(Event.IniDCSUnitName,'',true)
end
Event.IniDCSGroupName=Event.IniUnit and Event.IniUnit.GroupName or""
if Event.IniDCSGroup and Event.IniDCSGroup:isExist()then
Event.IniDCSGroupName=Event.IniDCSGroup:getName()
Event.IniGroup=GROUP:FindByName(Event.IniDCSGroupName)
Event.IniGroupName=Event.IniDCSGroupName
end
Event.IniPlayerName=Event.IniDCSUnit:getPlayerName()
Event.IniCoalition=Event.IniDCSUnit:getCoalition()
Event.IniTypeName=Event.IniDCSUnit:getTypeName()
Event.IniCategory=Event.IniDCSUnit:getDesc().category
end
if Event.IniObjectCategory==Object.Category.CARGO then
Event.IniDCSUnit=Event.initiator
Event.IniDCSUnitName=Event.IniDCSUnit:getName()
Event.IniUnitName=Event.IniDCSUnitName
Event.IniUnit=CARGO:FindByName(Event.IniDCSUnitName)
Event.IniCoalition=Event.IniDCSUnit:getCoalition()
Event.IniCategory=Event.IniDCSUnit:getDesc().category
Event.IniTypeName=Event.IniDCSUnit:getTypeName()
end
if Event.IniObjectCategory==Object.Category.SCENERY then
Event.IniDCSUnit=Event.initiator
Event.IniDCSUnitName=Event.IniDCSUnit:getName()
Event.IniUnitName=Event.IniDCSUnitName
Event.IniUnit=SCENERY:Register(Event.IniDCSUnitName,Event.initiator)
Event.IniCategory=Event.IniDCSUnit:getDesc().category
Event.IniTypeName=Event.initiator:isExist()and Event.IniDCSUnit:getTypeName()or"SCENERY"
end
if Event.IniObjectCategory==Object.Category.BASE then
Event.IniDCSUnit=Event.initiator
Event.IniDCSUnitName=Event.IniDCSUnit:getName()
Event.IniUnitName=Event.IniDCSUnitName
Event.IniUnit=AIRBASE:FindByName(Event.IniDCSUnitName)
Event.IniCoalition=Event.IniDCSUnit:getCoalition()
Event.IniCategory=Event.IniDCSUnit:getDesc().category
Event.IniTypeName=Event.IniDCSUnit:getTypeName()
if not Event.IniUnit then
_DATABASE:_RegisterAirbase(Event.initiator)
Event.IniUnit=AIRBASE:FindByName(Event.IniDCSUnitName)
end
end
end
if Event.target then
Event.TgtObjectCategory=Event.target:getCategory()
if Event.TgtObjectCategory==Object.Category.UNIT then
Event.TgtDCSUnit=Event.target
Event.TgtDCSGroup=Event.TgtDCSUnit:getGroup()
Event.TgtDCSUnitName=Event.TgtDCSUnit:getName()
Event.TgtUnitName=Event.TgtDCSUnitName
Event.TgtUnit=UNIT:FindByName(Event.TgtDCSUnitName)
Event.TgtDCSGroupName=""
if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist()then
Event.TgtDCSGroupName=Event.TgtDCSGroup:getName()
Event.TgtGroup=GROUP:FindByName(Event.TgtDCSGroupName)
Event.TgtGroupName=Event.TgtDCSGroupName
end
Event.TgtPlayerName=Event.TgtDCSUnit:getPlayerName()
Event.TgtCoalition=Event.TgtDCSUnit:getCoalition()
Event.TgtCategory=Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName=Event.TgtDCSUnit:getTypeName()
end
if Event.TgtObjectCategory==Object.Category.STATIC then
Event.TgtDCSUnit=Event.target
if Event.target:isExist()and Event.id~=33 and not Event.TgtObjectCategory==Object.Category.COORDINATE then
Event.TgtDCSUnitName=Event.TgtDCSUnit:getName()
Event.TgtUnitName=Event.TgtDCSUnitName
Event.TgtUnit=STATIC:FindByName(Event.TgtDCSUnitName,false)
Event.TgtCoalition=Event.TgtDCSUnit:getCoalition()
Event.TgtCategory=Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName=Event.TgtDCSUnit:getTypeName()
else
Event.TgtDCSUnitName=string.format("No target object for Event ID %s",tostring(Event.id))
Event.TgtUnitName=Event.TgtDCSUnitName
Event.TgtUnit=nil
Event.TgtCoalition=0
Event.TgtCategory=0
if Event.id==6 then
Event.TgtTypeName="Ejected Pilot"
Event.TgtDCSUnitName=string.format("Ejected Pilot ID %s",tostring(Event.IniDCSUnitName))
Event.TgtUnitName=Event.TgtDCSUnitName
elseif Event.id==33 then
Event.TgtTypeName="Ejection Seat"
Event.TgtDCSUnitName=string.format("Ejection Seat ID %s",tostring(Event.IniDCSUnitName))
Event.TgtUnitName=Event.TgtDCSUnitName
else
Event.TgtTypeName="Static"
end
end
end
if Event.TgtObjectCategory==Object.Category.SCENERY then
Event.TgtDCSUnit=Event.target
Event.TgtDCSUnitName=Event.TgtDCSUnit:getName()
Event.TgtUnitName=Event.TgtDCSUnitName
Event.TgtUnit=SCENERY:Register(Event.TgtDCSUnitName,Event.target)
Event.TgtCategory=Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName=Event.TgtDCSUnit:getTypeName()
end
end
if Event.weapon then
Event.Weapon=Event.weapon
Event.WeaponName=Event.Weapon:getTypeName()
Event.WeaponUNIT=CLIENT:Find(Event.Weapon,'',true)
Event.WeaponPlayerName=Event.WeaponUNIT and Event.Weapon:getPlayerName()
Event.WeaponCoalition=Event.WeaponUNIT and Event.Weapon:getCoalition()
Event.WeaponCategory=Event.WeaponUNIT and Event.Weapon:getDesc().category
Event.WeaponTypeName=Event.WeaponUNIT and Event.Weapon:getTypeName()
end
if Event.place then
if Event.id==EVENTS.LandingAfterEjection then
else
if Event.place:isExist()and Event.place:getCategory()~=Object.Category.SCENERY then
Event.Place=AIRBASE:Find(Event.place)
Event.PlaceName=Event.Place:GetName()
end
end
end
if Event.idx then
Event.MarkID=Event.idx
Event.MarkVec3=Event.pos
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
Event.MarkText=Event.text
Event.MarkCoalition=Event.coalition
Event.MarkGroupID=Event.groupID
end
if Event.cargo then
Event.Cargo=Event.cargo
Event.CargoName=Event.cargo.Name
end
if Event.zone then
Event.Zone=Event.zone
Event.ZoneName=Event.zone.ZoneName
end
local PriorityOrder=EventMeta.Order
local PriorityBegin=PriorityOrder==-1 and 5 or 1
local PriorityEnd=PriorityOrder==-1 and 1 or 5
for EventPriority=PriorityBegin,PriorityEnd,PriorityOrder do
if self.Events[Event.id][EventPriority]then
for EventClass,EventData in pairs(self.Events[Event.id][EventPriority])do
Event.IniGroup=Event.IniGroup or GROUP:FindByName(Event.IniDCSGroupName)
Event.TgtGroup=Event.TgtGroup or GROUP:FindByName(Event.TgtDCSGroupName)
if EventData.EventUnit then
if EventClass:IsAlive()or
Event.id==EVENTS.PlayerEnterUnit or
Event.id==EVENTS.Crash or
Event.id==EVENTS.Dead or
Event.id==EVENTS.RemoveUnit or
Event.id==EVENTS.UnitLost then
local UnitName=EventClass:GetName()
if(EventMeta.Side=="I"and UnitName==Event.IniDCSUnitName)or
(EventMeta.Side=="T"and UnitName==Event.TgtDCSUnitName)then
if EventData.EventFunction then
local Result,Value=xpcall(
function()
return EventData.EventFunction(EventClass,Event)
end,ErrorHandler)
else
local EventFunction=EventClass[EventMeta.Event]
if EventFunction and type(EventFunction)=="function"then
local Result,Value=xpcall(
function()
return EventFunction(EventClass,Event)
end,ErrorHandler)
end
end
end
else
self:RemoveEvent(EventClass,Event.id)
end
else
if EventData.EventGroup then
if EventClass:IsAlive()or
Event.id==EVENTS.PlayerEnterUnit or
Event.id==EVENTS.Crash or
Event.id==EVENTS.Dead or
Event.id==EVENTS.RemoveUnit or
Event.id==EVENTS.UnitLost then
local GroupName=EventClass:GetName()
if(EventMeta.Side=="I"and GroupName==Event.IniDCSGroupName)or
(EventMeta.Side=="T"and GroupName==Event.TgtDCSGroupName)then
if EventData.EventFunction then
local Result,Value=xpcall(
function()
return EventData.EventFunction(EventClass,Event,unpack(EventData.Params))
end,ErrorHandler)
else
local EventFunction=EventClass[EventMeta.Event]
if EventFunction and type(EventFunction)=="function"then
local Result,Value=xpcall(
function()
return EventFunction(EventClass,Event,unpack(EventData.Params))
end,ErrorHandler)
end
end
end
else
end
else
if not EventData.EventUnit then
if EventData.EventFunction then
local Result,Value=xpcall(
function()
return EventData.EventFunction(EventClass,Event)
end,ErrorHandler)
else
local EventFunction=EventClass[EventMeta.Event]
if EventFunction and type(EventFunction)=="function"then
local Result,Value=xpcall(
function()
local Result,Value=EventFunction(EventClass,Event)
return Result,Value
end,ErrorHandler)
end
end
end
end
end
end
end
end
if Event.id==EVENTS.DeleteCargo then
Event.Cargo.NoDestroy=nil
end
else
self:T({EventMeta.Text,Event})
end
else
self:E(string.format("WARNING: Could not get EVENTMETA data for event ID=%d! Is this an unknown/new DCS event?",tostring(Event.id)))
end
Event=nil
end
EVENTHANDLER={
ClassName="EVENTHANDLER",
ClassID=0,
}
function EVENTHANDLER:New()
self=BASE:Inherit(self,BASE:New())
return self
end
SETTINGS={
ClassName="SETTINGS",
ShowPlayerMenu=true,
MenuShort=false,
MenuStatic=false,
}
SETTINGS.__Enum={}
SETTINGS.__Enum.Era={
WWII=1,
Korea=2,
Cold=3,
Modern=4,
}
do
function SETTINGS:Set(PlayerName)
if PlayerName==nil then
local self=BASE:Inherit(self,BASE:New())
self:SetMetric()
self:SetA2G_BR()
self:SetA2A_BRAA()
self:SetLL_Accuracy(3)
self:SetMGRS_Accuracy(5)
self:SetMessageTime(MESSAGE.Type.Briefing,180)
self:SetMessageTime(MESSAGE.Type.Detailed,60)
self:SetMessageTime(MESSAGE.Type.Information,30)
self:SetMessageTime(MESSAGE.Type.Overview,60)
self:SetMessageTime(MESSAGE.Type.Update,15)
self:SetEraModern()
self:SetLocale("en")
return self
else
local Settings=_DATABASE:GetPlayerSettings(PlayerName)
if not Settings then
Settings=BASE:Inherit(self,BASE:New())
_DATABASE:SetPlayerSettings(PlayerName,Settings)
end
return Settings
end
end
function SETTINGS:SetMenutextShort(onoff)
_SETTINGS.MenuShort=onoff
end
function SETTINGS:SetMenuStatic(onoff)
_SETTINGS.MenuStatic=onoff
end
function SETTINGS:SetMetric()
self.Metric=true
end
function SETTINGS:SetLocale(Locale)
self.Locale=Locale or"en"
end
function SETTINGS:GetLocale()
return self.Locale or _SETTINGS:GetLocale()
end
function SETTINGS:IsMetric()
return(self.Metric~=nil and self.Metric==true)or(self.Metric==nil and _SETTINGS:IsMetric())
end
function SETTINGS:SetImperial()
self.Metric=false
end
function SETTINGS:IsImperial()
return(self.Metric~=nil and self.Metric==false)or(self.Metric==nil and _SETTINGS:IsImperial())
end
function SETTINGS:SetLL_Accuracy(LL_Accuracy)
self.LL_Accuracy=LL_Accuracy
end
function SETTINGS:GetLL_DDM_Accuracy()
return self.LL_DDM_Accuracy or _SETTINGS:GetLL_DDM_Accuracy()
end
function SETTINGS:SetMGRS_Accuracy(MGRS_Accuracy)
self.MGRS_Accuracy=MGRS_Accuracy
end
function SETTINGS:GetMGRS_Accuracy()
return self.MGRS_Accuracy or _SETTINGS:GetMGRS_Accuracy()
end
function SETTINGS:SetMessageTime(MessageType,MessageTime)
self.MessageTypeTimings=self.MessageTypeTimings or{}
self.MessageTypeTimings[MessageType]=MessageTime
end
function SETTINGS:GetMessageTime(MessageType)
return(self.MessageTypeTimings and self.MessageTypeTimings[MessageType])or _SETTINGS:GetMessageTime(MessageType)
end
function SETTINGS:SetA2G_LL_DMS()
self.A2GSystem="LL DMS"
end
function SETTINGS:SetA2G_LL_DDM()
self.A2GSystem="LL DDM"
end
function SETTINGS:IsA2G_LL_DMS()
return(self.A2GSystem and self.A2GSystem=="LL DMS")or(not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS())
end
function SETTINGS:IsA2G_LL_DDM()
return(self.A2GSystem and self.A2GSystem=="LL DDM")or(not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM())
end
function SETTINGS:SetA2G_MGRS()
self.A2GSystem="MGRS"
end
function SETTINGS:IsA2G_MGRS()
return(self.A2GSystem and self.A2GSystem=="MGRS")or(not self.A2GSystem and _SETTINGS:IsA2G_MGRS())
end
function SETTINGS:SetA2G_BR()
self.A2GSystem="BR"
end
function SETTINGS:IsA2G_BR()
return(self.A2GSystem and self.A2GSystem=="BR")or(not self.A2GSystem and _SETTINGS:IsA2G_BR())
end
function SETTINGS:SetA2A_BRAA()
self.A2ASystem="BRAA"
end
function SETTINGS:IsA2A_BRAA()
return(self.A2ASystem and self.A2ASystem=="BRAA")or(not self.A2ASystem and _SETTINGS:IsA2A_BRAA())
end
function SETTINGS:SetA2A_BULLS()
self.A2ASystem="BULLS"
end
function SETTINGS:IsA2A_BULLS()
return(self.A2ASystem and self.A2ASystem=="BULLS")or(not self.A2ASystem and _SETTINGS:IsA2A_BULLS())
end
function SETTINGS:SetA2A_LL_DMS()
self.A2ASystem="LL DMS"
end
function SETTINGS:SetA2A_LL_DDM()
self.A2ASystem="LL DDM"
end
function SETTINGS:IsA2A_LL_DMS()
return(self.A2ASystem and self.A2ASystem=="LL DMS")or(not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS())
end
function SETTINGS:IsA2A_LL_DDM()
return(self.A2ASystem and self.A2ASystem=="LL DDM")or(not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM())
end
function SETTINGS:SetA2A_MGRS()
self.A2ASystem="MGRS"
end
function SETTINGS:IsA2A_MGRS()
return(self.A2ASystem and self.A2ASystem=="MGRS")or(not self.A2ASystem and _SETTINGS:IsA2A_MGRS())
end
function SETTINGS:SetSystemMenu(MenuGroup,RootMenu)
local MenuText="System Settings"
local MenuTime=timer.getTime()
local SettingsMenu=MENU_GROUP:New(MenuGroup,MenuText,RootMenu):SetTime(MenuTime)
local text="A2G Coordinate System"
if _SETTINGS.MenuShort then
text="A2G Coordinates"
end
local A2GCoordinateMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime)
if not self:IsA2G_LL_DMS()then
local text="Lat/Lon Degree Min Sec (LL DMS)"
if _SETTINGS.MenuShort then
text="LL DMS"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime)
end
if not self:IsA2G_LL_DDM()then
local text="Lat/Lon Degree Dec Min (LL DDM)"
if _SETTINGS.MenuShort then
text="LL DDM"
end
MENU_GROUP_COMMAND:New(MenuGroup,"Lat/Lon Degree Dec Min (LL DDM)",A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime)
end
if self:IsA2G_LL_DDM()then
local text1="LL DDM Accuracy 1"
local text2="LL DDM Accuracy 2"
local text3="LL DDM Accuracy 3"
if _SETTINGS.MenuShort then
text1="LL DDM"
end
MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 1",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 2",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 3",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
end
if not self:IsA2G_BR()then
local text="Bearing, Range (BR)"
if _SETTINGS.MenuShort then
text="BR"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"BR"):SetTime(MenuTime)
end
if not self:IsA2G_MGRS()then
local text="Military Grid (MGRS)"
if _SETTINGS.MenuShort then
text="MGRS"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"MGRS"):SetTime(MenuTime)
end
if self:IsA2G_MGRS()then
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 1",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 2",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 3",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 4",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,4):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 5",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,5):SetTime(MenuTime)
end
local text="A2A Coordinate System"
if _SETTINGS.MenuShort then
text="A2A Coordinates"
end
local A2ACoordinateMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime)
if not self:IsA2A_LL_DMS()then
local text="Lat/Lon Degree Min Sec (LL DMS)"
if _SETTINGS.MenuShort then
text="LL DMS"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime)
end
if not self:IsA2A_LL_DDM()then
local text="Lat/Lon Degree Dec Min (LL DDM)"
if _SETTINGS.MenuShort then
text="LL DDM"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime)
end
if self:IsA2A_LL_DDM()or self:IsA2A_LL_DMS()then
MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 0",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,0):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 1",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 2",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 3",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
end
if not self:IsA2A_BULLS()then
local text="Bullseye (BULLS)"
if _SETTINGS.MenuShort then
text="Bulls"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BULLS"):SetTime(MenuTime)
end
if not self:IsA2A_BRAA()then
local text="Bearing Range Altitude Aspect (BRAA)"
if _SETTINGS.MenuShort then
text="BRAA"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BRAA"):SetTime(MenuTime)
end
if not self:IsA2A_MGRS()then
local text="Military Grid (MGRS)"
if _SETTINGS.MenuShort then
text="MGRS"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"MGRS"):SetTime(MenuTime)
end
if self:IsA2A_MGRS()then
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 1",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 2",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 3",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 4",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,4):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 5",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,5):SetTime(MenuTime)
end
local text="Measures and Weights System"
if _SETTINGS.MenuShort then
text="Unit System"
end
local MetricsMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime)
if self:IsMetric()then
local text="Imperial (Miles,Feet)"
if _SETTINGS.MenuShort then
text="Imperial"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,false):SetTime(MenuTime)
end
if self:IsImperial()then
local text="Metric (Kilometers,Meters)"
if _SETTINGS.MenuShort then
text="Metric"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,true):SetTime(MenuTime)
end
local text="Messages and Reports"
if _SETTINGS.MenuShort then
text="Messages & Reports"
end
local MessagesMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime)
local UpdateMessagesMenu=MENU_GROUP:New(MenuGroup,"Update Messages",MessagesMenu):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"Off",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,0):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"5 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,5):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"10 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,10):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,15):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,30):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,60):SetTime(MenuTime)
local InformationMessagesMenu=MENU_GROUP:New(MenuGroup,"Information Messages",MessagesMenu):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"5 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,5):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"10 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,10):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,15):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,30):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,60):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,120):SetTime(MenuTime)
local BriefingReportsMenu=MENU_GROUP:New(MenuGroup,"Briefing Reports",MessagesMenu):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,15):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,30):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,60):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,120):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,180):SetTime(MenuTime)
local OverviewReportsMenu=MENU_GROUP:New(MenuGroup,"Overview Reports",MessagesMenu):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,15):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,30):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,60):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,120):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,180):SetTime(MenuTime)
local DetailedReportsMenu=MENU_GROUP:New(MenuGroup,"Detailed Reports",MessagesMenu):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,15):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,30):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,60):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,120):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,180):SetTime(MenuTime)
SettingsMenu:Remove(MenuTime)
return self
end
function SETTINGS:SetPlayerMenuOn()
self.ShowPlayerMenu=true
end
function SETTINGS:SetPlayerMenuOff()
self.ShowPlayerMenu=false
end
function SETTINGS:SetPlayerMenu(PlayerUnit)
if _SETTINGS.ShowPlayerMenu==true then
local PlayerGroup=PlayerUnit:GetGroup()
local PlayerName=PlayerUnit:GetPlayerName()
local PlayerNames=PlayerGroup:GetPlayerNames()
local PlayerMenu=MENU_GROUP:New(PlayerGroup,'Settings "'..PlayerName..'"')
self.PlayerMenu=PlayerMenu
self:I(string.format("Setting menu for player %s",tostring(PlayerName)))
local submenu=MENU_GROUP:New(PlayerGroup,"LL Accuracy",PlayerMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"LL 0 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,0)
MENU_GROUP_COMMAND:New(PlayerGroup,"LL 1 Decimal",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1)
MENU_GROUP_COMMAND:New(PlayerGroup,"LL 2 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2)
MENU_GROUP_COMMAND:New(PlayerGroup,"LL 3 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3)
MENU_GROUP_COMMAND:New(PlayerGroup,"LL 4 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,4)
local submenu=MENU_GROUP:New(PlayerGroup,"MGRS Accuracy",PlayerMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 0",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,0)
MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 1",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1)
MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 2",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2)
MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 3",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3)
MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 4",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,4)
MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 5",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,5)
local text="A2G Coordinate System"
if _SETTINGS.MenuShort then
text="A2G Coordinates"
end
local A2GCoordinateMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu)
if not self:IsA2G_LL_DMS()or _SETTINGS.MenuStatic then
local text="Lat/Lon Degree Min Sec (LL DMS)"
if _SETTINGS.MenuShort then
text="A2G LL DMS"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DMS")
end
if not self:IsA2G_LL_DDM()or _SETTINGS.MenuStatic then
local text="Lat/Lon Degree Dec Min (LL DDM)"
if _SETTINGS.MenuShort then
text="A2G LL DDM"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DDM")
end
if not self:IsA2G_BR()or _SETTINGS.MenuStatic then
local text="Bearing, Range (BR)"
if _SETTINGS.MenuShort then
text="A2G BR"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"BR")
end
if not self:IsA2G_MGRS()or _SETTINGS.MenuStatic then
local text="Military Grid (MGRS)"
if _SETTINGS.MenuShort then
text="A2G MGRS"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"MGRS")
end
local text="A2A Coordinate System"
if _SETTINGS.MenuShort then
text="A2A Coordinates"
end
local A2ACoordinateMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu)
if not self:IsA2A_LL_DMS()or _SETTINGS.MenuStatic then
local text="Lat/Lon Degree Min Sec (LL DMS)"
if _SETTINGS.MenuShort then
text="A2A LL DMS"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DMS")
end
if not self:IsA2A_LL_DDM()or _SETTINGS.MenuStatic then
local text="Lat/Lon Degree Dec Min (LL DDM)"
if _SETTINGS.MenuShort then
text="A2A LL DDM"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DDM")
end
if not self:IsA2A_BULLS()or _SETTINGS.MenuStatic then
local text="Bullseye (BULLS)"
if _SETTINGS.MenuShort then
text="A2A BULLS"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"BULLS")
end
if not self:IsA2A_BRAA()or _SETTINGS.MenuStatic then
local text="Bearing Range Altitude Aspect (BRAA)"
if _SETTINGS.MenuShort then
text="A2A BRAA"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"BRAA")
end
if not self:IsA2A_MGRS()or _SETTINGS.MenuStatic then
local text="Military Grid (MGRS)"
if _SETTINGS.MenuShort then
text="A2A MGRS"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"MGRS")
end
local text="Measures and Weights System"
if _SETTINGS.MenuShort then
text="Unit System"
end
local MetricsMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu)
if self:IsMetric()or _SETTINGS.MenuStatic then
local text="Imperial (Miles,Feet)"
if _SETTINGS.MenuShort then
text="Imperial"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,MetricsMenu,self.MenuGroupMWSystem,self,PlayerUnit,PlayerGroup,PlayerName,false)
end
if self:IsImperial()or _SETTINGS.MenuStatic then
local text="Metric (Kilometers,Meters)"
if _SETTINGS.MenuShort then
text="Metric"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,MetricsMenu,self.MenuGroupMWSystem,self,PlayerUnit,PlayerGroup,PlayerName,true)
end
local text="Messages and Reports"
if _SETTINGS.MenuShort then
text="Messages & Reports"
end
local MessagesMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu)
local UpdateMessagesMenu=MENU_GROUP:New(PlayerGroup,"Update Messages",MessagesMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"Updates Off",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,0)
MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 5 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,5)
MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 10 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,10)
MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 15 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,15)
MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 30 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,30)
MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 1 min",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,60)
local InformationMessagesMenu=MENU_GROUP:New(PlayerGroup,"Info Messages",MessagesMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"Info 5 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,5)
MENU_GROUP_COMMAND:New(PlayerGroup,"Info 10 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,10)
MENU_GROUP_COMMAND:New(PlayerGroup,"Info 15 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,15)
MENU_GROUP_COMMAND:New(PlayerGroup,"Info 30 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,30)
MENU_GROUP_COMMAND:New(PlayerGroup,"Info 1 min",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,60)
MENU_GROUP_COMMAND:New(PlayerGroup,"Info 2 min",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,120)
local BriefingReportsMenu=MENU_GROUP:New(PlayerGroup,"Briefing Reports",MessagesMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 15 sec",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,15)
MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 30 sec",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,30)
MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 1 min",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,60)
MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 2 min",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,120)
MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 3 min",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,180)
local OverviewReportsMenu=MENU_GROUP:New(PlayerGroup,"Overview Reports",MessagesMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 15 sec",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,15)
MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 30 sec",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,30)
MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 1 min",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,60)
MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 2 min",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,120)
MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 3 min",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,180)
local DetailedReportsMenu=MENU_GROUP:New(PlayerGroup,"Detailed Reports",MessagesMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 15 sec",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,15)
MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 30 sec",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,30)
MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 1 min",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,60)
MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 2 min",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,120)
MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 3 min",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,180)
end
return self
end
function SETTINGS:RemovePlayerMenu(PlayerUnit)
if self.PlayerMenu then
self.PlayerMenu:Remove()
self.PlayerMenu=nil
end
return self
end
function SETTINGS:A2GMenuSystem(MenuGroup,RootMenu,A2GSystem)
self.A2GSystem=A2GSystem
MESSAGE:New(string.format("Settings: Default A2G coordinate system set to %s for all players!",A2GSystem),5):ToAll()
self:SetSystemMenu(MenuGroup,RootMenu)
end
function SETTINGS:A2AMenuSystem(MenuGroup,RootMenu,A2ASystem)
self.A2ASystem=A2ASystem
MESSAGE:New(string.format("Settings: Default A2A coordinate system set to %s for all players!",A2ASystem),5):ToAll()
self:SetSystemMenu(MenuGroup,RootMenu)
end
function SETTINGS:MenuLL_DDM_Accuracy(MenuGroup,RootMenu,LL_Accuracy)
self.LL_Accuracy=LL_Accuracy
MESSAGE:New(string.format("Settings: Default LL accuracy set to %s for all players!",LL_Accuracy),5):ToAll()
self:SetSystemMenu(MenuGroup,RootMenu)
end
function SETTINGS:MenuMGRS_Accuracy(MenuGroup,RootMenu,MGRS_Accuracy)
self.MGRS_Accuracy=MGRS_Accuracy
MESSAGE:New(string.format("Settings: Default MGRS accuracy set to %s for all players!",MGRS_Accuracy),5):ToAll()
self:SetSystemMenu(MenuGroup,RootMenu)
end
function SETTINGS:MenuMWSystem(MenuGroup,RootMenu,MW)
self.Metric=MW
MESSAGE:New(string.format("Settings: Default measurement format set to %s for all players!",MW and"Metric"or"Imperial"),5):ToAll()
self:SetSystemMenu(MenuGroup,RootMenu)
end
function SETTINGS:MenuMessageTimingsSystem(MenuGroup,RootMenu,MessageType,MessageTime)
self:SetMessageTime(MessageType,MessageTime)
MESSAGE:New(string.format("Settings: Default message time set for %s to %d.",MessageType,MessageTime),5):ToAll()
end
do
function SETTINGS:MenuGroupA2GSystem(PlayerUnit,PlayerGroup,PlayerName,A2GSystem)
BASE:E({self,PlayerUnit:GetName(),A2GSystem})
self.A2GSystem=A2GSystem
MESSAGE:New(string.format("Settings: A2G format set to %s for player %s.",A2GSystem,PlayerName),5):ToGroup(PlayerGroup)
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
end
function SETTINGS:MenuGroupA2ASystem(PlayerUnit,PlayerGroup,PlayerName,A2ASystem)
self.A2ASystem=A2ASystem
MESSAGE:New(string.format("Settings: A2A format set to %s for player %s.",A2ASystem,PlayerName),5):ToGroup(PlayerGroup)
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
end
function SETTINGS:MenuGroupLL_DDM_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,LL_Accuracy)
self.LL_Accuracy=LL_Accuracy
MESSAGE:New(string.format("Settings: LL format accuracy set to %d decimal places for player %s.",LL_Accuracy,PlayerName),5):ToGroup(PlayerGroup)
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
end
function SETTINGS:MenuGroupMGRS_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,MGRS_Accuracy)
self.MGRS_Accuracy=MGRS_Accuracy
MESSAGE:New(string.format("Settings: MGRS format accuracy set to %d for player %s.",MGRS_Accuracy,PlayerName),5):ToGroup(PlayerGroup)
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
end
function SETTINGS:MenuGroupMWSystem(PlayerUnit,PlayerGroup,PlayerName,MW)
self.Metric=MW
MESSAGE:New(string.format("Settings: Measurement format set to %s for player %s.",MW and"Metric"or"Imperial",PlayerName),5):ToGroup(PlayerGroup)
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
end
function SETTINGS:MenuGroupMessageTimingsSystem(PlayerUnit,PlayerGroup,PlayerName,MessageType,MessageTime)
self:SetMessageTime(MessageType,MessageTime)
MESSAGE:New(string.format("Settings: Default message time set for %s to %d.",MessageType,MessageTime),5):ToGroup(PlayerGroup)
end
end
function SETTINGS:SetEraWWII()
self.Era=SETTINGS.__Enum.Era.WWII
end
function SETTINGS:SetEraKorea()
self.Era=SETTINGS.__Enum.Era.Korea
end
function SETTINGS:SetEraCold()
self.Era=SETTINGS.__Enum.Era.Cold
end
function SETTINGS:SetEraModern()
self.Era=SETTINGS.__Enum.Era.Modern
end
end
MENU_INDEX={}
MENU_INDEX.MenuMission={}
MENU_INDEX.MenuMission.Menus={}
MENU_INDEX.Coalition={}
MENU_INDEX.Coalition[coalition.side.BLUE]={}
MENU_INDEX.Coalition[coalition.side.BLUE].Menus={}
MENU_INDEX.Coalition[coalition.side.RED]={}
MENU_INDEX.Coalition[coalition.side.RED].Menus={}
MENU_INDEX.Group={}
function MENU_INDEX:ParentPath(ParentMenu,MenuText)
local Path=ParentMenu and"@"..table.concat(ParentMenu.MenuPath or{},"@")or""
if ParentMenu then
if ParentMenu:IsInstanceOf("MENU_GROUP")or ParentMenu:IsInstanceOf("MENU_GROUP_COMMAND")then
local GroupName=ParentMenu.Group:GetName()
if not self.Group[GroupName].Menus[Path]then
BASE:E({Path=Path,GroupName=GroupName})
error("Parent path not found in menu index for group menu")
return nil
end
elseif ParentMenu:IsInstanceOf("MENU_COALITION")or ParentMenu:IsInstanceOf("MENU_COALITION_COMMAND")then
local Coalition=ParentMenu.Coalition
if not self.Coalition[Coalition].Menus[Path]then
BASE:E({Path=Path,Coalition=Coalition})
error("Parent path not found in menu index for coalition menu")
return nil
end
elseif ParentMenu:IsInstanceOf("MENU_MISSION")or ParentMenu:IsInstanceOf("MENU_MISSION_COMMAND")then
if not self.MenuMission.Menus[Path]then
BASE:E({Path=Path})
error("Parent path not found in menu index for mission menu")
return nil
end
end
end
Path=Path.."@"..MenuText
return Path
end
function MENU_INDEX:PrepareMission()
self.MenuMission.Menus=self.MenuMission.Menus or{}
end
function MENU_INDEX:PrepareCoalition(CoalitionSide)
self.Coalition[CoalitionSide]=self.Coalition[CoalitionSide]or{}
self.Coalition[CoalitionSide].Menus=self.Coalition[CoalitionSide].Menus or{}
end
function MENU_INDEX:PrepareGroup(Group)
if Group and Group:IsAlive()~=nil then
local GroupName=Group:GetName()
self.Group[GroupName]=self.Group[GroupName]or{}
self.Group[GroupName].Menus=self.Group[GroupName].Menus or{}
end
end
function MENU_INDEX:HasMissionMenu(Path)
return self.MenuMission.Menus[Path]
end
function MENU_INDEX:SetMissionMenu(Path,Menu)
self.MenuMission.Menus[Path]=Menu
end
function MENU_INDEX:ClearMissionMenu(Path)
self.MenuMission.Menus[Path]=nil
end
function MENU_INDEX:HasCoalitionMenu(Coalition,Path)
return self.Coalition[Coalition].Menus[Path]
end
function MENU_INDEX:SetCoalitionMenu(Coalition,Path,Menu)
self.Coalition[Coalition].Menus[Path]=Menu
end
function MENU_INDEX:ClearCoalitionMenu(Coalition,Path)
self.Coalition[Coalition].Menus[Path]=nil
end
function MENU_INDEX:HasGroupMenu(Group,Path)
if Group and Group:IsAlive()then
local MenuGroupName=Group:GetName()
return self.Group[MenuGroupName].Menus[Path]
end
return nil
end
function MENU_INDEX:SetGroupMenu(Group,Path,Menu)
local MenuGroupName=Group:GetName()
Group:F({MenuGroupName=MenuGroupName,Path=Path})
self.Group[MenuGroupName].Menus[Path]=Menu
end
function MENU_INDEX:ClearGroupMenu(Group,Path)
local MenuGroupName=Group:GetName()
self.Group[MenuGroupName].Menus[Path]=nil
end
function MENU_INDEX:Refresh(Group)
for MenuID,Menu in pairs(self.MenuMission.Menus)do
Menu:Refresh()
end
for MenuID,Menu in pairs(self.Coalition[coalition.side.BLUE].Menus)do
Menu:Refresh()
end
for MenuID,Menu in pairs(self.Coalition[coalition.side.RED].Menus)do
Menu:Refresh()
end
local GroupName=Group:GetName()
for MenuID,Menu in pairs(self.Group[GroupName].Menus)do
Menu:Refresh()
end
return self
end
do
MENU_BASE={
ClassName="MENU_BASE",
MenuPath=nil,
MenuText="",
MenuParentPath=nil,
}
function MENU_BASE:New(MenuText,ParentMenu)
local MenuParentPath={}
if ParentMenu~=nil then
MenuParentPath=ParentMenu.MenuPath
end
local self=BASE:Inherit(self,BASE:New())
self.MenuPath=nil
self.MenuText=MenuText
self.ParentMenu=ParentMenu
self.MenuParentPath=MenuParentPath
self.Path=(self.ParentMenu and"@"..table.concat(self.MenuParentPath or{},"@")or"").."@"..self.MenuText
self.Menus={}
self.MenuCount=0
self.MenuStamp=timer.getTime()
self.MenuRemoveParent=false
if self.ParentMenu then
self.ParentMenu.Menus=self.ParentMenu.Menus or{}
self.ParentMenu.Menus[MenuText]=self
end
return self
end
function MENU_BASE:SetParentMenu(MenuText,Menu)
if self.ParentMenu then
self.ParentMenu.Menus=self.ParentMenu.Menus or{}
self.ParentMenu.Menus[MenuText]=Menu
self.ParentMenu.MenuCount=self.ParentMenu.MenuCount+1
end
end
function MENU_BASE:ClearParentMenu(MenuText)
if self.ParentMenu and self.ParentMenu.Menus[MenuText]then
self.ParentMenu.Menus[MenuText]=nil
self.ParentMenu.MenuCount=self.ParentMenu.MenuCount-1
if self.ParentMenu.MenuCount==0 then
end
end
end
function MENU_BASE:SetRemoveParent(RemoveParent)
self.MenuRemoveParent=RemoveParent
return self
end
function MENU_BASE:GetMenu(MenuText)
return self.Menus[MenuText]
end
function MENU_BASE:SetStamp(MenuStamp)
self.MenuStamp=MenuStamp
return self
end
function MENU_BASE:GetStamp()
return timer.getTime()
end
function MENU_BASE:SetTime(MenuStamp)
self.MenuStamp=MenuStamp
return self
end
function MENU_BASE:SetTag(MenuTag)
self.MenuTag=MenuTag
return self
end
end
do
MENU_COMMAND_BASE={
ClassName="MENU_COMMAND_BASE",
CommandMenuFunction=nil,
CommandMenuArgument=nil,
MenuCallHandler=nil,
}
function MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,CommandMenuArguments)
local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
local ErrorHandler=function(errmsg)
env.info("MOOSE error in MENU COMMAND function: "..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
self:SetCommandMenuFunction(CommandMenuFunction)
self:SetCommandMenuArguments(CommandMenuArguments)
self.MenuCallHandler=function()
local function MenuFunction()
return self.CommandMenuFunction(unpack(self.CommandMenuArguments))
end
local Status,Result=xpcall(MenuFunction,ErrorHandler)
end
return self
end
function MENU_COMMAND_BASE:SetCommandMenuFunction(CommandMenuFunction)
self.CommandMenuFunction=CommandMenuFunction
return self
end
function MENU_COMMAND_BASE:SetCommandMenuArguments(CommandMenuArguments)
self.CommandMenuArguments=CommandMenuArguments
return self
end
end
do
MENU_MISSION={
ClassName="MENU_MISSION",
}
function MENU_MISSION:New(MenuText,ParentMenu)
MENU_INDEX:PrepareMission()
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local MissionMenu=MENU_INDEX:HasMissionMenu(Path)
if MissionMenu then
return MissionMenu
else
local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
MENU_INDEX:SetMissionMenu(Path,self)
self.MenuPath=missionCommands.addSubMenu(self.MenuText,self.MenuParentPath)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_MISSION:Refresh()
do
missionCommands.removeItem(self.MenuPath)
self.MenuPath=missionCommands.addSubMenu(self.MenuText,self.MenuParentPath)
end
return self
end
function MENU_MISSION:RemoveSubMenus()
for MenuID,Menu in pairs(self.Menus or{})do
Menu:Remove()
end
self.Menus=nil
end
function MENU_MISSION:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareMission()
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local MissionMenu=MENU_INDEX:HasMissionMenu(Path)
if MissionMenu==self then
self:RemoveSubMenus()
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
self:F({Text=self.MenuText,Path=self.MenuPath})
if self.MenuPath~=nil then
missionCommands.removeItem(self.MenuPath)
end
MENU_INDEX:ClearMissionMenu(self.Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_MISSION",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText})
end
return self
end
end
do
MENU_MISSION_COMMAND={
ClassName="MENU_MISSION_COMMAND",
}
function MENU_MISSION_COMMAND:New(MenuText,ParentMenu,CommandMenuFunction,...)
MENU_INDEX:PrepareMission()
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local MissionMenu=MENU_INDEX:HasMissionMenu(Path)
if MissionMenu then
MissionMenu:SetCommandMenuFunction(CommandMenuFunction)
MissionMenu:SetCommandMenuArguments(arg)
return MissionMenu
else
local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
MENU_INDEX:SetMissionMenu(Path,self)
self.MenuPath=missionCommands.addCommand(MenuText,self.MenuParentPath,self.MenuCallHandler)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_MISSION_COMMAND:Refresh()
do
missionCommands.removeItem(self.MenuPath)
missionCommands.addCommand(self.MenuText,self.MenuParentPath,self.MenuCallHandler)
end
return self
end
function MENU_MISSION_COMMAND:Remove()
MENU_INDEX:PrepareMission()
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local MissionMenu=MENU_INDEX:HasMissionMenu(Path)
if MissionMenu==self then
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
self:F({Text=self.MenuText,Path=self.MenuPath})
if self.MenuPath~=nil then
missionCommands.removeItem(self.MenuPath)
end
MENU_INDEX:ClearMissionMenu(self.Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_MISSION_COMMAND",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText})
end
return self
end
end
do
MENU_COALITION={
ClassName="MENU_COALITION"
}
function MENU_COALITION:New(Coalition,MenuText,ParentMenu)
MENU_INDEX:PrepareCoalition(Coalition)
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(Coalition,Path)
if CoalitionMenu then
return CoalitionMenu
else
local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
MENU_INDEX:SetCoalitionMenu(Coalition,Path,self)
self.Coalition=Coalition
self.MenuPath=missionCommands.addSubMenuForCoalition(Coalition,MenuText,self.MenuParentPath)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_COALITION:Refresh()
do
missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath)
missionCommands.addSubMenuForCoalition(self.Coalition,self.MenuText,self.MenuParentPath)
end
return self
end
function MENU_COALITION:RemoveSubMenus()
for MenuID,Menu in pairs(self.Menus or{})do
Menu:Remove()
end
self.Menus=nil
end
function MENU_COALITION:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareCoalition(self.Coalition)
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(self.Coalition,Path)
if CoalitionMenu==self then
self:RemoveSubMenus()
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
self:F({Coalition=self.Coalition,Text=self.MenuText,Path=self.MenuPath})
if self.MenuPath~=nil then
missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath)
end
MENU_INDEX:ClearCoalitionMenu(self.Coalition,Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_COALITION",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Coalition=self.Coalition})
end
return self
end
end
do
MENU_COALITION_COMMAND={
ClassName="MENU_COALITION_COMMAND"
}
function MENU_COALITION_COMMAND:New(Coalition,MenuText,ParentMenu,CommandMenuFunction,...)
MENU_INDEX:PrepareCoalition(Coalition)
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(Coalition,Path)
if CoalitionMenu then
CoalitionMenu:SetCommandMenuFunction(CommandMenuFunction)
CoalitionMenu:SetCommandMenuArguments(arg)
return CoalitionMenu
else
local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
MENU_INDEX:SetCoalitionMenu(Coalition,Path,self)
self.Coalition=Coalition
self.MenuPath=missionCommands.addCommandForCoalition(self.Coalition,MenuText,self.MenuParentPath,self.MenuCallHandler)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_COALITION_COMMAND:Refresh()
do
missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath)
missionCommands.addCommandForCoalition(self.Coalition,self.MenuText,self.MenuParentPath,self.MenuCallHandler)
end
return self
end
function MENU_COALITION_COMMAND:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareCoalition(self.Coalition)
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(self.Coalition,Path)
if CoalitionMenu==self then
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
self:F({Coalition=self.Coalition,Text=self.MenuText,Path=self.MenuPath})
if self.MenuPath~=nil then
missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath)
end
MENU_INDEX:ClearCoalitionMenu(self.Coalition,Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_COALITION_COMMAND",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Coalition=self.Coalition})
end
return self
end
end
do
local _MENUGROUPS={}
MENU_GROUP={
ClassName="MENU_GROUP"
}
function MENU_GROUP:New(Group,MenuText,ParentMenu)
MENU_INDEX:PrepareGroup(Group)
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(Group,Path)
if GroupMenu then
return GroupMenu
else
self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
MENU_INDEX:SetGroupMenu(Group,Path,self)
self.Group=Group
self.GroupID=Group:GetID()
self.MenuPath=missionCommands.addSubMenuForGroup(self.GroupID,MenuText,self.MenuParentPath)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_GROUP:Refresh()
do
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
missionCommands.addSubMenuForGroup(self.GroupID,self.MenuText,self.MenuParentPath)
for MenuText,Menu in pairs(self.Menus or{})do
Menu:Refresh()
end
end
return self
end
function MENU_GROUP:RefreshAndOrderByTag()
do
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
missionCommands.addSubMenuForGroup(self.GroupID,self.MenuText,self.MenuParentPath)
local MenuTable={}
for MenuText,Menu in pairs(self.Menus or{})do
local tag=Menu.MenuTag or math.random(1,10000)
MenuTable[#MenuTable+1]={Tag=tag,Enty=Menu}
end
table.sort(MenuTable,function(k1,k2)return k1.tag<k2.tag end)
for _,Menu in pairs(MenuTable)do
Menu.Entry:Refresh()
end
end
return self
end
function MENU_GROUP:RemoveSubMenus(MenuStamp,MenuTag)
for MenuText,Menu in pairs(self.Menus or{})do
Menu:Remove(MenuStamp,MenuTag)
end
self.Menus=nil
end
function MENU_GROUP:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareGroup(self.Group)
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(self.Group,Path)
if GroupMenu==self then
self:RemoveSubMenus(MenuStamp,MenuTag)
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
if self.MenuPath~=nil then
self:F({Group=self.GroupID,Text=self.MenuText,Path=self.MenuPath})
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
end
MENU_INDEX:ClearGroupMenu(self.Group,Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_GROUP",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Group=self.Group})
return nil
end
return self
end
MENU_GROUP_COMMAND={
ClassName="MENU_GROUP_COMMAND"
}
function MENU_GROUP_COMMAND:New(Group,MenuText,ParentMenu,CommandMenuFunction,...)
MENU_INDEX:PrepareGroup(Group)
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(Group,Path)
if GroupMenu then
GroupMenu:SetCommandMenuFunction(CommandMenuFunction)
GroupMenu:SetCommandMenuArguments(arg)
return GroupMenu
else
self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
MENU_INDEX:SetGroupMenu(Group,Path,self)
self.Group=Group
self.GroupID=Group:GetID()
self.MenuPath=missionCommands.addCommandForGroup(self.GroupID,MenuText,self.MenuParentPath,self.MenuCallHandler)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_GROUP_COMMAND:Refresh()
do
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
missionCommands.addCommandForGroup(self.GroupID,self.MenuText,self.MenuParentPath,self.MenuCallHandler)
end
return self
end
function MENU_GROUP_COMMAND:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareGroup(self.Group)
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(self.Group,Path)
if GroupMenu==self then
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
if self.MenuPath~=nil then
self:F({Group=self.GroupID,Text=self.MenuText,Path=self.MenuPath})
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
end
MENU_INDEX:ClearGroupMenu(self.Group,Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_GROUP_COMMAND",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Group=self.Group})
end
return self
end
end
do
MENU_GROUP_DELAYED={
ClassName="MENU_GROUP_DELAYED"
}
function MENU_GROUP_DELAYED:New(Group,MenuText,ParentMenu)
MENU_INDEX:PrepareGroup(Group)
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(Group,Path)
if GroupMenu then
return GroupMenu
else
self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
MENU_INDEX:SetGroupMenu(Group,Path,self)
self.Group=Group
self.GroupID=Group:GetID()
if self.MenuParentPath then
self.MenuPath=UTILS.DeepCopy(self.MenuParentPath)
else
self.MenuPath={}
end
table.insert(self.MenuPath,self.MenuText)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_GROUP_DELAYED:Set()
do
if not self.MenuSet then
missionCommands.addSubMenuForGroup(self.GroupID,self.MenuText,self.MenuParentPath)
self.MenuSet=true
end
for MenuText,Menu in pairs(self.Menus or{})do
Menu:Set()
end
end
end
function MENU_GROUP_DELAYED:Refresh()
do
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
missionCommands.addSubMenuForGroup(self.GroupID,self.MenuText,self.MenuParentPath)
for MenuText,Menu in pairs(self.Menus or{})do
Menu:Refresh()
end
end
return self
end
function MENU_GROUP_DELAYED:RemoveSubMenus(MenuStamp,MenuTag)
for MenuText,Menu in pairs(self.Menus or{})do
Menu:Remove(MenuStamp,MenuTag)
end
self.Menus=nil
end
function MENU_GROUP_DELAYED:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareGroup(self.Group)
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(self.Group,Path)
if GroupMenu==self then
self:RemoveSubMenus(MenuStamp,MenuTag)
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
if self.MenuPath~=nil then
self:F({Group=self.GroupID,Text=self.MenuText,Path=self.MenuPath})
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
end
MENU_INDEX:ClearGroupMenu(self.Group,Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_GROUP_DELAYED",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Group=self.Group})
return nil
end
return self
end
MENU_GROUP_COMMAND_DELAYED={
ClassName="MENU_GROUP_COMMAND_DELAYED"
}
function MENU_GROUP_COMMAND_DELAYED:New(Group,MenuText,ParentMenu,CommandMenuFunction,...)
MENU_INDEX:PrepareGroup(Group)
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(Group,Path)
if GroupMenu then
GroupMenu:SetCommandMenuFunction(CommandMenuFunction)
GroupMenu:SetCommandMenuArguments(arg)
return GroupMenu
else
self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
MENU_INDEX:SetGroupMenu(Group,Path,self)
self.Group=Group
self.GroupID=Group:GetID()
if self.MenuParentPath then
self.MenuPath=UTILS.DeepCopy(self.MenuParentPath)
else
self.MenuPath={}
end
table.insert(self.MenuPath,self.MenuText)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_GROUP_COMMAND_DELAYED:Set()
do
if not self.MenuSet then
self.MenuPath=missionCommands.addCommandForGroup(self.GroupID,self.MenuText,self.MenuParentPath,self.MenuCallHandler)
self.MenuSet=true
end
end
end
function MENU_GROUP_COMMAND_DELAYED:Refresh()
do
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
missionCommands.addCommandForGroup(self.GroupID,self.MenuText,self.MenuParentPath,self.MenuCallHandler)
end
return self
end
function MENU_GROUP_COMMAND_DELAYED:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareGroup(self.Group)
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(self.Group,Path)
if GroupMenu==self then
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
if self.MenuPath~=nil then
self:F({Group=self.GroupID,Text=self.MenuText,Path=self.MenuPath})
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
end
MENU_INDEX:ClearGroupMenu(self.Group,Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_GROUP_COMMAND_DELAYED",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Group=self.Group})
end
return self
end
end
ZONE_BASE={
ClassName="ZONE_BASE",
ZoneName="",
ZoneProbability=1,
DrawID=nil,
Color={},
ZoneID=nil,
Properties={},
Surface=nil,
}
function ZONE_BASE:New(ZoneName)
local self=BASE:Inherit(self,FSM:New())
self:F(ZoneName)
self.ZoneName=ZoneName
return self
end
function ZONE_BASE:GetName()
self:F2()
return self.ZoneName
end
function ZONE_BASE:SetName(ZoneName)
self:F2()
self.ZoneName=ZoneName
end
function ZONE_BASE:IsVec2InZone(Vec2)
self:F2(Vec2)
return false
end
function ZONE_BASE:IsVec3InZone(Vec3)
if not Vec3 then return false end
local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
return InZone
end
function ZONE_BASE:IsCoordinateInZone(Coordinate)
local InZone=self:IsVec2InZone(Coordinate:GetVec2())
return InZone
end
function ZONE_BASE:IsPointVec2InZone(Coordinate)
local InZone=self:IsVec2InZone(Coordinate:GetVec2())
return InZone
end
function ZONE_BASE:IsPointVec3InZone(PointVec3)
local InZone=self:IsPointVec2InZone(PointVec3)
return InZone
end
function ZONE_BASE:GetVec2()
return nil
end
function ZONE_BASE:GetPointVec2()
self:F2(self.ZoneName)
local Vec2=self:GetVec2()
local PointVec2=POINT_VEC2:NewFromVec2(Vec2)
self:T2({PointVec2})
return PointVec2
end
function ZONE_BASE:GetVec3(Height)
self:F2(self.ZoneName)
Height=Height or 0
local Vec2=self:GetVec2()
local Vec3={x=Vec2.x,y=Height and Height or land.getHeight(self:GetVec2()),z=Vec2.y}
self:T2({Vec3})
return Vec3
end
function ZONE_BASE:GetPointVec3(Height)
self:F2(self.ZoneName)
local Vec3=self:GetVec3(Height)
local PointVec3=POINT_VEC3:NewFromVec3(Vec3)
self:T2({PointVec3})
return PointVec3
end
function ZONE_BASE:GetCoordinate(Height)
self:F2(self.ZoneName)
local Vec3=self:GetVec3(Height)
if self.Coordinate then
self.Coordinate.x=Vec3.x
self.Coordinate.y=Vec3.y
self.Coordinate.z=Vec3.z
else
self.Coordinate=COORDINATE:NewFromVec3(Vec3)
end
return self.Coordinate
end
function ZONE_BASE:Get2DDistance(Coordinate)
local a=self:GetVec2()
local b={}
if Coordinate.z then
b.x=Coordinate.x
b.y=Coordinate.z
else
b.x=Coordinate.x
b.y=Coordinate.y
end
local dist=UTILS.VecDist2D(a,b)
return dist
end
function ZONE_BASE:GetRandomVec2()
return nil
end
function ZONE_BASE:GetRandomPointVec2()
return nil
end
function ZONE_BASE:GetRandomPointVec3()
return nil
end
function ZONE_BASE:GetBoundingSquare()
return nil
end
function ZONE_BASE:GetSurfaceType()
local coord=self:GetCoordinate()
local surface=coord:GetSurfaceType()
return surface
end
function ZONE_BASE:BoundZone()
self:F2()
end
function ZONE_BASE:SetDrawCoalition(Coalition)
self.drawCoalition=Coalition or-1
return self
end
function ZONE_BASE:GetDrawCoalition()
return self.drawCoalition or-1
end
function ZONE_BASE:SetColor(RGBcolor,Alpha)
RGBcolor=RGBcolor or{1,0,0}
Alpha=Alpha or 0.15
self.Color={}
self.Color[1]=RGBcolor[1]
self.Color[2]=RGBcolor[2]
self.Color[3]=RGBcolor[3]
self.Color[4]=Alpha
return self
end
function ZONE_BASE:GetColor()
return self.Color or{1,0,0,0.15}
end
function ZONE_BASE:GetColorRGB()
local rgb={}
local Color=self:GetColor()
rgb[1]=Color[1]
rgb[2]=Color[2]
rgb[3]=Color[3]
return rgb
end
function ZONE_BASE:GetColorAlpha()
local Color=self:GetColor()
local alpha=Color[4]
return alpha
end
function ZONE_BASE:SetFillColor(RGBcolor,Alpha)
RGBcolor=RGBcolor or{1,0,0}
Alpha=Alpha or 0.15
self.FillColor={}
self.FillColor[1]=RGBcolor[1]
self.FillColor[2]=RGBcolor[2]
self.FillColor[3]=RGBcolor[3]
self.FillColor[4]=Alpha
return self
end
function ZONE_BASE:GetFillColor()
return self.FillColor or{1,0,0,0.15}
end
function ZONE_BASE:GetFillColorRGB()
local rgb={}
local FillColor=self:GetFillColor()
rgb[1]=FillColor[1]
rgb[2]=FillColor[2]
rgb[3]=FillColor[3]
return rgb
end
function ZONE_BASE:GetFillColorAlpha()
local FillColor=self:GetFillColor()
local alpha=FillColor[4]
return alpha
end
function ZONE_BASE:UndrawZone(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,ZONE_BASE.UndrawZone,self)
else
if self.DrawID then
UTILS.RemoveMark(self.DrawID)
end
end
return self
end
function ZONE_BASE:GetDrawID()
return self.DrawID
end
function ZONE_BASE:SmokeZone(SmokeColor)
self:F2(SmokeColor)
end
function ZONE_BASE:SetZoneProbability(ZoneProbability)
self:F({self:GetName(),ZoneProbability=ZoneProbability})
self.ZoneProbability=ZoneProbability or 1
return self
end
function ZONE_BASE:GetZoneProbability()
self:F2()
return self.ZoneProbability
end
function ZONE_BASE:GetZoneMaybe()
self:F2()
local Randomization=math.random()
if Randomization<=self.ZoneProbability then
return self
else
return nil
end
end
function ZONE_BASE:GetProperty(PropertyName)
return self.Properties[PropertyName]
end
function ZONE_BASE:GetAllProperties()
return self.Properties
end
ZONE_RADIUS={
ClassName="ZONE_RADIUS",
}
function ZONE_RADIUS:New(ZoneName,Vec2,Radius,DoNotRegisterZone)
local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName))
self:F({ZoneName,Vec2,Radius})
self.Radius=Radius
self.Vec2=Vec2
if not DoNotRegisterZone then
_EVENTDISPATCHER:CreateEventNewZone(self)
end
return self
end
function ZONE_RADIUS:UpdateFromVec2(Vec2,Radius)
self.Vec2=Vec2
if Radius then
self.Radius=Radius
end
return self
end
function ZONE_RADIUS:UpdateFromVec3(Vec3,Radius)
self.Vec2.x=Vec3.x
self.Vec2.y=Vec3.z
if Radius then
self.Radius=Radius
end
return self
end
function ZONE_RADIUS:MarkZone(Points)
local Point={}
local Vec2=self:GetVec2()
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,(360/Points)do
local Radial=Angle*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
COORDINATE:NewFromVec2(Point):MarkToAll(self:GetName())
end
end
function ZONE_RADIUS:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
local coordinate=self:GetCoordinate()
local Radius=self:GetRadius()
Color=Color or self:GetColorRGB()
Alpha=Alpha or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillAlpha=FillAlpha or self:GetColorAlpha()
self.DrawID=coordinate:CircleToAll(Radius,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
return self
end
function ZONE_RADIUS:BoundZone(Points,CountryID,UnBound)
local Point={}
local Vec2=self:GetVec2()
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,(360/Points)do
local Radial=Angle*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
local CountryName=_DATABASE.COUNTRY_NAME[CountryID]
local Tire={
["country"]=CountryName,
["category"]="Fortifications",
["canCargo"]=false,
["shape_name"]="H-tyre_B_WF",
["type"]="Black_Tyre_WF",
["y"]=Point.y,
["x"]=Point.x,
["name"]=string.format("%s-Tire #%0d",self:GetName(),Angle),
["heading"]=0,
}
local Group=coalition.addStaticObject(CountryID,Tire)
if UnBound and UnBound==true then
Group:destroy()
end
end
return self
end
function ZONE_RADIUS:SmokeZone(SmokeColor,Points,AddHeight,AngleOffset)
self:F2(SmokeColor)
local Point={}
local Vec2=self:GetVec2()
AddHeight=AddHeight or 0
AngleOffset=AngleOffset or 0
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,360/Points do
local Radial=(Angle+AngleOffset)*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
POINT_VEC2:New(Point.x,Point.y,AddHeight):Smoke(SmokeColor)
end
return self
end
function ZONE_RADIUS:FlareZone(FlareColor,Points,Azimuth,AddHeight)
self:F2({FlareColor,Azimuth})
local Point={}
local Vec2=self:GetVec2()
AddHeight=AddHeight or 0
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,360/Points do
local Radial=Angle*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
POINT_VEC2:New(Point.x,Point.y,AddHeight):Flare(FlareColor,Azimuth)
end
return self
end
function ZONE_RADIUS:GetRadius()
self:F2(self.ZoneName)
self:T2({self.Radius})
return self.Radius
end
function ZONE_RADIUS:SetRadius(Radius)
self:F2(self.ZoneName)
self.Radius=Radius
self:T2({self.Radius})
return self.Radius
end
function ZONE_RADIUS:GetVec2()
self:F2(self.ZoneName)
self:T2({self.Vec2})
return self.Vec2
end
function ZONE_RADIUS:SetVec2(Vec2)
self:F2(self.ZoneName)
self.Vec2=Vec2
self:T2({self.Vec2})
return self.Vec2
end
function ZONE_RADIUS:GetVec3(Height)
self:F2({self.ZoneName,Height})
Height=Height or 0
local Vec2=self:GetVec2()
local Vec3={x=Vec2.x,y=land.getHeight(self:GetVec2())+Height,z=Vec2.y}
self:T2({Vec3})
return Vec3
end
function ZONE_RADIUS:Scan(ObjectCategories,UnitCategories)
self.ScanData={}
self.ScanData.Coalitions={}
self.ScanData.Scenery={}
self.ScanData.SceneryTable={}
self.ScanData.Units={}
local ZoneCoord=self:GetCoordinate()
local ZoneRadius=self:GetRadius()
self:F({ZoneCoord=ZoneCoord,ZoneRadius=ZoneRadius,ZoneCoordLL=ZoneCoord:ToStringLLDMS()})
local SphereSearch={
id=world.VolumeType.SPHERE,
params={
point=ZoneCoord:GetVec3(),
radius=ZoneRadius,
}
}
local function EvaluateZone(ZoneObject)
if ZoneObject then
local ObjectCategory=ZoneObject:getCategory()
if(ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()and ZoneObject:isActive())or(ObjectCategory==Object.Category.STATIC and ZoneObject:isExist())then
local CoalitionDCSUnit=ZoneObject:getCoalition()
local Include=false
if not UnitCategories then
Include=true
else
local CategoryDCSUnit=ZoneObject:getDesc().category
for UnitCategoryID,UnitCategory in pairs(UnitCategories)do
if UnitCategory==CategoryDCSUnit then
Include=true
break
end
end
end
if Include then
local CoalitionDCSUnit=ZoneObject:getCoalition()
self.ScanData.Coalitions[CoalitionDCSUnit]=true
self.ScanData.Units[ZoneObject]=ZoneObject
self:F2({Name=ZoneObject:getName(),Coalition=CoalitionDCSUnit})
end
end
if ObjectCategory==Object.Category.SCENERY then
local SceneryType=ZoneObject:getTypeName()
local SceneryName=ZoneObject:getName()
self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{}
self.ScanData.Scenery[SceneryType][SceneryName]=SCENERY:Register(SceneryName,ZoneObject)
table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName])
self:T({SCENERY=self.ScanData.Scenery[SceneryType][SceneryName]})
end
end
return true
end
world.searchObjects(ObjectCategories,SphereSearch,EvaluateZone)
end
function ZONE_RADIUS:GetScannedUnits()
return self.ScanData.Units
end
function ZONE_RADIUS:GetScannedSetUnit()
local SetUnit=SET_UNIT:New()
if self.ScanData then
for ObjectID,UnitObject in pairs(self.ScanData.Units)do
local UnitObject=UnitObject
if UnitObject:isExist()then
local FoundUnit=UNIT:FindByName(UnitObject:getName())
if FoundUnit then
SetUnit:AddUnit(FoundUnit)
else
local FoundStatic=STATIC:FindByName(UnitObject:getName())
if FoundStatic then
SetUnit:AddUnit(FoundStatic)
end
end
end
end
end
return SetUnit
end
function ZONE_RADIUS:GetScannedSetGroup()
self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New()
self.ScanSetGroup.Set={}
if self.ScanData then
for ObjectID,UnitObject in pairs(self.ScanData.Units)do
local UnitObject=UnitObject
if UnitObject:isExist()then
local FoundUnit=UNIT:FindByName(UnitObject:getName())
if FoundUnit then
local group=FoundUnit:GetGroup()
self.ScanSetGroup:AddGroup(group)
end
end
end
end
return self.ScanSetGroup
end
function ZONE_RADIUS:CountScannedCoalitions()
local Count=0
for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do
Count=Count+1
end
return Count
end
function ZONE_RADIUS:CheckScannedCoalition(Coalition)
if Coalition then
return self.ScanData.Coalitions[Coalition]
end
return nil
end
function ZONE_RADIUS:GetScannedCoalition(Coalition)
if Coalition then
return self.ScanData.Coalitions[Coalition]
else
local Count=0
local ReturnCoalition=nil
for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do
Count=Count+1
ReturnCoalition=CoalitionID
end
if Count~=1 then
ReturnCoalition=nil
end
return ReturnCoalition
end
end
function ZONE_RADIUS:GetScannedSceneryType(SceneryType)
return self.ScanData.Scenery[SceneryType]
end
function ZONE_RADIUS:GetScannedScenery()
return self.ScanData.Scenery
end
function ZONE_RADIUS:GetScannedSceneryObjects()
return self.ScanData.SceneryTable
end
function ZONE_RADIUS:GetScannedSetScenery()
local scenery=SET_SCENERY:New()
local objects=self:GetScannedSceneryObjects()
for _,_obj in pairs(objects)do
scenery:AddScenery(_obj)
end
return scenery
end
function ZONE_RADIUS:IsAllInZoneOfCoalition(Coalition)
return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==true
end
function ZONE_RADIUS:IsAllInZoneOfOtherCoalition(Coalition)
return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==nil
end
function ZONE_RADIUS:IsSomeInZoneOfCoalition(Coalition)
return self:CountScannedCoalitions()>1 and self:GetScannedCoalition(Coalition)==true
end
function ZONE_RADIUS:IsNoneInZoneOfCoalition(Coalition)
return self:GetScannedCoalition(Coalition)==nil
end
function ZONE_RADIUS:IsNoneInZone()
return self:CountScannedCoalitions()==0
end
function ZONE_RADIUS:SearchZone(EvaluateFunction,ObjectCategories)
local SearchZoneResult=true
local ZoneCoord=self:GetCoordinate()
local ZoneRadius=self:GetRadius()
self:F({ZoneCoord=ZoneCoord,ZoneRadius=ZoneRadius,ZoneCoordLL=ZoneCoord:ToStringLLDMS()})
local SphereSearch={
id=world.VolumeType.SPHERE,
params={
point=ZoneCoord:GetVec3(),
radius=ZoneRadius/2,
}
}
local function EvaluateZone(ZoneDCSUnit)
local ZoneUnit=UNIT:Find(ZoneDCSUnit)
return EvaluateFunction(ZoneUnit)
end
world.searchObjects(Object.Category.UNIT,SphereSearch,EvaluateZone)
end
function ZONE_RADIUS:IsVec2InZone(Vec2)
self:F2(Vec2)
if not Vec2 then return false end
local ZoneVec2=self:GetVec2()
if ZoneVec2 then
if((Vec2.x-ZoneVec2.x)^2+(Vec2.y-ZoneVec2.y)^2)^0.5<=self:GetRadius()then
return true
end
end
return false
end
function ZONE_RADIUS:IsVec3InZone(Vec3)
self:F2(Vec3)
if not Vec3 then return false end
local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
return InZone
end
function ZONE_RADIUS:GetRandomVec2(inner,outer,surfacetypes)
local Vec2=self:GetVec2()
local _inner=inner or 0
local _outer=outer or self:GetRadius()
if surfacetypes and type(surfacetypes)~="table"then
surfacetypes={surfacetypes}
end
local function _getpoint()
local point={}
local angle=math.random()*math.pi*2
point.x=Vec2.x+math.cos(angle)*math.random(_inner,_outer)
point.y=Vec2.y+math.sin(angle)*math.random(_inner,_outer)
return point
end
local function _checkSurface(point)
local stype=land.getSurfaceType(point)
for _,sf in pairs(surfacetypes)do
if sf==stype then
return true
end
end
return false
end
local point=_getpoint()
if surfacetypes then
local N=1;local Nmax=100;local gotit=false
while gotit==false and N<=Nmax do
gotit=_checkSurface(point)
if gotit then
else
point=_getpoint()
N=N+1
end
end
end
return point
end
function ZONE_RADIUS:GetRandomPointVec2(inner,outer)
self:F(self.ZoneName,inner,outer)
local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2(inner,outer))
self:T3({PointVec2})
return PointVec2
end
function ZONE_RADIUS:GetRandomVec3(inner,outer)
self:F(self.ZoneName,inner,outer)
local Vec2=self:GetRandomVec2(inner,outer)
self:T3({x=Vec2.x,y=self.y,z=Vec2.y})
return{x=Vec2.x,y=self.y,z=Vec2.y}
end
function ZONE_RADIUS:GetRandomPointVec3(inner,outer)
self:F(self.ZoneName,inner,outer)
local PointVec3=POINT_VEC3:NewFromVec2(self:GetRandomVec2(inner,outer))
self:T3({PointVec3})
return PointVec3
end
function ZONE_RADIUS:GetRandomCoordinate(inner,outer,surfacetypes)
local vec2=self:GetRandomVec2(inner,outer,surfacetypes)
local Coordinate=COORDINATE:NewFromVec2(vec2)
return Coordinate
end
function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,markbuildings,markfinal)
local dist=distance or 100
local objects={}
if self.ScanData and self.ScanData.Scenery then
objects=self:GetScannedScenery()
else
self:Scan({Object.Category.SCENERY})
objects=self:GetScannedScenery()
end
local T0=timer.getTime()
local T1=timer.getTime()
local buildings={}
if self.ScanData and self.ScanData.BuildingCoordinates then
buildings=self.ScanData.BuildingCoordinates
else
for _,_object in pairs(objects)do
for _,_scen in pairs(_object)do
local scenery=_scen
local description=scenery:GetDesc()
if description and description.attributes and description.attributes.Buildings then
if markbuildings then
MARKER:New(scenery:GetCoordinate(),"Building"):ToAll()
end
buildings[#buildings+1]=scenery:GetCoordinate()
end
end
end
self.ScanData.BuildingCoordinates=buildings
end
local rcoord=nil
local found=false
local iterations=0
for i=1,1000 do
iterations=iterations+1
rcoord=self:GetRandomCoordinate(inner,outer)
found=false
for _,_coord in pairs(buildings)do
local coord=_coord
if coord:Get3DDistance(rcoord)>dist then
found=true
else
found=false
end
end
if found then
if markfinal then
MARKER:New(rcoord,"FREE"):ToAll()
end
break
end
end
T1=timer.getTime()
self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %d",tostring(found),iterations,T1-T0))
if found then return rcoord else return nil end
end
ZONE={
ClassName="ZONE",
}
function ZONE:New(ZoneName)
local zone=_DATABASE:FindZone(ZoneName)
if zone then
return zone
end
local Zone=trigger.misc.getZone(ZoneName)
if not Zone then
env.error("ERROR: Zone "..ZoneName.." does not exist!")
return nil
end
local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,{x=Zone.point.x,y=Zone.point.z},Zone.radius,true))
self:F(ZoneName)
self.Color={1,0,0,0.15}
self.Zone=Zone
return self
end
function ZONE:FindByName(ZoneName)
local ZoneFound=_DATABASE:FindZone(ZoneName)
return ZoneFound
end
ZONE_UNIT={
ClassName="ZONE_UNIT",
}
function ZONE_UNIT:New(ZoneName,ZoneUNIT,Radius,Offset)
if Offset then
if(Offset.dx or Offset.dy)and(Offset.rho or Offset.theta)then
error("Cannot use (dx, dy) with (rho, theta)")
end
self.dy=Offset.dy or 0.0
self.dx=Offset.dx or 0.0
self.rho=Offset.rho or 0.0
self.theta=(Offset.theta or 0.0)*math.pi/180.0
self.relative_to_unit=Offset.relative_to_unit or false
end
local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,ZoneUNIT:GetVec2(),Radius,true))
self:F({ZoneName,ZoneUNIT:GetVec2(),Radius})
self.ZoneUNIT=ZoneUNIT
self.LastVec2=ZoneUNIT:GetVec2()
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
function ZONE_UNIT:GetVec2()
self:F2(self.ZoneName)
local ZoneVec2=self.ZoneUNIT:GetVec2()
if ZoneVec2 then
local heading
if self.relative_to_unit then
heading=(self.ZoneUNIT:GetHeading()or 0.0)*math.pi/180.0
else
heading=0.0
end
if(self.dx or self.dy)then
ZoneVec2.x=ZoneVec2.x+self.dx*math.cos(-heading)+self.dy*math.sin(-heading)
ZoneVec2.y=ZoneVec2.y-self.dx*math.sin(-heading)+self.dy*math.cos(-heading)
end
if(self.rho or self.theta)then
ZoneVec2.x=ZoneVec2.x+self.rho*math.cos(self.theta+heading)
ZoneVec2.y=ZoneVec2.y+self.rho*math.sin(self.theta+heading)
end
self.LastVec2=ZoneVec2
return ZoneVec2
else
return self.LastVec2
end
self:T2({ZoneVec2})
return nil
end
function ZONE_UNIT:GetRandomVec2()
self:F(self.ZoneName)
local RandomVec2={}
local Vec2=self:GetVec2()
if not Vec2 then
Vec2=self.LastVec2
end
local angle=math.random()*math.pi*2;
RandomVec2.x=Vec2.x+math.cos(angle)*math.random()*self:GetRadius();
RandomVec2.y=Vec2.y+math.sin(angle)*math.random()*self:GetRadius();
self:T({RandomVec2})
return RandomVec2
end
function ZONE_UNIT:GetVec3(Height)
self:F2(self.ZoneName)
Height=Height or 0
local Vec2=self:GetVec2()
local Vec3={x=Vec2.x,y=land.getHeight(self:GetVec2())+Height,z=Vec2.y}
self:T2({Vec3})
return Vec3
end
ZONE_GROUP={
ClassName="ZONE_GROUP",
}
function ZONE_GROUP:New(ZoneName,ZoneGROUP,Radius)
local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,ZoneGROUP:GetVec2(),Radius,true))
self:F({ZoneName,ZoneGROUP:GetVec2(),Radius})
self._.ZoneGROUP=ZoneGROUP
self._.ZoneVec2Cache=self._.ZoneGROUP:GetVec2()
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
function ZONE_GROUP:GetVec2()
self:F(self.ZoneName)
local ZoneVec2=nil
if self._.ZoneGROUP:IsAlive()then
ZoneVec2=self._.ZoneGROUP:GetVec2()
self._.ZoneVec2Cache=ZoneVec2
else
ZoneVec2=self._.ZoneVec2Cache
end
self:T({ZoneVec2})
return ZoneVec2
end
function ZONE_GROUP:GetRandomVec2()
self:F(self.ZoneName)
local Point={}
local Vec2=self._.ZoneGROUP:GetVec2()
local angle=math.random()*math.pi*2;
Point.x=Vec2.x+math.cos(angle)*math.random()*self:GetRadius();
Point.y=Vec2.y+math.sin(angle)*math.random()*self:GetRadius();
self:T({Point})
return Point
end
function ZONE_GROUP:GetRandomPointVec2(inner,outer)
self:F(self.ZoneName,inner,outer)
local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2())
self:T3({PointVec2})
return PointVec2
end
ZONE_POLYGON_BASE={
ClassName="ZONE_POLYGON_BASE",
}
function ZONE_POLYGON_BASE:New(ZoneName,PointsArray)
local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName))
self:F({ZoneName,PointsArray})
if PointsArray then
self._.Polygon={}
for i=1,#PointsArray do
self._.Polygon[i]={}
self._.Polygon[i].x=PointsArray[i].x
self._.Polygon[i].y=PointsArray[i].y
end
end
return self
end
function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array)
self._.Polygon={}
for i=1,#Vec2Array do
self._.Polygon[i]={}
self._.Polygon[i].x=Vec2Array[i].x
self._.Polygon[i].y=Vec2Array[i].y
end
return self
end
function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array)
self._.Polygon={}
for i=1,#Vec3Array do
self._.Polygon[i]={}
self._.Polygon[i].x=Vec3Array[i].x
self._.Polygon[i].y=Vec3Array[i].z
end
return self
end
function ZONE_POLYGON_BASE:GetVec2()
self:F(self.ZoneName)
local Bounds=self:GetBoundingSquare()
return{x=(Bounds.x2+Bounds.x1)/2,y=(Bounds.y2+Bounds.y1)/2}
end
function ZONE_POLYGON_BASE:GetVertexVec2(Index)
return self._.Polygon[Index or 1]
end
function ZONE_POLYGON_BASE:GetVertexVec3(Index)
local vec2=self:GetVertexVec2(Index)
if vec2 then
local vec3={x=vec2.x,y=land.getHeight(vec2),z=vec2.y}
return vec3
end
return nil
end
function ZONE_POLYGON_BASE:GetVertexCoordinate(Index)
local vec2=self:GetVertexVec2(Index)
if vec2 then
local coord=COORDINATE:NewFromVec2(vec2)
return coord
end
return nil
end
function ZONE_POLYGON_BASE:GetVerticiesVec2()
return self._.Polygon
end
function ZONE_POLYGON_BASE:GetVerticiesVec3()
local coords={}
for i,vec2 in ipairs(self._.Polygon)do
local vec3={x=vec2.x,y=land.getHeight(vec2),z=vec2.y}
table.insert(coords,vec3)
end
return coords
end
function ZONE_POLYGON_BASE:GetVerticiesCoordinates()
local coords={}
for i,vec2 in ipairs(self._.Polygon)do
local coord=COORDINATE:NewFromVec2(vec2)
table.insert(coords,coord)
end
return coords
end
function ZONE_POLYGON_BASE:Flush()
self:F2()
self:F({Polygon=self.ZoneName,Coordinates=self._.Polygon})
return self
end
function ZONE_POLYGON_BASE:BoundZone(UnBound)
local i
local j
local Segments=10
i=1
j=#self._.Polygon
while i<=#self._.Polygon do
self:T({i,j,self._.Polygon[i],self._.Polygon[j]})
local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x
local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y
for Segment=0,Segments do
local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments)
local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments)
local Tire={
["country"]="USA",
["category"]="Fortifications",
["canCargo"]=false,
["shape_name"]="H-tyre_B_WF",
["type"]="Black_Tyre_WF",
["y"]=PointY,
["x"]=PointX,
["name"]=string.format("%s-Tire #%0d",self:GetName(),((i-1)*Segments)+Segment),
["heading"]=0,
}
local Group=coalition.addStaticObject(country.id.USA,Tire)
if UnBound and UnBound==true then
Group:destroy()
end
end
j=i
i=i+1
end
return self
end
function ZONE_POLYGON_BASE:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
if self._.Polygon and#self._.Polygon>=3 then
local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1])
Coalition=Coalition or self:GetDrawCoalition()
self:SetDrawCoalition(Coalition)
Color=Color or self:GetColorRGB()
Alpha=Alpha or 1
self:SetColor(Color,Alpha)
FillColor=FillColor or self:GetFillColorRGB()
if not FillColor then UTILS.DeepCopy(Color)end
FillAlpha=FillAlpha or self:GetFillColorAlpha()
if not FillAlpha then FillAlpha=0.15 end
self:SetFillColor(FillColor,FillAlpha)
if#self._.Polygon==4 then
local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2])
local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3])
local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4])
self.DrawID=coordinate:QuadToAll(Coord2,Coord3,Coord4,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
else
local Coordinates=self:GetVerticiesCoordinates()
table.remove(Coordinates,1)
self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
end
end
return self
end
function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName,DoNotRegisterZone)
local center=self:GetVec2()
local radius=0
for _,_vec2 in pairs(self._.Polygon)do
local vec2=_vec2
local r=UTILS.VecDist2D(center,vec2)
if r>radius then
radius=r
end
end
local zone=ZONE_RADIUS:New(ZoneName or self.ZoneName,center,radius,DoNotRegisterZone)
return zone
end
function ZONE_POLYGON_BASE:GetZoneQuad(ZoneName,DoNotRegisterZone)
local vec1,vec3=self:GetBoundingVec2()
local vec2={x=vec1.x,y=vec3.y}
local vec4={x=vec3.x,y=vec1.y}
local zone=ZONE_POLYGON_BASE:New(ZoneName or self.ZoneName,{vec1,vec2,vec3,vec4})
return zone
end
function ZONE_POLYGON_BASE:SmokeZone(SmokeColor,Segments)
self:F2(SmokeColor)
Segments=Segments or 10
local i=1
local j=#self._.Polygon
while i<=#self._.Polygon do
self:T({i,j,self._.Polygon[i],self._.Polygon[j]})
local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x
local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y
for Segment=0,Segments do
local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments)
local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments)
POINT_VEC2:New(PointX,PointY):Smoke(SmokeColor)
end
j=i
i=i+1
end
return self
end
function ZONE_POLYGON_BASE:FlareZone(FlareColor,Segments,Azimuth,AddHeight)
self:F2(FlareColor)
Segments=Segments or 10
AddHeight=AddHeight or 0
local i=1
local j=#self._.Polygon
while i<=#self._.Polygon do
self:T({i,j,self._.Polygon[i],self._.Polygon[j]})
local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x
local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y
for Segment=0,Segments do
local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments)
local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments)
POINT_VEC2:New(PointX,PointY,AddHeight):Flare(FlareColor,Azimuth)
end
j=i
i=i+1
end
return self
end
function ZONE_POLYGON_BASE:IsVec2InZone(Vec2)
self:F2(Vec2)
if not Vec2 then return false end
local Next
local Prev
local InPolygon=false
Next=1
Prev=#self._.Polygon
while Next<=#self._.Polygon do
self:T({Next,Prev,self._.Polygon[Next],self._.Polygon[Prev]})
if(((self._.Polygon[Next].y>Vec2.y)~=(self._.Polygon[Prev].y>Vec2.y))and
(Vec2.x<(self._.Polygon[Prev].x-self._.Polygon[Next].x)*(Vec2.y-self._.Polygon[Next].y)/(self._.Polygon[Prev].y-self._.Polygon[Next].y)+self._.Polygon[Next].x)
)then
InPolygon=not InPolygon
end
self:T2({InPolygon=InPolygon})
Prev=Next
Next=Next+1
end
self:T({InPolygon=InPolygon})
return InPolygon
end
function ZONE_POLYGON_BASE:IsVec3InZone(Vec3)
self:F2(Vec3)
if not Vec3 then return false end
local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
return InZone
end
function ZONE_POLYGON_BASE:GetRandomVec2()
local BS=self:GetBoundingSquare()
local Nmax=1000;local n=0
while n<Nmax do
local Vec2={x=math.random(BS.x1,BS.x2),y=math.random(BS.y1,BS.y2)}
if self:IsVec2InZone(Vec2)then
return Vec2
end
n=n+1
end
self:E("Could not find a random point in the polygon zone!")
return nil
end
function ZONE_POLYGON_BASE:GetRandomPointVec2()
self:F2()
local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2())
self:T2(PointVec2)
return PointVec2
end
function ZONE_POLYGON_BASE:GetRandomPointVec3()
self:F2()
local PointVec3=POINT_VEC3:NewFromVec2(self:GetRandomVec2())
self:T2(PointVec3)
return PointVec3
end
function ZONE_POLYGON_BASE:GetRandomCoordinate()
self:F2()
local Coordinate=COORDINATE:NewFromVec2(self:GetRandomVec2())
self:T2(Coordinate)
return Coordinate
end
function ZONE_POLYGON_BASE:GetBoundingSquare()
local x1=self._.Polygon[1].x
local y1=self._.Polygon[1].y
local x2=self._.Polygon[1].x
local y2=self._.Polygon[1].y
for i=2,#self._.Polygon do
self:T2({self._.Polygon[i],x1,y1,x2,y2})
x1=(x1>self._.Polygon[i].x)and self._.Polygon[i].x or x1
x2=(x2<self._.Polygon[i].x)and self._.Polygon[i].x or x2
y1=(y1>self._.Polygon[i].y)and self._.Polygon[i].y or y1
y2=(y2<self._.Polygon[i].y)and self._.Polygon[i].y or y2
end
return{x1=x1,y1=y1,x2=x2,y2=y2}
end
function ZONE_POLYGON_BASE:GetBoundingVec2()
local x1=self._.Polygon[1].x
local y1=self._.Polygon[1].y
local x2=self._.Polygon[1].x
local y2=self._.Polygon[1].y
for i=2,#self._.Polygon do
self:T2({self._.Polygon[i],x1,y1,x2,y2})
x1=(x1>self._.Polygon[i].x)and self._.Polygon[i].x or x1
x2=(x2<self._.Polygon[i].x)and self._.Polygon[i].x or x2
y1=(y1>self._.Polygon[i].y)and self._.Polygon[i].y or y1
y2=(y2<self._.Polygon[i].y)and self._.Polygon[i].y or y2
end
local vec1={x=x1,y=y1}
local vec2={x=x2,y=y2}
return vec1,vec2
end
function ZONE_POLYGON_BASE:Boundary(Coalition,Color,Radius,Alpha,Segments,Closed)
Coalition=Coalition or-1
Color=Color or{1,1,1}
Radius=Radius or 1000
Alpha=Alpha or 1
Segments=Segments or 10
Closed=Closed or false
local i=1
local j=#self._.Polygon
if(Closed)then
Limit=#self._.Polygon+1
else
Limit=#self._.Polygon
end
while i<=#self._.Polygon do
self:T({i,j,self._.Polygon[i],self._.Polygon[j]})
if j~=Limit then
local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x
local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y
for Segment=0,Segments do
local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments)
local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments)
end
end
j=i
i=i+1
end
return self
end
ZONE_POLYGON={
ClassName="ZONE_POLYGON",
}
function ZONE_POLYGON:New(ZoneName,ZoneGroup)
local GroupPoints=ZoneGroup:GetTaskRoute()
local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(ZoneName,GroupPoints))
self:F({ZoneName,ZoneGroup,self._.Polygon})
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
function ZONE_POLYGON:NewFromPointsArray(ZoneName,PointsArray)
local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(ZoneName,PointsArray))
self:F({ZoneName,self._.Polygon})
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
function ZONE_POLYGON:NewFromGroupName(GroupName)
local ZoneGroup=GROUP:FindByName(GroupName)
local GroupPoints=ZoneGroup:GetTaskRoute()
local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(GroupName,GroupPoints))
self:F({GroupName,ZoneGroup,self._.Polygon})
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
function ZONE_POLYGON:FindByName(ZoneName)
local ZoneFound=_DATABASE:FindZone(ZoneName)
return ZoneFound
end
function ZONE_POLYGON:Scan(ObjectCategories,UnitCategories)
self.ScanData={}
self.ScanData.Coalitions={}
self.ScanData.Scenery={}
self.ScanData.SceneryTable={}
self.ScanData.Units={}
local vectors=self:GetBoundingSquare()
local minVec3={x=vectors.x1,y=0,z=vectors.y1}
local maxVec3={x=vectors.x2,y=0,z=vectors.y2}
local minmarkcoord=COORDINATE:NewFromVec3(minVec3)
local maxmarkcoord=COORDINATE:NewFromVec3(maxVec3)
local ZoneRadius=minmarkcoord:Get2DDistance(maxmarkcoord)/2
local CenterVec3=self:GetCoordinate():GetVec3()
local SphereSearch={
id=world.VolumeType.SPHERE,
params={
point=CenterVec3,
radius=ZoneRadius,
}
}
local function EvaluateZone(ZoneObject)
if ZoneObject then
local ObjectCategory=ZoneObject:getCategory()
if(ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()and ZoneObject:isActive())or(ObjectCategory==Object.Category.STATIC and ZoneObject:isExist())then
local CoalitionDCSUnit=ZoneObject:getCoalition()
local Include=false
if not UnitCategories then
Include=true
else
local CategoryDCSUnit=ZoneObject:getDesc().category
for UnitCategoryID,UnitCategory in pairs(UnitCategories)do
if UnitCategory==CategoryDCSUnit then
Include=true
break
end
end
end
if Include then
local CoalitionDCSUnit=ZoneObject:getCoalition()
self.ScanData.Coalitions[CoalitionDCSUnit]=true
self.ScanData.Units[ZoneObject]=ZoneObject
self:F2({Name=ZoneObject:getName(),Coalition=CoalitionDCSUnit})
end
end
if ObjectCategory==Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint())then
local SceneryType=ZoneObject:getTypeName()
local SceneryName=ZoneObject:getName()
self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{}
self.ScanData.Scenery[SceneryType][SceneryName]=SCENERY:Register(SceneryName,ZoneObject)
table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName])
self:T({SCENERY=self.ScanData.Scenery[SceneryType][SceneryName]})
end
end
return true
end
local inzoneunits=SET_UNIT:New():FilterZones({self}):FilterOnce()
local inzonestatics=SET_STATIC:New():FilterZones({self}):FilterOnce()
inzoneunits:ForEach(
function(unit)
local Unit=unit
local DCS=Unit:GetDCSObject()
EvaluateZone(DCS)
end
)
inzonestatics:ForEach(
function(static)
local Static=static
local DCS=Static:GetDCSObject()
EvaluateZone(DCS)
end
)
local searchscenery=false
for _,_type in pairs(ObjectCategories)do
if _type==Object.Category.SCENERY then
searchscenery=true
end
end
if searchscenery then
world.searchObjects({Object.Category.SCENERY},SphereSearch,EvaluateZone)
end
end
function ZONE_POLYGON:GetScannedUnits()
return self.ScanData.Units
end
function ZONE_POLYGON:GetScannedSetUnit()
local SetUnit=SET_UNIT:New()
if self.ScanData then
for ObjectID,UnitObject in pairs(self.ScanData.Units)do
local UnitObject=UnitObject
if UnitObject:isExist()then
local FoundUnit=UNIT:FindByName(UnitObject:getName())
if FoundUnit then
SetUnit:AddUnit(FoundUnit)
else
local FoundStatic=STATIC:FindByName(UnitObject:getName())
if FoundStatic then
SetUnit:AddUnit(FoundStatic)
end
end
end
end
end
return SetUnit
end
function ZONE_POLYGON:GetScannedSetGroup()
self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New()
self.ScanSetGroup.Set={}
if self.ScanData then
for ObjectID,UnitObject in pairs(self.ScanData.Units)do
local UnitObject=UnitObject
if UnitObject:isExist()then
local FoundUnit=UNIT:FindByName(UnitObject:getName())
if FoundUnit then
local group=FoundUnit:GetGroup()
self.ScanSetGroup:AddGroup(group)
end
end
end
end
return self.ScanSetGroup
end
function ZONE_POLYGON:CountScannedCoalitions()
local Count=0
for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do
Count=Count+1
end
return Count
end
function ZONE_POLYGON:CheckScannedCoalition(Coalition)
if Coalition then
return self.ScanData.Coalitions[Coalition]
end
return nil
end
function ZONE_POLYGON:GetScannedCoalition(Coalition)
if Coalition then
return self.ScanData.Coalitions[Coalition]
else
local Count=0
local ReturnCoalition=nil
for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do
Count=Count+1
ReturnCoalition=CoalitionID
end
if Count~=1 then
ReturnCoalition=nil
end
return ReturnCoalition
end
end
function ZONE_POLYGON:GetScannedSceneryType(SceneryType)
return self.ScanData.Scenery[SceneryType]
end
function ZONE_POLYGON:GetScannedSceneryObjects()
return self.ScanData.SceneryTable
end
function ZONE_POLYGON:GetScannedScenery()
return self.ScanData.Scenery
end
function ZONE_POLYGON:GetScannedSetScenery()
local scenery=SET_SCENERY:New()
local objects=self:GetScannedSceneryObjects()
for _,_obj in pairs(objects)do
scenery:AddScenery(_obj)
end
return scenery
end
function ZONE_POLYGON:IsAllInZoneOfCoalition(Coalition)
return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==true
end
function ZONE_POLYGON:IsAllInZoneOfOtherCoalition(Coalition)
return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==nil
end
function ZONE_POLYGON:IsSomeInZoneOfCoalition(Coalition)
return self:CountScannedCoalitions()>1 and self:GetScannedCoalition(Coalition)==true
end
function ZONE_POLYGON:IsNoneInZoneOfCoalition(Coalition)
return self:GetScannedCoalition(Coalition)==nil
end
function ZONE_POLYGON:IsNoneInZone()
return self:CountScannedCoalitions()==0
end
do
ZONE_ELASTIC={
ClassName="ZONE_ELASTIC",
points={},
setGroups={}
}
function ZONE_ELASTIC:New(ZoneName,Points)
local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(ZoneName,Points))
_EVENTDISPATCHER:CreateEventNewZone(self)
if Points then
self.points=Points
end
return self
end
function ZONE_ELASTIC:AddVertex2D(Vec2)
table.insert(self.points,Vec2)
return self
end
function ZONE_ELASTIC:AddVertex3D(Vec3)
table.insert(self.points,{x=Vec3.x,y=Vec3.z})
return self
end
function ZONE_ELASTIC:AddSetGroup(GroupSet)
table.insert(self.setGroups,GroupSet)
return self
end
function ZONE_ELASTIC:Update(Delay,Draw)
self:T(string.format("Updating ZONE_ELASTIC %s",tostring(self.ZoneName)))
local points=UTILS.DeepCopy(self.points or{})
if self.setGroups then
for _,_setGroup in pairs(self.setGroups)do
local setGroup=_setGroup
for _,_group in pairs(setGroup.Set)do
local group=_group
if group and group:IsAlive()then
table.insert(points,group:GetVec2())
end
end
end
end
self._.Polygon=self:_ConvexHull(points)
if Draw~=false then
if self.DrawID or Draw==true then
self:UndrawZone()
self:DrawZone()
end
end
return self
end
function ZONE_ELASTIC:StartUpdate(Tstart,dT,Tstop,Draw)
self.updateID=self:ScheduleRepeat(Tstart,dT,0,Tstop,ZONE_ELASTIC.Update,self,0,Draw)
return self
end
function ZONE_ELASTIC:StopUpdate(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,ZONE_ELASTIC.StopUpdate,self)
else
if self.updateID then
self:ScheduleStop(self.updateID)
self.updateID=nil
end
end
return self
end
function ZONE_ELASTIC:_ConvexHull(pl)
if#pl==0 then
return{}
end
table.sort(pl,function(left,right)
return left.x<right.x
end)
local h={}
local function ccw(a,b,c)
return(b.x-a.x)*(c.y-a.y)>(b.y-a.y)*(c.x-a.x)
end
for i,pt in pairs(pl)do
while#h>=2 and not ccw(h[#h-1],h[#h],pt)do
table.remove(h,#h)
end
table.insert(h,pt)
end
local t=#h+1
for i=#pl,1,-1 do
local pt=pl[i]
while#h>=t and not ccw(h[#h-1],h[#h],pt)do
table.remove(h,#h)
end
table.insert(h,pt)
end
table.remove(h,#h)
return h
end
end
do
ZONE_AIRBASE={
ClassName="ZONE_AIRBASE",
}
function ZONE_AIRBASE:New(AirbaseName,Radius)
Radius=Radius or 4000
local Airbase=AIRBASE:FindByName(AirbaseName)
local self=BASE:Inherit(self,ZONE_RADIUS:New(AirbaseName,Airbase:GetVec2(),Radius,true))
self._.ZoneAirbase=Airbase
self._.ZoneVec2Cache=self._.ZoneAirbase:GetVec2()
if Airbase:IsShip()then
self.isShip=true
self.isHelipad=false
self.isAirdrome=false
elseif Airbase:IsHelipad()then
self.isShip=false
self.isHelipad=true
self.isAirdrome=false
elseif Airbase:IsAirdrome()then
self.isShip=false
self.isHelipad=false
self.isAirdrome=true
end
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
function ZONE_AIRBASE:GetAirbase()
return self._.ZoneAirbase
end
function ZONE_AIRBASE:GetVec2()
self:F(self.ZoneName)
local ZoneVec2=nil
if self._.ZoneAirbase:IsAlive()then
ZoneVec2=self._.ZoneAirbase:GetVec2()
self._.ZoneVec2Cache=ZoneVec2
else
ZoneVec2=self._.ZoneVec2Cache
end
self:T({ZoneVec2})
return ZoneVec2
end
function ZONE_AIRBASE:GetRandomPointVec2(inner,outer)
self:F(self.ZoneName,inner,outer)
local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2())
self:T3({PointVec2})
return PointVec2
end
end
ZONE_DETECTION={
ClassName="ZONE_DETECTION",
}
function ZONE_DETECTION:New(ZoneName,Detection,Radius)
local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName))
self:F({ZoneName,Detection,Radius})
self.Detection=Detection
self.Radius=Radius
return self
end
function ZONE_DETECTION:BoundZone(Points,CountryID,UnBound)
local Point={}
local Vec2=self:GetVec2()
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,(360/Points)do
local Radial=Angle*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
local CountryName=_DATABASE.COUNTRY_NAME[CountryID]
local Tire={
["country"]=CountryName,
["category"]="Fortifications",
["canCargo"]=false,
["shape_name"]="H-tyre_B_WF",
["type"]="Black_Tyre_WF",
["y"]=Point.y,
["x"]=Point.x,
["name"]=string.format("%s-Tire #%0d",self:GetName(),Angle),
["heading"]=0,
}
local Group=coalition.addStaticObject(CountryID,Tire)
if UnBound and UnBound==true then
Group:destroy()
end
end
return self
end
function ZONE_DETECTION:SmokeZone(SmokeColor,Points,AddHeight,AngleOffset)
self:F2(SmokeColor)
local Point={}
local Vec2=self:GetVec2()
AddHeight=AddHeight or 0
AngleOffset=AngleOffset or 0
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,360/Points do
local Radial=(Angle+AngleOffset)*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
POINT_VEC2:New(Point.x,Point.y,AddHeight):Smoke(SmokeColor)
end
return self
end
function ZONE_DETECTION:FlareZone(FlareColor,Points,Azimuth,AddHeight)
self:F2({FlareColor,Azimuth})
local Point={}
local Vec2=self:GetVec2()
AddHeight=AddHeight or 0
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,360/Points do
local Radial=Angle*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
POINT_VEC2:New(Point.x,Point.y,AddHeight):Flare(FlareColor,Azimuth)
end
return self
end
function ZONE_DETECTION:GetRadius()
self:F2(self.ZoneName)
self:T2({self.Radius})
return self.Radius
end
function ZONE_DETECTION:SetRadius(Radius)
self:F2(self.ZoneName)
self.Radius=Radius
self:T2({self.Radius})
return self.Radius
end
function ZONE_DETECTION:IsVec2InZone(Vec2)
self:F2(Vec2)
local Coordinates=self.Detection:GetDetectedItemCoordinates()
for CoordinateID,Coordinate in pairs(Coordinates)do
local ZoneVec2=Coordinate:GetVec2()
if ZoneVec2 then
if((Vec2.x-ZoneVec2.x)^2+(Vec2.y-ZoneVec2.y)^2)^0.5<=self:GetRadius()then
return true
end
end
end
return false
end
function ZONE_DETECTION:IsVec3InZone(Vec3)
self:F2(Vec3)
local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
return InZone
end
DATABASE={
ClassName="DATABASE",
Templates={
Units={},
Groups={},
Statics={},
ClientsByName={},
ClientsByID={},
},
UNITS={},
UNITS_Index={},
STATICS={},
GROUPS={},
PLAYERS={},
PLAYERSJOINED={},
PLAYERUNITS={},
CLIENTS={},
CARGOS={},
AIRBASES={},
COUNTRY_ID={},
COUNTRY_NAME={},
NavPoints={},
PLAYERSETTINGS={},
ZONENAMES={},
HITS={},
DESTROYS={},
ZONES={},
ZONES_GOAL={},
WAREHOUSES={},
FLIGHTGROUPS={},
FLIGHTCONTROLS={},
}
local _DATABASECoalition=
{
[1]="Red",
[2]="Blue",
[3]="Neutral",
}
local _DATABASECategory=
{
["plane"]=Unit.Category.AIRPLANE,
["helicopter"]=Unit.Category.HELICOPTER,
["vehicle"]=Unit.Category.GROUND_UNIT,
["ship"]=Unit.Category.SHIP,
["static"]=Unit.Category.STRUCTURE,
}
function DATABASE:New()
local self=BASE:Inherit(self,BASE:New())
self:SetEventPriority(1)
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.RemoveUnit,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Hit,self.AccountHits)
self:HandleEvent(EVENTS.NewCargo)
self:HandleEvent(EVENTS.DeleteCargo)
self:HandleEvent(EVENTS.NewZone)
self:HandleEvent(EVENTS.DeleteZone)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventOnPlayerLeaveUnit)
self:_RegisterTemplates()
self:_RegisterGroupsAndUnits()
self:_RegisterClients()
self:_RegisterStatics()
self.UNITS_Position=0
return self
end
function DATABASE:FindUnit(UnitName)
local UnitFound=self.UNITS[UnitName]
return UnitFound
end
function DATABASE:AddUnit(DCSUnitName)
if not self.UNITS[DCSUnitName]then
self:T({"Add UNIT:",DCSUnitName})
self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName)
end
return self.UNITS[DCSUnitName]
end
function DATABASE:DeleteUnit(DCSUnitName)
self.UNITS[DCSUnitName]=nil
end
function DATABASE:AddStatic(DCSStaticName)
if not self.STATICS[DCSStaticName]then
self.STATICS[DCSStaticName]=STATIC:Register(DCSStaticName)
return self.STATICS[DCSStaticName]
end
return nil
end
function DATABASE:DeleteStatic(DCSStaticName)
self.STATICS[DCSStaticName]=nil
end
function DATABASE:FindStatic(StaticName)
local StaticFound=self.STATICS[StaticName]
return StaticFound
end
function DATABASE:AddAirbase(AirbaseName)
if not self.AIRBASES[AirbaseName]then
self.AIRBASES[AirbaseName]=AIRBASE:Register(AirbaseName)
end
return self.AIRBASES[AirbaseName]
end
function DATABASE:DeleteAirbase(AirbaseName)
self.AIRBASES[AirbaseName]=nil
end
function DATABASE:FindAirbase(AirbaseName)
local AirbaseFound=self.AIRBASES[AirbaseName]
return AirbaseFound
end
do
function DATABASE:FindZone(ZoneName)
local ZoneFound=self.ZONES[ZoneName]
return ZoneFound
end
function DATABASE:AddZone(ZoneName,Zone)
if not self.ZONES[ZoneName]then
self.ZONES[ZoneName]=Zone
end
end
function DATABASE:DeleteZone(ZoneName)
self.ZONES[ZoneName]=nil
end
function DATABASE:_RegisterZones()
for ZoneID,ZoneData in pairs(env.mission.triggers.zones)do
local ZoneName=ZoneData.name
local color=ZoneData.color or{1,0,0,0.15}
local Zone=nil
if ZoneData.type==0 then
self:I(string.format("Register ZONE: %s (Circular)",ZoneName))
Zone=ZONE:New(ZoneName)
else
self:I(string.format("Register ZONE: %s (Polygon, Quad)",ZoneName))
Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,ZoneData.verticies)
end
if Zone then
Zone.Color=color
Zone.ZoneID=ZoneData.zoneId
local ZoneProperties=ZoneData.properties or nil
Zone.Properties={}
if ZoneName and ZoneProperties then
for _,ZoneProp in ipairs(ZoneProperties)do
if ZoneProp.key then
Zone.Properties[ZoneProp.key]=ZoneProp.value
end
end
end
self.ZONENAMES[ZoneName]=ZoneName
self:AddZone(ZoneName,Zone)
end
end
for ZoneGroupName,ZoneGroup in pairs(self.GROUPS)do
if ZoneGroupName:match("#ZONE_POLYGON")then
local ZoneName1=ZoneGroupName:match("(.*)#ZONE_POLYGON")
local ZoneName2=ZoneGroupName:match(".*#ZONE_POLYGON(.*)")
local ZoneName=ZoneName1..(ZoneName2 or"")
self:I(string.format("Register ZONE: %s (Polygon)",ZoneName))
local Zone_Polygon=ZONE_POLYGON:New(ZoneName,ZoneGroup)
Zone_Polygon:SetColor({1,0,0},0.15)
self.ZONENAMES[ZoneName]=ZoneName
self:AddZone(ZoneName,Zone_Polygon)
end
end
if env.mission.drawings and env.mission.drawings.layers then
for layerID,layerData in pairs(env.mission.drawings.layers or{})do
for objectID,objectData in pairs(layerData.objects or{})do
if objectData.polygonMode=="free"and objectData.points and#objectData.points>=4 then
local ZoneName=objectData.name or"Unknown Drawing Zone"
local vec2={x=objectData.mapX,y=objectData.mapY}
local points=UTILS.DeepCopy(objectData.points)
for i,_point in pairs(points)do
local point=_point
points[i]=UTILS.Vec2Add(point,vec2)
end
table.remove(points,#points)
self:I(string.format("Register ZONE: %s (Polygon drawing with %d verticies)",ZoneName,#points))
local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,points)
Zone:SetColor({1,0,0},0.15)
self.ZONENAMES[ZoneName]=ZoneName
self:AddZone(ZoneName,Zone)
end
end
end
end
end
end
do
function DATABASE:FindZoneGoal(ZoneName)
local ZoneFound=self.ZONES_GOAL[ZoneName]
return ZoneFound
end
function DATABASE:AddZoneGoal(ZoneName,Zone)
if not self.ZONES_GOAL[ZoneName]then
self.ZONES_GOAL[ZoneName]=Zone
end
end
function DATABASE:DeleteZoneGoal(ZoneName)
self.ZONES_GOAL[ZoneName]=nil
end
end
do
function DATABASE:AddCargo(Cargo)
if not self.CARGOS[Cargo.Name]then
self.CARGOS[Cargo.Name]=Cargo
end
end
function DATABASE:DeleteCargo(CargoName)
self.CARGOS[CargoName]=nil
end
function DATABASE:FindCargo(CargoName)
local CargoFound=self.CARGOS[CargoName]
return CargoFound
end
function DATABASE:IsCargo(TemplateName)
TemplateName=env.getValueDictByKey(TemplateName)
local Cargo=TemplateName:match("#(CARGO)")
return Cargo and Cargo=="CARGO"
end
function DATABASE:_RegisterCargos()
local Groups=UTILS.DeepCopy(self.GROUPS)
for CargoGroupName,CargoGroup in pairs(Groups)do
if self:IsCargo(CargoGroupName)then
local CargoInfo=CargoGroupName:match("#CARGO(.*)")
local CargoParam=CargoInfo and CargoInfo:match("%((.*)%)")
local CargoName1=CargoGroupName:match("(.*)#CARGO%(.*%)")
local CargoName2=CargoGroupName:match(".*#CARGO%(.*%)(.*)")
local CargoName=CargoName1..(CargoName2 or"")
local Type=CargoParam and CargoParam:match("T=([%a%d ]+),?")
local Name=CargoParam and CargoParam:match("N=([%a%d]+),?")or CargoName
local LoadRadius=CargoParam and tonumber(CargoParam:match("RR=([%a%d]+),?"))
local NearRadius=CargoParam and tonumber(CargoParam:match("NR=([%a%d]+),?"))
self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_GROUP:New(CargoGroup,Type,Name,LoadRadius,NearRadius)
end
end
for CargoStaticName,CargoStatic in pairs(self.STATICS)do
if self:IsCargo(CargoStaticName)then
local CargoInfo=CargoStaticName:match("#CARGO(.*)")
local CargoParam=CargoInfo and CargoInfo:match("%((.*)%)")
local CargoName=CargoStaticName:match("(.*)#CARGO")
local Type=CargoParam and CargoParam:match("T=([%a%d ]+),?")
local Category=CargoParam and CargoParam:match("C=([%a%d ]+),?")
local Name=CargoParam and CargoParam:match("N=([%a%d]+),?")or CargoName
local LoadRadius=CargoParam and tonumber(CargoParam:match("RR=([%a%d]+),?"))
local NearRadius=CargoParam and tonumber(CargoParam:match("NR=([%a%d]+),?"))
if Category=="SLING"then
self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_SLINGLOAD:New(CargoStatic,Type,Name,LoadRadius,NearRadius)
else
if Category=="CRATE"then
self:I({"Register CargoCrate:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_CRATE:New(CargoStatic,Type,Name,LoadRadius,NearRadius)
end
end
end
end
end
end
function DATABASE:FindClient(ClientName)
local ClientFound=self.CLIENTS[ClientName]
return ClientFound
end
function DATABASE:AddClient(ClientName)
if not self.CLIENTS[ClientName]then
self.CLIENTS[ClientName]=CLIENT:Register(ClientName)
end
return self.CLIENTS[ClientName]
end
function DATABASE:FindGroup(GroupName)
local GroupFound=self.GROUPS[GroupName]
return GroupFound
end
function DATABASE:AddGroup(GroupName)
if not self.GROUPS[GroupName]then
self:T({"Add GROUP:",GroupName})
self.GROUPS[GroupName]=GROUP:Register(GroupName)
end
return self.GROUPS[GroupName]
end
function DATABASE:AddPlayer(UnitName,PlayerName)
if PlayerName then
self:T({"Add player for unit:",UnitName,PlayerName})
self.PLAYERS[PlayerName]=UnitName
self.PLAYERUNITS[PlayerName]=self:FindUnit(UnitName)
self.PLAYERSJOINED[PlayerName]=PlayerName
end
end
function DATABASE:DeletePlayer(UnitName,PlayerName)
if PlayerName then
self:T({"Clean player:",PlayerName})
self.PLAYERS[PlayerName]=nil
self.PLAYERUNITS[PlayerName]=nil
end
end
function DATABASE:GetPlayers()
return self.PLAYERS
end
function DATABASE:GetPlayerUnits()
return self.PLAYERUNITS
end
function DATABASE:GetPlayersJoined()
return self.PLAYERSJOINED
end
function DATABASE:Spawn(SpawnTemplate)
self:F(SpawnTemplate.name)
self:T({SpawnTemplate.SpawnCountryID,SpawnTemplate.SpawnCategoryID})
local SpawnCoalitionID=SpawnTemplate.CoalitionID
local SpawnCountryID=SpawnTemplate.CountryID
local SpawnCategoryID=SpawnTemplate.CategoryID
SpawnTemplate.CoalitionID=nil
SpawnTemplate.CountryID=nil
SpawnTemplate.CategoryID=nil
self:_RegisterGroupTemplate(SpawnTemplate,SpawnCoalitionID,SpawnCategoryID,SpawnCountryID)
self:T3(SpawnTemplate)
coalition.addGroup(SpawnCountryID,SpawnCategoryID,SpawnTemplate)
SpawnTemplate.CoalitionID=SpawnCoalitionID
SpawnTemplate.CountryID=SpawnCountryID
SpawnTemplate.CategoryID=SpawnCategoryID
local SpawnGroup=self:AddGroup(SpawnTemplate.name)
for UnitID,UnitData in pairs(SpawnTemplate.units)do
self:AddUnit(UnitData.name)
end
return SpawnGroup
end
function DATABASE:SetStatusGroup(GroupName,Status)
self:F2(Status)
self.Templates.Groups[GroupName].Status=Status
end
function DATABASE:GetStatusGroup(GroupName)
self:F2(GroupName)
if self.Templates.Groups[GroupName]then
return self.Templates.Groups[GroupName].Status
else
return""
end
end
function DATABASE:_RegisterGroupTemplate(GroupTemplate,CoalitionSide,CategoryID,CountryID,GroupName)
local GroupTemplateName=GroupName or env.getValueDictByKey(GroupTemplate.name)
if not self.Templates.Groups[GroupTemplateName]then
self.Templates.Groups[GroupTemplateName]={}
self.Templates.Groups[GroupTemplateName].Status=nil
end
if GroupTemplate.route and GroupTemplate.route.spans then
GroupTemplate.route.spans=nil
end
GroupTemplate.CategoryID=CategoryID
GroupTemplate.CoalitionID=CoalitionSide
GroupTemplate.CountryID=CountryID
self.Templates.Groups[GroupTemplateName].GroupName=GroupTemplateName
self.Templates.Groups[GroupTemplateName].Template=GroupTemplate
self.Templates.Groups[GroupTemplateName].groupId=GroupTemplate.groupId
self.Templates.Groups[GroupTemplateName].UnitCount=#GroupTemplate.units
self.Templates.Groups[GroupTemplateName].Units=GroupTemplate.units
self.Templates.Groups[GroupTemplateName].CategoryID=CategoryID
self.Templates.Groups[GroupTemplateName].CoalitionID=CoalitionSide
self.Templates.Groups[GroupTemplateName].CountryID=CountryID
local UnitNames={}
for unit_num,UnitTemplate in pairs(GroupTemplate.units)do
UnitTemplate.name=env.getValueDictByKey(UnitTemplate.name)
self.Templates.Units[UnitTemplate.name]={}
self.Templates.Units[UnitTemplate.name].UnitName=UnitTemplate.name
self.Templates.Units[UnitTemplate.name].Template=UnitTemplate
self.Templates.Units[UnitTemplate.name].GroupName=GroupTemplateName
self.Templates.Units[UnitTemplate.name].GroupTemplate=GroupTemplate
self.Templates.Units[UnitTemplate.name].GroupId=GroupTemplate.groupId
self.Templates.Units[UnitTemplate.name].CategoryID=CategoryID
self.Templates.Units[UnitTemplate.name].CoalitionID=CoalitionSide
self.Templates.Units[UnitTemplate.name].CountryID=CountryID
if UnitTemplate.skill and(UnitTemplate.skill=="Client"or UnitTemplate.skill=="Player")then
self.Templates.ClientsByName[UnitTemplate.name]=UnitTemplate
self.Templates.ClientsByName[UnitTemplate.name].CategoryID=CategoryID
self.Templates.ClientsByName[UnitTemplate.name].CoalitionID=CoalitionSide
self.Templates.ClientsByName[UnitTemplate.name].CountryID=CountryID
self.Templates.ClientsByID[UnitTemplate.unitId]=UnitTemplate
end
UnitNames[#UnitNames+1]=self.Templates.Units[UnitTemplate.name].UnitName
end
self:T({Group=self.Templates.Groups[GroupTemplateName].GroupName,
Coalition=self.Templates.Groups[GroupTemplateName].CoalitionID,
Category=self.Templates.Groups[GroupTemplateName].CategoryID,
Country=self.Templates.Groups[GroupTemplateName].CountryID,
Units=UnitNames
}
)
end
function DATABASE:GetGroupTemplate(GroupName)
local GroupTemplate=self.Templates.Groups[GroupName].Template
GroupTemplate.SpawnCoalitionID=self.Templates.Groups[GroupName].CoalitionID
GroupTemplate.SpawnCategoryID=self.Templates.Groups[GroupName].CategoryID
GroupTemplate.SpawnCountryID=self.Templates.Groups[GroupName].CountryID
return GroupTemplate
end
function DATABASE:_RegisterStaticTemplate(StaticTemplate,CoalitionID,CategoryID,CountryID)
local StaticTemplate=UTILS.DeepCopy(StaticTemplate)
local StaticTemplateGroupName=env.getValueDictByKey(StaticTemplate.name)
local StaticTemplateName=StaticTemplate.units[1].name
self.Templates.Statics[StaticTemplateName]=self.Templates.Statics[StaticTemplateName]or{}
StaticTemplate.CategoryID=CategoryID
StaticTemplate.CoalitionID=CoalitionID
StaticTemplate.CountryID=CountryID
self.Templates.Statics[StaticTemplateName].StaticName=StaticTemplateGroupName
self.Templates.Statics[StaticTemplateName].GroupTemplate=StaticTemplate
self.Templates.Statics[StaticTemplateName].UnitTemplate=StaticTemplate.units[1]
self.Templates.Statics[StaticTemplateName].CategoryID=CategoryID
self.Templates.Statics[StaticTemplateName].CoalitionID=CoalitionID
self.Templates.Statics[StaticTemplateName].CountryID=CountryID
self:T({Static=self.Templates.Statics[StaticTemplateName].StaticName,
Coalition=self.Templates.Statics[StaticTemplateName].CoalitionID,
Category=self.Templates.Statics[StaticTemplateName].CategoryID,
Country=self.Templates.Statics[StaticTemplateName].CountryID
}
)
self:AddStatic(StaticTemplateName)
return self
end
function DATABASE:GetStaticGroupTemplate(StaticName)
if self.Templates.Statics[StaticName]then
local StaticTemplate=self.Templates.Statics[StaticName].GroupTemplate
return StaticTemplate,self.Templates.Statics[StaticName].CoalitionID,self.Templates.Statics[StaticName].CategoryID,self.Templates.Statics[StaticName].CountryID
else
self:E("ERROR: Static group template does NOT exist for static "..tostring(StaticName))
return nil
end
end
function DATABASE:GetStaticUnitTemplate(StaticName)
if self.Templates.Statics[StaticName]then
local UnitTemplate=self.Templates.Statics[StaticName].UnitTemplate
return UnitTemplate,self.Templates.Statics[StaticName].CoalitionID,self.Templates.Statics[StaticName].CategoryID,self.Templates.Statics[StaticName].CountryID
else
self:E("ERROR: Static unit template does NOT exist for static "..tostring(StaticName))
return nil
end
end
function DATABASE:GetGroupNameFromUnitName(UnitName)
if self.Templates.Units[UnitName]then
return self.Templates.Units[UnitName].GroupName
else
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
return nil
end
end
function DATABASE:GetGroupTemplateFromUnitName(UnitName)
if self.Templates.Units[UnitName]then
return self.Templates.Units[UnitName].GroupTemplate
else
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
return nil
end
end
function DATABASE:GetUnitTemplateFromUnitName(UnitName)
if self.Templates.Units[UnitName]then
return self.Templates.Units[UnitName]
else
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
return nil
end
end
function DATABASE:GetCoalitionFromClientTemplate(ClientName)
return self.Templates.ClientsByName[ClientName].CoalitionID
end
function DATABASE:GetCategoryFromClientTemplate(ClientName)
return self.Templates.ClientsByName[ClientName].CategoryID
end
function DATABASE:GetCountryFromClientTemplate(ClientName)
return self.Templates.ClientsByName[ClientName].CountryID
end
function DATABASE:GetCoalitionFromAirbase(AirbaseName)
return self.AIRBASES[AirbaseName]:GetCoalition()
end
function DATABASE:GetCategoryFromAirbase(AirbaseName)
return self.AIRBASES[AirbaseName]:GetCategory()
end
function DATABASE:_RegisterPlayers()
local CoalitionsData={AlivePlayersRed=coalition.getPlayers(coalition.side.RED),AlivePlayersBlue=coalition.getPlayers(coalition.side.BLUE),AlivePlayersNeutral=coalition.getPlayers(coalition.side.NEUTRAL)}
for CoalitionId,CoalitionData in pairs(CoalitionsData)do
for UnitId,UnitData in pairs(CoalitionData)do
self:T3({"UnitData:",UnitData})
if UnitData and UnitData:isExist()then
local UnitName=UnitData:getName()
local PlayerName=UnitData:getPlayerName()
if not self.PLAYERS[PlayerName]then
self:I({"Add player for unit:",UnitName,PlayerName})
self:AddPlayer(UnitName,PlayerName)
end
end
end
end
return self
end
function DATABASE:_RegisterGroupsAndUnits()
local CoalitionsData={GroupsRed=coalition.getGroups(coalition.side.RED),GroupsBlue=coalition.getGroups(coalition.side.BLUE),GroupsNeutral=coalition.getGroups(coalition.side.NEUTRAL)}
for CoalitionId,CoalitionData in pairs(CoalitionsData)do
for DCSGroupId,DCSGroup in pairs(CoalitionData)do
if DCSGroup:isExist()then
local DCSGroupName=DCSGroup:getName()
self:I(string.format("Register Group: %s",tostring(DCSGroupName)))
self:AddGroup(DCSGroupName)
for DCSUnitId,DCSUnit in pairs(DCSGroup:getUnits())do
local DCSUnitName=DCSUnit:getName()
self:I(string.format("Register Unit: %s",tostring(DCSUnitName)))
self:AddUnit(DCSUnitName)
end
else
self:E({"Group does not exist: ",DCSGroup})
end
end
end
return self
end
function DATABASE:_RegisterClients()
for ClientName,ClientTemplate in pairs(self.Templates.ClientsByName)do
self:I(string.format("Register Client: %s",tostring(ClientName)))
local client=self:AddClient(ClientName)
client.SpawnCoord=COORDINATE:New(ClientTemplate.x,ClientTemplate.alt,ClientTemplate.y)
end
return self
end
function DATABASE:_RegisterStatics()
local CoalitionsData={GroupsRed=coalition.getStaticObjects(coalition.side.RED),GroupsBlue=coalition.getStaticObjects(coalition.side.BLUE),GroupsNeutral=coalition.getStaticObjects(coalition.side.NEUTRAL)}
for CoalitionId,CoalitionData in pairs(CoalitionsData)do
for DCSStaticId,DCSStatic in pairs(CoalitionData)do
if DCSStatic:isExist()then
local DCSStaticName=DCSStatic:getName()
self:I(string.format("Register Static: %s",tostring(DCSStaticName)))
self:AddStatic(DCSStaticName)
else
self:E({"Static does not exist: ",DCSStatic})
end
end
end
return self
end
function DATABASE:_RegisterAirbases()
for DCSAirbaseId,DCSAirbase in pairs(world.getAirbases())do
self:_RegisterAirbase(DCSAirbase)
end
return self
end
function DATABASE:_RegisterAirbase(airbase)
if airbase then
local DCSAirbaseName=airbase:getName()
local airbaseID=airbase:getID()
local airbase=self:AddAirbase(DCSAirbaseName)
local airbaseUID=airbase:GetID(true)
local text=string.format("Register %s: %s (UID=%d), Runways=%d, Parking=%d [",AIRBASE.CategoryName[airbase.category],tostring(DCSAirbaseName),airbaseUID,#airbase.runways,airbase.NparkingTotal)
for _,terminalType in pairs(AIRBASE.TerminalType)do
if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType]then
text=text..string.format("%d=%d ",terminalType,airbase.NparkingTerminal[terminalType])
end
end
text=text.."]"
self:I(text)
end
return self
end
function DATABASE:_EventOnBirth(Event)
self:F({Event})
if Event.IniDCSUnit then
if Event.IniObjectCategory==Object.Category.STATIC then
self:AddStatic(Event.IniDCSUnitName)
else
if Event.IniObjectCategory==Object.Category.UNIT then
self:AddUnit(Event.IniDCSUnitName)
self:AddGroup(Event.IniDCSGroupName)
local DCSAirbase=Airbase.getByName(Event.IniDCSUnitName)
if DCSAirbase then
self:I(string.format("Adding airbase %s",tostring(Event.IniDCSUnitName)))
self:AddAirbase(Event.IniDCSUnitName)
end
end
end
if Event.IniObjectCategory==Object.Category.UNIT then
Event.IniUnit=self:FindUnit(Event.IniDCSUnitName)
Event.IniGroup=self:FindGroup(Event.IniDCSGroupName)
local client=self.CLIENTS[Event.IniDCSUnitName]
if client then
end
local PlayerName=Event.IniUnit:GetPlayerName()
if PlayerName then
self:I(string.format("Player '%s' joint unit '%s' of group '%s'",tostring(PlayerName),tostring(Event.IniDCSUnitName),tostring(Event.IniDCSGroupName)))
if not client then
client=self:AddClient(Event.IniDCSUnitName)
end
client:AddPlayer(PlayerName)
if not self.PLAYERS[PlayerName]then
self:AddPlayer(Event.IniUnitName,PlayerName)
end
local Settings=SETTINGS:Set(PlayerName)
Settings:SetPlayerMenu(Event.IniUnit)
self:CreateEventPlayerEnterAircraft(Event.IniUnit)
end
end
end
end
function DATABASE:_EventOnDeadOrCrash(Event)
if Event.IniDCSUnit then
local name=Event.IniDCSUnitName
if Event.IniObjectCategory==3 then
if self.STATICS[Event.IniDCSUnitName]then
self:DeleteStatic(Event.IniDCSUnitName)
end
if self.UNITS[Event.IniDCSUnitName]then
self:T("STATIC Event for UNIT "..tostring(Event.IniDCSUnitName))
local DCSUnit=_DATABASE:FindUnit(Event.IniDCSUnitName)
self:T({DCSUnit})
if DCSUnit then
return
end
end
else
if Event.IniObjectCategory==1 then
if self.UNITS[Event.IniDCSUnitName]then
self:DeleteUnit(Event.IniDCSUnitName)
end
local client=self.CLIENTS[name]
if client then
client:RemovePlayers()
end
end
end
local airbase=self.AIRBASES[Event.IniDCSUnitName]
if airbase and(airbase:IsHelipad()or airbase:IsShip())then
self:DeleteAirbase(Event.IniDCSUnitName)
end
end
self:AccountDestroys(Event)
end
function DATABASE:_EventOnPlayerEnterUnit(Event)
self:F2({Event})
if Event.IniDCSUnit then
if Event.IniObjectCategory==1 then
self:AddUnit(Event.IniDCSUnitName)
Event.IniUnit=self:FindUnit(Event.IniDCSUnitName)
self:AddGroup(Event.IniDCSGroupName)
local PlayerName=Event.IniDCSUnit:getPlayerName()
if PlayerName then
if not self.PLAYERS[PlayerName]then
self:AddPlayer(Event.IniDCSUnitName,PlayerName)
end
local Settings=SETTINGS:Set(PlayerName)
Settings:SetPlayerMenu(Event.IniUnit)
else
self:E("ERROR: getPlayerName() returned nil for event PlayerEnterUnit")
end
end
end
end
function DATABASE:_EventOnPlayerLeaveUnit(Event)
self:F2({Event})
if Event.IniUnit then
if Event.IniObjectCategory==1 then
local PlayerName=Event.IniUnit:GetPlayerName()
if PlayerName then
self:I(string.format("Player '%s' left unit %s",tostring(PlayerName),tostring(Event.IniUnitName)))
local Settings=SETTINGS:Set(PlayerName)
Settings:RemovePlayerMenu(Event.IniUnit)
self:DeletePlayer(Event.IniUnit,PlayerName)
local client=self.CLIENTS[Event.IniDCSUnitName]
if client then
client:RemovePlayer(PlayerName)
end
end
end
end
end
function DATABASE:ForEach(IteratorFunction,FinalizeFunction,arg,Set)
self:F2(arg)
local function CoRoutine()
local Count=0
for ObjectID,Object in pairs(Set)do
self:T2(Object)
IteratorFunction(Object,unpack(arg))
Count=Count+1
end
return true
end
local co=CoRoutine
local function Schedule()
local status,res=co()
self:T3({status,res})
if status==false then
error(res)
end
if res==false then
return true
end
if FinalizeFunction then
FinalizeFunction(unpack(arg))
end
return false
end
Schedule()
return self
end
function DATABASE:ForEachStatic(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.STATICS)
return self
end
function DATABASE:ForEachUnit(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.UNITS)
return self
end
function DATABASE:ForEachGroup(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.GROUPS)
return self
end
function DATABASE:ForEachPlayer(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.PLAYERS)
return self
end
function DATABASE:ForEachPlayerJoined(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.PLAYERSJOINED)
return self
end
function DATABASE:ForEachPlayerUnit(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.PLAYERUNITS)
return self
end
function DATABASE:ForEachClient(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self.CLIENTS)
return self
end
function DATABASE:ForEachCargo(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self.CARGOS)
return self
end
function DATABASE:OnEventNewCargo(EventData)
self:F2({EventData})
if EventData.Cargo then
self:AddCargo(EventData.Cargo)
end
end
function DATABASE:OnEventDeleteCargo(EventData)
self:F2({EventData})
if EventData.Cargo then
self:DeleteCargo(EventData.Cargo.Name)
end
end
function DATABASE:OnEventNewZone(EventData)
self:F2({EventData})
if EventData.Zone then
self:AddZone(EventData.Zone.ZoneName,EventData.Zone)
end
end
function DATABASE:OnEventDeleteZone(EventData)
self:F2({EventData})
if EventData.Zone then
self:DeleteZone(EventData.Zone.ZoneName)
end
end
function DATABASE:GetPlayerSettings(PlayerName)
self:F2({PlayerName})
return self.PLAYERSETTINGS[PlayerName]
end
function DATABASE:SetPlayerSettings(PlayerName,Settings)
self:F2({PlayerName,Settings})
self.PLAYERSETTINGS[PlayerName]=Settings
end
function DATABASE:AddOpsGroup(opsgroup)
self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup
end
function DATABASE:GetOpsGroup(groupname)
if type(groupname)=="string"then
else
groupname=groupname:GetName()
end
return self.FLIGHTGROUPS[groupname]
end
function DATABASE:FindOpsGroup(groupname)
if type(groupname)=="string"then
else
groupname=groupname:GetName()
end
return self.FLIGHTGROUPS[groupname]
end
function DATABASE:FindOpsGroupFromUnit(unitname)
local unit=nil
local groupname
if type(unitname)=="string"then
unit=UNIT:FindByName(unitname)
else
unit=unitname
end
if unit then
groupname=unit:GetGroup():GetName()
end
if groupname then
return self.FLIGHTGROUPS[groupname]
else
return nil
end
end
function DATABASE:AddFlightControl(flightcontrol)
self:F2({flightcontrol})
self.FLIGHTCONTROLS[flightcontrol.airbasename]=flightcontrol
end
function DATABASE:GetFlightControl(airbasename)
return self.FLIGHTCONTROLS[airbasename]
end
function DATABASE:_RegisterTemplates()
self:F2()
self.Navpoints={}
self.UNITS={}
for CoalitionName,coa_data in pairs(env.mission.coalition)do
self:T({CoalitionName=CoalitionName})
if(CoalitionName=='red'or CoalitionName=='blue'or CoalitionName=='neutrals')and type(coa_data)=='table'then
local CoalitionSide=coalition.side[string.upper(CoalitionName)]
if CoalitionName=="red"then
CoalitionSide=coalition.side.RED
elseif CoalitionName=="blue"then
CoalitionSide=coalition.side.BLUE
else
CoalitionSide=coalition.side.NEUTRAL
end
self.Navpoints[CoalitionName]={}
if coa_data.nav_points then
for nav_ind,nav_data in pairs(coa_data.nav_points)do
if type(nav_data)=='table'then
self.Navpoints[CoalitionName][nav_ind]=routines.utils.deepCopy(nav_data)
self.Navpoints[CoalitionName][nav_ind]['name']=nav_data.callsignStr
self.Navpoints[CoalitionName][nav_ind]['point']={}
self.Navpoints[CoalitionName][nav_ind]['point']['x']=nav_data.x
self.Navpoints[CoalitionName][nav_ind]['point']['y']=0
self.Navpoints[CoalitionName][nav_ind]['point']['z']=nav_data.y
end
end
end
if coa_data.country then
for cntry_id,cntry_data in pairs(coa_data.country)do
local CountryName=string.upper(cntry_data.name)
local CountryID=cntry_data.id
self.COUNTRY_ID[CountryName]=CountryID
self.COUNTRY_NAME[CountryID]=CountryName
if type(cntry_data)=='table'then
for obj_type_name,obj_type_data in pairs(cntry_data)do
if obj_type_name=="helicopter"or obj_type_name=="ship"or obj_type_name=="plane"or obj_type_name=="vehicle"or obj_type_name=="static"then
local CategoryName=obj_type_name
if((type(obj_type_data)=='table')and obj_type_data.group and(type(obj_type_data.group)=='table')and(#obj_type_data.group>0))then
for group_num,Template in pairs(obj_type_data.group)do
if obj_type_name~="static"and Template and Template.units and type(Template.units)=='table'then
self:_RegisterGroupTemplate(Template,CoalitionSide,_DATABASECategory[string.lower(CategoryName)],CountryID)
else
self:_RegisterStaticTemplate(Template,CoalitionSide,_DATABASECategory[string.lower(CategoryName)],CountryID)
end
end
end
end
end
end
end
end
end
end
return self
end
function DATABASE:AccountHits(Event)
self:F({Event})
if Event.IniPlayerName~=nil then
self:T("Hitting Something")
if Event.TgtCategory then
self.HITS[Event.TgtUnitName]=self.HITS[Event.TgtUnitName]or{}
local Hit=self.HITS[Event.TgtUnitName]
Hit.Players=Hit.Players or{}
Hit.Players[Event.IniPlayerName]=true
end
end
if Event.WeaponPlayerName~=nil then
self:T("Hitting Scenery")
if Event.TgtCategory then
if Event.WeaponCoalition then
self.HITS[Event.TgtUnitName]=self.HITS[Event.TgtUnitName]or{}
local Hit=self.HITS[Event.TgtUnitName]
Hit.Players=Hit.Players or{}
Hit.Players[Event.WeaponPlayerName]=true
else
end
end
end
end
function DATABASE:AccountDestroys(Event)
self:F({Event})
local TargetUnit=nil
local TargetGroup=nil
local TargetUnitName=""
local TargetGroupName=""
local TargetPlayerName=""
local TargetCoalition=nil
local TargetCategory=nil
local TargetType=nil
local TargetUnitCoalition=nil
local TargetUnitCategory=nil
local TargetUnitType=nil
if Event.IniDCSUnit then
TargetUnit=Event.IniUnit
TargetUnitName=Event.IniDCSUnitName
TargetGroup=Event.IniDCSGroup
TargetGroupName=Event.IniDCSGroupName
TargetPlayerName=Event.IniPlayerName
TargetCoalition=Event.IniCoalition
TargetCategory=Event.IniCategory
TargetType=Event.IniTypeName
TargetUnitType=TargetType
self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType})
end
local Destroyed=false
if self.HITS[Event.IniUnitName]then
self.DESTROYS[Event.IniUnitName]=self.DESTROYS[Event.IniUnitName]or{}
self.DESTROYS[Event.IniUnitName]=true
end
end
do
SET_BASE={
ClassName="SET_BASE",
Filter={},
Set={},
List={},
Index={},
Database=nil,
CallScheduler=nil,
TimeInterval=nil,
YieldInterval=nil,
}
function SET_BASE:New(Database)
local self=BASE:Inherit(self,FSM:New())
self.Database=Database
self:SetStartState("Started")
self:AddTransition("*","Added","*")
self:AddTransition("*","Removed","*")
self.YieldInterval=10
self.TimeInterval=0.001
self.Set={}
self.Index={}
self.CallScheduler=SCHEDULER:New(self)
self:SetEventPriority(2)
return self
end
function SET_BASE:Clear(TriggerEvent)
for Name,Object in pairs(self.Set)do
self:Remove(Name,not TriggerEvent)
end
return self
end
function SET_BASE:_Find(ObjectName)
local ObjectFound=self.Set[ObjectName]
return ObjectFound
end
function SET_BASE:GetSet()
self:F2()
return self.Set or{}
end
function SET_BASE:GetSetNames()
self:F2()
local Names={}
for Name,Object in pairs(self.Set)do
table.insert(Names,Name)
end
return Names
end
function SET_BASE:GetSetObjects()
self:F2()
local Objects={}
for Name,Object in pairs(self.Set)do
table.insert(Objects,Object)
end
return Objects
end
function SET_BASE:Remove(ObjectName,NoTriggerEvent)
self:F2({ObjectName=ObjectName})
local TriggerEvent=true
if NoTriggerEvent then
TriggerEvent=false
else
TriggerEvent=true
end
local Object=self.Set[ObjectName]
if Object then
for Index,Key in ipairs(self.Index)do
if Key==ObjectName then
table.remove(self.Index,Index)
self.Set[ObjectName]=nil
break
end
end
if TriggerEvent then
self:Removed(ObjectName,Object)
end
end
end
function SET_BASE:Add(ObjectName,Object)
self:T({ObjectName=ObjectName,Object=Object})
if self.Set[ObjectName]then
self:Remove(ObjectName,true)
end
self.Set[ObjectName]=Object
table.insert(self.Index,ObjectName)
self:Added(ObjectName,Object)
end
function SET_BASE:AddObject(Object)
self:F2(Object.ObjectName)
self:T(Object.UnitName)
self:T(Object.ObjectName)
self:Add(Object.ObjectName,Object)
end
function SET_BASE:SortByName()
local function sort(a,b)
return a<b
end
table.sort(self.Index)
end
function SET_BASE:AddSet(SetToAdd)
if not SetToAdd then return self end
for _,ObjectB in pairs(SetToAdd.Set)do
self:AddObject(ObjectB)
end
return self
end
function SET_BASE:GetSetUnion(SetB)
local union=SET_BASE:New()
for _,ObjectA in pairs(self.Set)do
union:AddObject(ObjectA)
end
for _,ObjectB in pairs(SetB.Set)do
union:AddObject(ObjectB)
end
return union
end
function SET_BASE:GetSetIntersection(SetB)
local intersection=SET_BASE:New()
local union=self:GetSetUnion(SetB)
for _,Object in pairs(union.Set)do
if self:IsIncludeObject(Object)and SetB:IsIncludeObject(Object)then
intersection:AddObject(Object)
end
end
return intersection
end
function SET_BASE:GetSetComplement(SetB)
local complement=self:GetSetUnion(SetB)
local intersection=self:GetSetIntersection(SetB)
for _,Object in pairs(intersection.Set)do
complement:Remove(Object.ObjectName,true)
end
return complement
end
function SET_BASE:CompareSets(SetA,SetB)
for _,ObjectB in pairs(SetB.Set)do
if SetA:IsIncludeObject(ObjectB)then
SetA:Add(ObjectB)
end
end
return SetA
end
function SET_BASE:Get(ObjectName)
self:F(ObjectName)
local Object=self.Set[ObjectName]
self:T3({ObjectName,Object})
return Object
end
function SET_BASE:GetFirst()
local ObjectName=self.Index[1]
local FirstObject=self.Set[ObjectName]
self:T3({FirstObject})
return FirstObject
end
function SET_BASE:GetLast()
local tablemax=table.maxn(self.Index)
local ObjectName=self.Index[tablemax]
local LastObject=self.Set[ObjectName]
self:T3({LastObject})
return LastObject
end
function SET_BASE:GetRandom()
local tablemax=table.maxn(self.Index)
local RandomItem=self.Set[self.Index[math.random(1,tablemax)]]
self:T3({RandomItem})
return RandomItem
end
function SET_BASE:Count()
return self.Index and table.maxn(self.Index)or 0
end
function SET_BASE:SetDatabase(BaseSet)
local OtherFilter=routines.utils.deepCopy(BaseSet.Filter)
self.Filter=OtherFilter
self.Database=BaseSet:GetSet()
return self
end
function SET_BASE:SetIteratorIntervals(YieldInterval,TimeInterval)
self.YieldInterval=YieldInterval
self.TimeInterval=TimeInterval
return self
end
function SET_BASE:SetSomeIteratorLimit(Limit)
self.SomeIteratorLimit=Limit or 1
return self
end
function SET_BASE:GetSomeIteratorLimit()
return self.SomeIteratorLimit or self:Count()
end
function SET_BASE:FilterOnce()
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
else
self:Remove(ObjectName,true)
end
end
return self
end
function SET_BASE:FilterClear()
for key,value in pairs(self.Filter)do
self.Filter[key]={}
end
return self
end
function SET_BASE:_FilterStart()
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
end
end
return self
end
function SET_BASE:FilterDeads()
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
return self
end
function SET_BASE:FilterCrashes()
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
return self
end
function SET_BASE:FilterStop()
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Crash)
return self
end
function SET_BASE:FindNearestObjectFromPointVec2(PointVec2)
self:F2(PointVec2)
local NearestObject=nil
local ClosestDistance=nil
for ObjectID,ObjectData in pairs(self.Set)do
if NearestObject==nil then
NearestObject=ObjectData
ClosestDistance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate())
else
local Distance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate())
if Distance<ClosestDistance then
NearestObject=ObjectData
ClosestDistance=Distance
end
end
end
return NearestObject
end
function SET_BASE:_EventOnBirth(Event)
self:F3({Event})
if Event.IniDCSUnit then
local ObjectName,Object=self:AddInDatabase(Event)
self:T3(ObjectName,Object)
if Object and self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
end
end
end
function SET_BASE:_EventOnDeadOrCrash(Event)
self:F({Event})
if Event.IniDCSUnit then
local ObjectName,Object=self:FindInDatabase(Event)
if ObjectName then
self:Remove(ObjectName)
end
end
end
function SET_BASE:ForEach(IteratorFunction,arg,Set,Function,FunctionArguments)
self:F3(arg)
Set=Set or self:GetSet()
arg=arg or{}
local function CoRoutine()
local Count=0
for ObjectID,ObjectData in pairs(Set)do
local Object=ObjectData
self:T3(Object)
if Function then
if Function(unpack(FunctionArguments or{}),Object)==true then
IteratorFunction(Object,unpack(arg))
end
else
IteratorFunction(Object,unpack(arg))
end
Count=Count+1
end
return true
end
local co=CoRoutine
local function Schedule()
local status,res=co()
self:T3({status,res})
if status==false then
error(res)
end
if res==false then
return true
end
return false
end
Schedule()
return self
end
function SET_BASE:ForSome(IteratorFunction,arg,Set,Function,FunctionArguments)
self:F3(arg)
Set=Set or self:GetSet()
arg=arg or{}
local Limit=self:GetSomeIteratorLimit()
local function CoRoutine()
local Count=0
for ObjectID,ObjectData in pairs(Set)do
local Object=ObjectData
self:T3(Object)
if Function then
if Function(unpack(FunctionArguments),Object)==true then
IteratorFunction(Object,unpack(arg))
end
else
IteratorFunction(Object,unpack(arg))
end
Count=Count+1
if Count>=Limit then
break
end
end
return true
end
local co=CoRoutine
local function Schedule()
local status,res=co()
self:T3({status,res})
if status==false then
error(res)
end
if res==false then
return true
end
return false
end
Schedule()
return self
end
function SET_BASE:IsIncludeObject(Object)
self:F3(Object)
return true
end
function SET_BASE:IsInSet(Object)
self:F3(Object)
local outcome=false
local name=Object:GetName()
self:ForEach(
function(object)
if object:GetName()==name then
outcome=true
end
end
)
return outcome
end
function SET_BASE:IsNotInSet(Object)
self:F3(Object)
return not self:IsInSet(Object)
end
function SET_BASE:GetObjectNames()
self:F3()
local ObjectNames=""
for ObjectName,Object in pairs(self.Set)do
ObjectNames=ObjectNames..ObjectName..", "
end
return ObjectNames
end
function SET_BASE:Flush(MasterObject)
self:F3()
local ObjectNames=""
for ObjectName,Object in pairs(self.Set)do
ObjectNames=ObjectNames..ObjectName..", "
end
self:F({MasterObject=MasterObject and MasterObject:GetClassNameAndID(),"Objects in Set:",ObjectNames})
return ObjectNames
end
end
do
SET_GROUP={
ClassName="SET_GROUP",
Filter={
Coalitions=nil,
Categories=nil,
Countries=nil,
GroupPrefixes=nil,
Zones=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
plane=Group.Category.AIRPLANE,
helicopter=Group.Category.HELICOPTER,
ground=Group.Category.GROUND,
ship=Group.Category.SHIP,
structure=Group.Category.STRUCTURE,
},
},
}
function SET_GROUP:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.GROUPS))
self:FilterActive(false)
return self
end
function SET_GROUP:GetAliveSet()
self:F2()
local AliveSet=SET_GROUP:New()
for GroupName,GroupObject in pairs(self.Set)do
local GroupObject=GroupObject
if GroupObject then
if GroupObject:IsAlive()then
AliveSet:Add(GroupName,GroupObject)
end
end
end
return AliveSet.Set or{}
end
function SET_GROUP:GetUnitTypeNames()
self:F2()
local MT={}
local UnitTypes={}
local ReportUnitTypes=REPORT:New()
for GroupID,GroupData in pairs(self:GetSet())do
local Units=GroupData:GetUnits()
for UnitID,UnitData in pairs(Units)do
if UnitData:IsAlive()then
local UnitType=UnitData:GetTypeName()
if not UnitTypes[UnitType]then
UnitTypes[UnitType]=1
else
UnitTypes[UnitType]=UnitTypes[UnitType]+1
end
end
end
end
for UnitTypeID,UnitType in pairs(UnitTypes)do
ReportUnitTypes:Add(UnitType.." of "..UnitTypeID)
end
return ReportUnitTypes
end
function SET_GROUP:AddGroup(group,DontSetCargoBayLimit)
self:Add(group:GetName(),group)
if not DontSetCargoBayLimit then
for UnitID,UnitData in pairs(group:GetUnits())do
if UnitData and UnitData:IsAlive()then
UnitData:SetCargoBayWeightLimit()
end
end
end
return self
end
function SET_GROUP:AddGroupsByName(AddGroupNames)
local AddGroupNamesArray=(type(AddGroupNames)=="table")and AddGroupNames or{AddGroupNames}
for AddGroupID,AddGroupName in pairs(AddGroupNamesArray)do
self:Add(AddGroupName,GROUP:FindByName(AddGroupName))
end
return self
end
function SET_GROUP:RemoveGroupsByName(RemoveGroupNames)
local RemoveGroupNamesArray=(type(RemoveGroupNames)=="table")and RemoveGroupNames or{RemoveGroupNames}
for RemoveGroupID,RemoveGroupName in pairs(RemoveGroupNamesArray)do
self:Remove(RemoveGroupName)
end
return self
end
function SET_GROUP:FindGroup(GroupName)
local GroupFound=self.Set[GroupName]
return GroupFound
end
function SET_GROUP:FindNearestGroupFromPointVec2(PointVec2)
self:F2(PointVec2)
local NearestGroup=nil
local ClosestDistance=nil
local Set=self:GetAliveSet()
for ObjectID,ObjectData in pairs(Set)do
if NearestGroup==nil then
NearestGroup=ObjectData
ClosestDistance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate())
else
local Distance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate())
if Distance<ClosestDistance then
NearestGroup=ObjectData
ClosestDistance=Distance
end
end
end
return NearestGroup
end
function SET_GROUP:FilterZones(Zones,Clear)
if Clear or not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_GROUP:FilterCoalitions(Coalitions,Clear)
if Clear or(not self.Filter.Coalitions)then
self.Filter.Coalitions={}
end
Coalitions=UTILS.EnsureTable(Coalitions,false)
for CoalitionID,Coalition in pairs(Coalitions)do
self.Filter.Coalitions[Coalition]=Coalition
end
return self
end
function SET_GROUP:FilterCategories(Categories,Clear)
if Clear or not self.Filter.Categories then
self.Filter.Categories={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_GROUP:FilterCategoryGround()
self:FilterCategories("ground")
return self
end
function SET_GROUP:FilterCategoryAirplane()
self:FilterCategories("plane")
return self
end
function SET_GROUP:FilterCategoryHelicopter()
self:FilterCategories("helicopter")
return self
end
function SET_GROUP:FilterCategoryShip()
self:FilterCategories("ship")
return self
end
function SET_GROUP:FilterCategoryStructure()
self:FilterCategories("structure")
return self
end
function SET_GROUP:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_GROUP:FilterPrefixes(Prefixes)
if not self.Filter.GroupPrefixes then
self.Filter.GroupPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.GroupPrefixes[Prefix]=Prefix
end
return self
end
function SET_GROUP:FilterActive(Active)
Active=Active or not(Active==false)
self.Filter.Active=Active
return self
end
function SET_GROUP:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.RemoveUnit,self._EventOnDeadOrCrash)
end
return self
end
function SET_GROUP:_EventOnDeadOrCrash(Event)
self:F({Event})
if Event.IniDCSUnit then
local ObjectName,Object=self:FindInDatabase(Event)
if ObjectName then
local size=1
if Event.IniDCSGroup then
size=Event.IniDCSGroup:getSize()
end
if size==1 then
self:Remove(ObjectName)
end
end
end
end
function SET_GROUP:AddInDatabase(Event)
self:F3({Event})
if Event.IniObjectCategory==1 then
if not self.Database[Event.IniDCSGroupName]then
self.Database[Event.IniDCSGroupName]=GROUP:Register(Event.IniDCSGroupName)
self:T3(self.Database[Event.IniDCSGroupName])
end
end
return Event.IniDCSGroupName,self.Database[Event.IniDCSGroupName]
end
function SET_GROUP:FindInDatabase(Event)
self:F3({Event})
return Event.IniDCSGroupName,self.Database[Event.IniDCSGroupName]
end
function SET_GROUP:ForEachGroup(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_GROUP:ForSomeGroup(IteratorFunction,...)
self:F2(arg)
self:ForSome(IteratorFunction,arg,self:GetSet())
return self
end
function SET_GROUP:ForEachGroupAlive(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetAliveSet())
return self
end
function SET_GROUP:ForSomeGroupAlive(IteratorFunction,...)
self:F2(arg)
self:ForSome(IteratorFunction,arg,self:GetAliveSet())
return self
end
function SET_GROUP:Activate(Delay)
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
local group=GroupData
if group and group:IsAlive()==false then
group:Activate(Delay)
end
end
return self
end
function SET_GROUP:ForEachGroupCompletelyInZone(ZoneObject,IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,GroupObject)
if GroupObject:IsCompletelyInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_GROUP:ForEachGroupPartlyInZone(ZoneObject,IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,GroupObject)
if GroupObject:IsPartlyInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_GROUP:ForEachGroupNotInZone(ZoneObject,IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,GroupObject)
if GroupObject:IsNotInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_GROUP:AllCompletelyInZone(Zone)
self:F2(Zone)
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if not GroupData:IsCompletelyInZone(Zone)then
return false
end
end
return true
end
function SET_GROUP:ForEachGroupAnyInZone(ZoneObject,IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,GroupObject)
if GroupObject:IsAnyInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_GROUP:AnyCompletelyInZone(Zone)
self:F2(Zone)
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if GroupData:IsCompletelyInZone(Zone)then
return true
end
end
return false
end
function SET_GROUP:AnyInZone(Zone)
self:F2(Zone)
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if GroupData:IsPartlyInZone(Zone)or GroupData:IsCompletelyInZone(Zone)then
return true
end
end
return false
end
function SET_GROUP:AnyPartlyInZone(Zone)
self:F2(Zone)
local IsPartlyInZone=false
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if GroupData:IsCompletelyInZone(Zone)then
return false
elseif GroupData:IsPartlyInZone(Zone)then
IsPartlyInZone=true
end
end
if IsPartlyInZone then
return true
else
return false
end
end
function SET_GROUP:NoneInZone(Zone)
self:F2(Zone)
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if not GroupData:IsNotInZone(Zone)then
return false
end
end
return true
end
function SET_GROUP:CountInZone(Zone)
self:F2(Zone)
local Count=0
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if GroupData:IsCompletelyInZone(Zone)then
Count=Count+1
end
end
return Count
end
function SET_GROUP:CountUnitInZone(Zone)
self:F2(Zone)
local Count=0
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
Count=Count+GroupData:CountInZone(Zone)
end
return Count
end
function SET_GROUP:CountAlive()
local CountG=0
local CountU=0
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if GroupData and GroupData:IsAlive()then
CountG=CountG+1
for _,_unit in pairs(GroupData:GetUnits())do
local unit=_unit
if unit and unit:IsAlive()then
CountU=CountU+1
end
end
end
end
return CountG,CountU
end
function SET_GROUP:IsIncludeObject(MGroup)
self:F2(MGroup)
local MGroupInclude=true
if self.Filter.Active~=nil then
local MGroupActive=false
self:F({Active=self.Filter.Active})
if self.Filter.Active==false or(self.Filter.Active==true and MGroup:IsActive()==true)then
MGroupActive=true
end
MGroupInclude=MGroupInclude and MGroupActive
end
if self.Filter.Coalitions then
local MGroupCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
self:T3({"Coalition:",MGroup:GetCoalition(),self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MGroup:GetCoalition()then
MGroupCoalition=true
end
end
MGroupInclude=MGroupInclude and MGroupCoalition
end
if self.Filter.Categories then
local MGroupCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
self:T3({"Category:",MGroup:GetCategory(),self.FilterMeta.Categories[CategoryName],CategoryName})
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MGroup:GetCategory()then
MGroupCategory=true
end
end
MGroupInclude=MGroupInclude and MGroupCategory
end
if self.Filter.Countries then
local MGroupCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
self:T3({"Country:",MGroup:GetCountry(),CountryName})
if country.id[CountryName]==MGroup:GetCountry()then
MGroupCountry=true
end
end
MGroupInclude=MGroupInclude and MGroupCountry
end
if self.Filter.GroupPrefixes then
local MGroupPrefix=false
for GroupPrefixId,GroupPrefix in pairs(self.Filter.GroupPrefixes)do
self:T3({"Prefix:",string.find(MGroup:GetName(),GroupPrefix,1),GroupPrefix})
if string.find(MGroup:GetName(),GroupPrefix:gsub("-","%%-"),1)then
MGroupPrefix=true
end
end
MGroupInclude=MGroupInclude and MGroupPrefix
end
if self.Filter.Zones then
local MGroupZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
self:T3("Zone:",ZoneName)
if MGroup:IsInZone(Zone)then
MGroupZone=true
end
end
MGroupInclude=MGroupInclude and MGroupZone
end
self:T2(MGroupInclude)
return MGroupInclude
end
function SET_GROUP:SetCargoBayWeightLimit()
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
for UnitName,UnitData in pairs(GroupData:GetUnits())do
UnitData:SetCargoBayWeightLimit()
end
end
end
function SET_GROUP:GetClosestGroup(Coordinate,Coalitions)
local Set=self:GetSet()
local dmin=math.huge
local gmin=nil
for GroupID,GroupData in pairs(Set)do
local group=GroupData
if group and group:IsAlive()and(Coalitions==nil or UTILS.IsAnyInTable(Coalitions,group:GetCoalition()))then
local coord=group:GetCoord()
local d=UTILS.VecDist3D(Coordinate,coord)
if d<dmin then
dmin=d
gmin=group
end
end
end
return gmin,dmin
end
end
do
SET_UNIT={
ClassName="SET_UNIT",
Units={},
Filter={
Coalitions=nil,
Categories=nil,
Types=nil,
Countries=nil,
UnitPrefixes=nil,
Zones=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
plane=Unit.Category.AIRPLANE,
helicopter=Unit.Category.HELICOPTER,
ground=Unit.Category.GROUND_UNIT,
ship=Unit.Category.SHIP,
structure=Unit.Category.STRUCTURE,
},
},
}
function SET_UNIT:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.UNITS))
self:FilterActive(false)
return self
end
function SET_UNIT:AddUnit(Unit)
self:F2(Unit:GetName())
self:Add(Unit:GetName(),Unit)
Unit:SetCargoBayWeightLimit()
return self
end
function SET_UNIT:AddUnitsByName(AddUnitNames)
local AddUnitNamesArray=(type(AddUnitNames)=="table")and AddUnitNames or{AddUnitNames}
self:T(AddUnitNamesArray)
for AddUnitID,AddUnitName in pairs(AddUnitNamesArray)do
self:Add(AddUnitName,UNIT:FindByName(AddUnitName))
end
return self
end
function SET_UNIT:RemoveUnitsByName(RemoveUnitNames)
local RemoveUnitNamesArray=(type(RemoveUnitNames)=="table")and RemoveUnitNames or{RemoveUnitNames}
for RemoveUnitID,RemoveUnitName in pairs(RemoveUnitNamesArray)do
self:Remove(RemoveUnitName)
end
return self
end
function SET_UNIT:FindUnit(UnitName)
local UnitFound=self.Set[UnitName]
return UnitFound
end
function SET_UNIT:FilterCoalitions(Coalitions)
self.Filter.Coalitions={}
if type(Coalitions)~="table"then
Coalitions={Coalitions}
end
for CoalitionID,Coalition in pairs(Coalitions)do
self.Filter.Coalitions[Coalition]=Coalition
end
return self
end
function SET_UNIT:FilterCategories(Categories)
if not self.Filter.Categories then
self.Filter.Categories={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_UNIT:FilterTypes(Types)
if not self.Filter.Types then
self.Filter.Types={}
end
if type(Types)~="table"then
Types={Types}
end
for TypeID,Type in pairs(Types)do
self.Filter.Types[Type]=Type
end
return self
end
function SET_UNIT:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_UNIT:FilterPrefixes(Prefixes)
if not self.Filter.UnitPrefixes then
self.Filter.UnitPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.UnitPrefixes[Prefix]=Prefix
end
return self
end
function SET_UNIT:FilterZones(Zones)
if not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_UNIT:FilterActive(Active)
Active=Active or not(Active==false)
self.Filter.Active=Active
return self
end
function SET_UNIT:FilterHasRadar(RadarTypes)
self.Filter.RadarTypes=self.Filter.RadarTypes or{}
if type(RadarTypes)~="table"then
RadarTypes={RadarTypes}
end
for RadarTypeID,RadarType in pairs(RadarTypes)do
self.Filter.RadarTypes[RadarType]=RadarType
end
return self
end
function SET_UNIT:FilterHasSEAD()
self.Filter.SEAD=true
return self
end
function SET_UNIT:CountAlive()
local Set=self:GetSet()
local CountU=0
for UnitID,UnitData in pairs(Set)do
if UnitData and UnitData:IsAlive()then
CountU=CountU+1
end
end
return CountU
end
function SET_UNIT:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.RemoveUnit,self._EventOnDeadOrCrash)
end
return self
end
function SET_UNIT:AddInDatabase(Event)
self:F3({Event})
if Event.IniObjectCategory==1 then
if not self.Database[Event.IniDCSUnitName]then
self.Database[Event.IniDCSUnitName]=UNIT:Register(Event.IniDCSUnitName)
self:T3(self.Database[Event.IniDCSUnitName])
end
end
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_UNIT:FindInDatabase(Event)
self:F2({Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName],Event})
return Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName]
end
do
function SET_UNIT:IsPartiallyInZone(ZoneTest)
local IsPartiallyInZone=false
local function EvaluateZone(ZoneUnit)
local ZoneUnitName=ZoneUnit:GetName()
self:F({ZoneUnitName=ZoneUnitName})
if self:FindUnit(ZoneUnitName)then
IsPartiallyInZone=true
self:F({Found=true})
return false
end
return true
end
ZoneTest:SearchZone(EvaluateZone)
return IsPartiallyInZone
end
function SET_UNIT:IsNotInZone(Zone)
local IsNotInZone=true
local function EvaluateZone(ZoneUnit)
local ZoneUnitName=ZoneUnit:GetName()
if self:FindUnit(ZoneUnitName)then
IsNotInZone=false
return false
end
return true
end
Zone:SearchZone(EvaluateZone)
return IsNotInZone
end
end
function SET_UNIT:ForEachUnit(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_UNIT:GetSetPerThreatLevel(FromThreatLevel,ToThreatLevel)
self:F2(arg)
local ThreatLevelSet={}
if self:Count()~=0 then
for UnitName,UnitObject in pairs(self.Set)do
local Unit=UnitObject
local ThreatLevel=Unit:GetThreatLevel()
ThreatLevelSet[ThreatLevel]=ThreatLevelSet[ThreatLevel]or{}
ThreatLevelSet[ThreatLevel].Set=ThreatLevelSet[ThreatLevel].Set or{}
ThreatLevelSet[ThreatLevel].Set[UnitName]=UnitObject
self:F({ThreatLevel=ThreatLevel,ThreatLevelSet=ThreatLevelSet[ThreatLevel].Set})
end
local OrderedPerThreatLevelSet={}
local ThreatLevelIncrement=FromThreatLevel<=ToThreatLevel and 1 or-1
for ThreatLevel=FromThreatLevel,ToThreatLevel,ThreatLevelIncrement do
self:F({ThreatLevel=ThreatLevel})
local ThreatLevelItem=ThreatLevelSet[ThreatLevel]
if ThreatLevelItem then
for UnitName,UnitObject in pairs(ThreatLevelItem.Set)do
table.insert(OrderedPerThreatLevelSet,UnitObject)
end
end
end
return OrderedPerThreatLevelSet
end
end
function SET_UNIT:ForEachUnitPerThreatLevel(FromThreatLevel,ToThreatLevel,IteratorFunction,...)
self:F2(arg)
local ThreatLevelSet={}
if self:Count()~=0 then
for UnitName,UnitObject in pairs(self.Set)do
local Unit=UnitObject
local ThreatLevel=Unit:GetThreatLevel()
ThreatLevelSet[ThreatLevel]=ThreatLevelSet[ThreatLevel]or{}
ThreatLevelSet[ThreatLevel].Set=ThreatLevelSet[ThreatLevel].Set or{}
ThreatLevelSet[ThreatLevel].Set[UnitName]=UnitObject
self:F({ThreatLevel=ThreatLevel,ThreatLevelSet=ThreatLevelSet[ThreatLevel].Set})
end
local ThreatLevelIncrement=FromThreatLevel<=ToThreatLevel and 1 or-1
for ThreatLevel=FromThreatLevel,ToThreatLevel,ThreatLevelIncrement do
self:F({ThreatLevel=ThreatLevel})
local ThreatLevelItem=ThreatLevelSet[ThreatLevel]
if ThreatLevelItem then
self:ForEach(IteratorFunction,arg,ThreatLevelItem.Set)
end
end
end
return self
end
function SET_UNIT:ForEachUnitCompletelyInZone(ZoneObject,IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,UnitObject)
if UnitObject:IsInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_UNIT:ForEachUnitNotInZone(ZoneObject,IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,UnitObject)
if UnitObject:IsNotInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_UNIT:GetUnitTypes()
self:F2()
local MT={}
local UnitTypes={}
for UnitID,UnitData in pairs(self:GetSet())do
local TextUnit=UnitData
if TextUnit:IsAlive()then
local UnitType=TextUnit:GetTypeName()
if not UnitTypes[UnitType]then
UnitTypes[UnitType]=1
else
UnitTypes[UnitType]=UnitTypes[UnitType]+1
end
end
end
for UnitTypeID,UnitType in pairs(UnitTypes)do
MT[#MT+1]=UnitType.." of "..UnitTypeID
end
return UnitTypes
end
function SET_UNIT:GetUnitTypesText()
self:F2()
local MT={}
local UnitTypes=self:GetUnitTypes()
for UnitTypeID,UnitType in pairs(UnitTypes)do
MT[#MT+1]=UnitType.." of "..UnitTypeID
end
return table.concat(MT,", ")
end
function SET_UNIT:GetUnitThreatLevels()
self:F2()
local UnitThreatLevels={}
for UnitID,UnitData in pairs(self:GetSet())do
local ThreatUnit=UnitData
if ThreatUnit:IsAlive()then
local UnitThreatLevel,UnitThreatLevelText=ThreatUnit:GetThreatLevel()
local ThreatUnitName=ThreatUnit:GetName()
UnitThreatLevels[UnitThreatLevel]=UnitThreatLevels[UnitThreatLevel]or{}
UnitThreatLevels[UnitThreatLevel].UnitThreatLevelText=UnitThreatLevelText
UnitThreatLevels[UnitThreatLevel].Units=UnitThreatLevels[UnitThreatLevel].Units or{}
UnitThreatLevels[UnitThreatLevel].Units[ThreatUnitName]=ThreatUnit
end
end
return UnitThreatLevels
end
function SET_UNIT:CalculateThreatLevelA2G()
local MaxThreatLevelA2G=0
local MaxThreatText=""
for UnitName,UnitData in pairs(self:GetSet())do
local ThreatUnit=UnitData
local ThreatLevelA2G,ThreatText=ThreatUnit:GetThreatLevel()
if ThreatLevelA2G>MaxThreatLevelA2G then
MaxThreatLevelA2G=ThreatLevelA2G
MaxThreatText=ThreatText
end
end
self:F({MaxThreatLevelA2G=MaxThreatLevelA2G,MaxThreatText=MaxThreatText})
return MaxThreatLevelA2G,MaxThreatText
end
function SET_UNIT:GetCoordinate()
local Coordinate=self:GetRandom():GetCoordinate()
local x1=Coordinate.x
local x2=Coordinate.x
local y1=Coordinate.y
local y2=Coordinate.y
local z1=Coordinate.z
local z2=Coordinate.z
local MaxVelocity=0
local AvgHeading=nil
local MovingCount=0
for UnitName,UnitData in pairs(self:GetSet())do
local Unit=UnitData
local Coordinate=Unit:GetCoordinate()
x1=(Coordinate.x<x1)and Coordinate.x or x1
x2=(Coordinate.x>x2)and Coordinate.x or x2
y1=(Coordinate.y<y1)and Coordinate.y or y1
y2=(Coordinate.y>y2)and Coordinate.y or y2
z1=(Coordinate.y<z1)and Coordinate.z or z1
z2=(Coordinate.y>z2)and Coordinate.z or z2
local Velocity=Coordinate:GetVelocity()
if Velocity~=0 then
MaxVelocity=(MaxVelocity<Velocity)and Velocity or MaxVelocity
local Heading=Coordinate:GetHeading()
AvgHeading=AvgHeading and(AvgHeading+Heading)or Heading
MovingCount=MovingCount+1
end
end
AvgHeading=AvgHeading and(AvgHeading/MovingCount)
Coordinate.x=(x2-x1)/2+x1
Coordinate.y=(y2-y1)/2+y1
Coordinate.z=(z2-z1)/2+z1
Coordinate:SetHeading(AvgHeading)
Coordinate:SetVelocity(MaxVelocity)
self:F({Coordinate=Coordinate})
return Coordinate
end
function SET_UNIT:GetVelocity()
local Coordinate=self:GetFirst():GetCoordinate()
local MaxVelocity=0
for UnitName,UnitData in pairs(self:GetSet())do
local Unit=UnitData
local Coordinate=Unit:GetCoordinate()
local Velocity=Coordinate:GetVelocity()
if Velocity~=0 then
MaxVelocity=(MaxVelocity<Velocity)and Velocity or MaxVelocity
end
end
self:F({MaxVelocity=MaxVelocity})
return MaxVelocity
end
function SET_UNIT:GetHeading()
local HeadingSet=nil
local MovingCount=0
for UnitName,UnitData in pairs(self:GetSet())do
local Unit=UnitData
local Coordinate=Unit:GetCoordinate()
local Velocity=Coordinate:GetVelocity()
if Velocity~=0 then
local Heading=Coordinate:GetHeading()
if HeadingSet==nil then
HeadingSet=Heading
else
local HeadingDiff=(HeadingSet-Heading+180+360)%360-180
HeadingDiff=math.abs(HeadingDiff)
if HeadingDiff>5 then
HeadingSet=nil
break
end
end
end
end
return HeadingSet
end
function SET_UNIT:HasRadar(RadarType)
self:F2(RadarType)
local RadarCount=0
for UnitID,UnitData in pairs(self:GetSet())do
local UnitSensorTest=UnitData
local HasSensors
if RadarType then
HasSensors=UnitSensorTest:HasSensors(Unit.SensorType.RADAR,RadarType)
else
HasSensors=UnitSensorTest:HasSensors(Unit.SensorType.RADAR)
end
self:T3(HasSensors)
if HasSensors then
RadarCount=RadarCount+1
end
end
return RadarCount
end
function SET_UNIT:HasSEAD()
self:F2()
local SEADCount=0
for UnitID,UnitData in pairs(self:GetSet())do
local UnitSEAD=UnitData
if UnitSEAD:IsAlive()then
local UnitSEADAttributes=UnitSEAD:GetDesc().attributes
local HasSEAD=UnitSEAD:HasSEAD()
self:T3(HasSEAD)
if HasSEAD then
SEADCount=SEADCount+1
end
end
end
return SEADCount
end
function SET_UNIT:HasGroundUnits()
self:F2()
local GroundUnitCount=0
for UnitID,UnitData in pairs(self:GetSet())do
local UnitTest=UnitData
if UnitTest:IsGround()then
GroundUnitCount=GroundUnitCount+1
end
end
return GroundUnitCount
end
function SET_UNIT:HasAirUnits()
self:F2()
local AirUnitCount=0
for UnitID,UnitData in pairs(self:GetSet())do
local UnitTest=UnitData
if UnitTest:IsAir()then
AirUnitCount=AirUnitCount+1
end
end
return AirUnitCount
end
function SET_UNIT:HasFriendlyUnits(FriendlyCoalition)
self:F2()
local FriendlyUnitCount=0
for UnitID,UnitData in pairs(self:GetSet())do
local UnitTest=UnitData
if UnitTest:IsFriendly(FriendlyCoalition)then
FriendlyUnitCount=FriendlyUnitCount+1
end
end
return FriendlyUnitCount
end
function SET_UNIT:IsIncludeObject(MUnit)
self:F2(MUnit)
local MUnitInclude=false
if MUnit:IsAlive()~=nil then
MUnitInclude=true
if self.Filter.Active~=nil then
local MUnitActive=false
if self.Filter.Active==false or(self.Filter.Active==true and MUnit:IsActive()==true)then
MUnitActive=true
end
MUnitInclude=MUnitInclude and MUnitActive
end
if self.Filter.Coalitions then
local MUnitCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
self:F({"Coalition:",MUnit:GetCoalition(),self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MUnit:GetCoalition()then
MUnitCoalition=true
end
end
MUnitInclude=MUnitInclude and MUnitCoalition
end
if self.Filter.Categories then
local MUnitCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
self:T3({"Category:",MUnit:GetDesc().category,self.FilterMeta.Categories[CategoryName],CategoryName})
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MUnit:GetDesc().category then
MUnitCategory=true
end
end
MUnitInclude=MUnitInclude and MUnitCategory
end
if self.Filter.Types then
local MUnitType=false
for TypeID,TypeName in pairs(self.Filter.Types)do
self:T3({"Type:",MUnit:GetTypeName(),TypeName})
if TypeName==MUnit:GetTypeName()then
MUnitType=true
end
end
MUnitInclude=MUnitInclude and MUnitType
end
if self.Filter.Countries then
local MUnitCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
self:T3({"Country:",MUnit:GetCountry(),CountryName})
if country.id[CountryName]==MUnit:GetCountry()then
MUnitCountry=true
end
end
MUnitInclude=MUnitInclude and MUnitCountry
end
if self.Filter.UnitPrefixes then
local MUnitPrefix=false
for UnitPrefixId,UnitPrefix in pairs(self.Filter.UnitPrefixes)do
self:T3({"Prefix:",string.find(MUnit:GetName(),UnitPrefix,1),UnitPrefix})
if string.find(MUnit:GetName(),UnitPrefix,1)then
MUnitPrefix=true
end
end
MUnitInclude=MUnitInclude and MUnitPrefix
end
if self.Filter.RadarTypes then
local MUnitRadar=false
for RadarTypeID,RadarType in pairs(self.Filter.RadarTypes)do
self:T3({"Radar:",RadarType})
if MUnit:HasSensors(Unit.SensorType.RADAR,RadarType)==true then
if MUnit:GetRadar()==true then
self:T3("RADAR Found")
end
MUnitRadar=true
end
end
MUnitInclude=MUnitInclude and MUnitRadar
end
if self.Filter.SEAD then
local MUnitSEAD=false
if MUnit:HasSEAD()==true then
self:T3("SEAD Found")
MUnitSEAD=true
end
MUnitInclude=MUnitInclude and MUnitSEAD
end
end
if self.Filter.Zones then
local MGroupZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
self:T3("Zone:",ZoneName)
if MUnit:IsInZone(Zone)then
MGroupZone=true
end
end
MUnitInclude=MUnitInclude and MGroupZone
end
self:T2(MUnitInclude)
return MUnitInclude
end
function SET_UNIT:GetTypeNames(Delimiter)
Delimiter=Delimiter or", "
local TypeReport=REPORT:New()
local Types={}
for UnitName,UnitData in pairs(self:GetSet())do
local Unit=UnitData
local UnitTypeName=Unit:GetTypeName()
if not Types[UnitTypeName]then
Types[UnitTypeName]=UnitTypeName
TypeReport:Add(UnitTypeName)
end
end
return TypeReport:Text(Delimiter)
end
function SET_UNIT:SetCargoBayWeightLimit()
local Set=self:GetSet()
for UnitID,UnitData in pairs(Set)do
UnitData:SetCargoBayWeightLimit()
end
end
end
do
SET_STATIC={
ClassName="SET_STATIC",
Statics={},
Filter={
Coalitions=nil,
Categories=nil,
Types=nil,
Countries=nil,
StaticPrefixes=nil,
Zones=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
plane=Unit.Category.AIRPLANE,
helicopter=Unit.Category.HELICOPTER,
ground=Unit.Category.GROUND_STATIC,
ship=Unit.Category.SHIP,
structure=Unit.Category.STRUCTURE,
},
},
}
function SET_STATIC:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.STATICS))
return self
end
function SET_STATIC:AddStatic(AddStatic)
self:F2(AddStatic:GetName())
self:Add(AddStatic:GetName(),AddStatic)
return self
end
function SET_STATIC:AddStaticsByName(AddStaticNames)
local AddStaticNamesArray=(type(AddStaticNames)=="table")and AddStaticNames or{AddStaticNames}
self:T(AddStaticNamesArray)
for AddStaticID,AddStaticName in pairs(AddStaticNamesArray)do
self:Add(AddStaticName,STATIC:FindByName(AddStaticName))
end
return self
end
function SET_STATIC:RemoveStaticsByName(RemoveStaticNames)
local RemoveStaticNamesArray=(type(RemoveStaticNames)=="table")and RemoveStaticNames or{RemoveStaticNames}
for RemoveStaticID,RemoveStaticName in pairs(RemoveStaticNamesArray)do
self:Remove(RemoveStaticName)
end
return self
end
function SET_STATIC:FindStatic(StaticName)
local StaticFound=self.Set[StaticName]
return StaticFound
end
function SET_STATIC:FilterCoalitions(Coalitions)
if not self.Filter.Coalitions then
self.Filter.Coalitions={}
end
if type(Coalitions)~="table"then
Coalitions={Coalitions}
end
for CoalitionID,Coalition in pairs(Coalitions)do
self.Filter.Coalitions[Coalition]=Coalition
end
return self
end
function SET_STATIC:FilterZones(Zones)
if not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_STATIC:FilterCategories(Categories)
if not self.Filter.Categories then
self.Filter.Categories={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_STATIC:FilterTypes(Types)
if not self.Filter.Types then
self.Filter.Types={}
end
if type(Types)~="table"then
Types={Types}
end
for TypeID,Type in pairs(Types)do
self.Filter.Types[Type]=Type
end
return self
end
function SET_STATIC:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_STATIC:FilterPrefixes(Prefixes)
if not self.Filter.StaticPrefixes then
self.Filter.StaticPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.StaticPrefixes[Prefix]=Prefix
end
return self
end
function SET_STATIC:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
end
return self
end
function SET_STATIC:CountAlive()
local Set=self:GetSet()
local CountU=0
for UnitID,UnitData in pairs(Set)do
if UnitData and UnitData:IsAlive()then
CountU=CountU+1
end
end
return CountU
end
function SET_STATIC:AddInDatabase(Event)
self:F3({Event})
if Event.IniObjectCategory==Object.Category.STATIC then
if not self.Database[Event.IniDCSUnitName]then
self.Database[Event.IniDCSUnitName]=STATIC:Register(Event.IniDCSUnitName)
self:T3(self.Database[Event.IniDCSUnitName])
end
end
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_STATIC:FindInDatabase(Event)
self:F2({Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName],Event})
return Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName]
end
do
function SET_STATIC:IsPartiallyInZone(Zone)
local IsPartiallyInZone=false
local function EvaluateZone(ZoneStatic)
local ZoneStaticName=ZoneStatic:GetName()
if self:FindStatic(ZoneStaticName)then
IsPartiallyInZone=true
return false
end
return true
end
return IsPartiallyInZone
end
function SET_STATIC:IsNotInZone(Zone)
local IsNotInZone=true
local function EvaluateZone(ZoneStatic)
local ZoneStaticName=ZoneStatic:GetName()
if self:FindStatic(ZoneStaticName)then
IsNotInZone=false
return false
end
return true
end
Zone:Search(EvaluateZone)
return IsNotInZone
end
function SET_STATIC:ForEachStaticInZone(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
end
function SET_STATIC:ForEachStatic(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_STATIC:ForEachStaticCompletelyInZone(ZoneObject,IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,StaticObject)
if StaticObject:IsInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_STATIC:ForEachStaticNotInZone(ZoneObject,IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,StaticObject)
if StaticObject:IsNotInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_STATIC:GetStaticTypes()
self:F2()
local MT={}
local StaticTypes={}
for StaticID,StaticData in pairs(self:GetSet())do
local TextStatic=StaticData
if TextStatic:IsAlive()then
local StaticType=TextStatic:GetTypeName()
if not StaticTypes[StaticType]then
StaticTypes[StaticType]=1
else
StaticTypes[StaticType]=StaticTypes[StaticType]+1
end
end
end
for StaticTypeID,StaticType in pairs(StaticTypes)do
MT[#MT+1]=StaticType.." of "..StaticTypeID
end
return StaticTypes
end
function SET_STATIC:GetStaticTypesText()
self:F2()
local MT={}
local StaticTypes=self:GetStaticTypes()
for StaticTypeID,StaticType in pairs(StaticTypes)do
MT[#MT+1]=StaticType.." of "..StaticTypeID
end
return table.concat(MT,", ")
end
function SET_STATIC:GetCoordinate()
local Coordinate=self:GetFirst():GetCoordinate()
local x1=Coordinate.x
local x2=Coordinate.x
local y1=Coordinate.y
local y2=Coordinate.y
local z1=Coordinate.z
local z2=Coordinate.z
local MaxVelocity=0
local AvgHeading=nil
local MovingCount=0
for StaticName,StaticData in pairs(self:GetSet())do
local Static=StaticData
local Coordinate=Static:GetCoordinate()
x1=(Coordinate.x<x1)and Coordinate.x or x1
x2=(Coordinate.x>x2)and Coordinate.x or x2
y1=(Coordinate.y<y1)and Coordinate.y or y1
y2=(Coordinate.y>y2)and Coordinate.y or y2
z1=(Coordinate.y<z1)and Coordinate.z or z1
z2=(Coordinate.y>z2)and Coordinate.z or z2
local Velocity=Coordinate:GetVelocity()
if Velocity~=0 then
MaxVelocity=(MaxVelocity<Velocity)and Velocity or MaxVelocity
local Heading=Coordinate:GetHeading()
AvgHeading=AvgHeading and(AvgHeading+Heading)or Heading
MovingCount=MovingCount+1
end
end
AvgHeading=AvgHeading and(AvgHeading/MovingCount)
Coordinate.x=(x2-x1)/2+x1
Coordinate.y=(y2-y1)/2+y1
Coordinate.z=(z2-z1)/2+z1
Coordinate:SetHeading(AvgHeading)
Coordinate:SetVelocity(MaxVelocity)
self:F({Coordinate=Coordinate})
return Coordinate
end
function SET_STATIC:GetVelocity()
return 0
end
function SET_STATIC:GetHeading()
local HeadingSet=nil
local MovingCount=0
for StaticName,StaticData in pairs(self:GetSet())do
local Static=StaticData
local Coordinate=Static:GetCoordinate()
local Velocity=Coordinate:GetVelocity()
if Velocity~=0 then
local Heading=Coordinate:GetHeading()
if HeadingSet==nil then
HeadingSet=Heading
else
local HeadingDiff=(HeadingSet-Heading+180+360)%360-180
HeadingDiff=math.abs(HeadingDiff)
if HeadingDiff>5 then
HeadingSet=nil
break
end
end
end
end
return HeadingSet
end
function SET_STATIC:CalculateThreatLevelA2G()
local MaxThreatLevelA2G=0
local MaxThreatText=""
for StaticName,StaticData in pairs(self:GetSet())do
local ThreatStatic=StaticData
local ThreatLevelA2G,ThreatText=ThreatStatic:GetThreatLevel()
if ThreatLevelA2G>MaxThreatLevelA2G then
MaxThreatLevelA2G=ThreatLevelA2G
MaxThreatText=ThreatText
end
end
self:F({MaxThreatLevelA2G=MaxThreatLevelA2G,MaxThreatText=MaxThreatText})
return MaxThreatLevelA2G,MaxThreatText
end
function SET_STATIC:IsIncludeObject(MStatic)
self:F2(MStatic)
local MStaticInclude=true
if self.Filter.Coalitions then
local MStaticCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
self:T3({"Coalition:",MStatic:GetCoalition(),self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MStatic:GetCoalition()then
MStaticCoalition=true
end
end
MStaticInclude=MStaticInclude and MStaticCoalition
end
if self.Filter.Categories then
local MStaticCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
self:T3({"Category:",MStatic:GetDesc().category,self.FilterMeta.Categories[CategoryName],CategoryName})
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MStatic:GetDesc().category then
MStaticCategory=true
end
end
MStaticInclude=MStaticInclude and MStaticCategory
end
if self.Filter.Types then
local MStaticType=false
for TypeID,TypeName in pairs(self.Filter.Types)do
self:T3({"Type:",MStatic:GetTypeName(),TypeName})
if TypeName==MStatic:GetTypeName()then
MStaticType=true
end
end
MStaticInclude=MStaticInclude and MStaticType
end
if self.Filter.Countries then
local MStaticCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
self:T3({"Country:",MStatic:GetCountry(),CountryName})
if country.id[CountryName]==MStatic:GetCountry()then
MStaticCountry=true
end
end
MStaticInclude=MStaticInclude and MStaticCountry
end
if self.Filter.StaticPrefixes then
local MStaticPrefix=false
for StaticPrefixId,StaticPrefix in pairs(self.Filter.StaticPrefixes)do
self:T3({"Prefix:",string.find(MStatic:GetName(),StaticPrefix,1),StaticPrefix})
if string.find(MStatic:GetName(),StaticPrefix,1)then
MStaticPrefix=true
end
end
MStaticInclude=MStaticInclude and MStaticPrefix
end
if self.Filter.Zones then
local MStaticZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
self:T3("Zone:",ZoneName)
if MStatic and MStatic:IsInZone(Zone)then
MStaticZone=true
end
end
MStaticInclude=MStaticInclude and MStaticZone
end
self:T2(MStaticInclude)
return MStaticInclude
end
function SET_STATIC:GetTypeNames(Delimiter)
Delimiter=Delimiter or", "
local TypeReport=REPORT:New()
local Types={}
for StaticName,StaticData in pairs(self:GetSet())do
local Static=StaticData
local StaticTypeName=Static:GetTypeName()
if not Types[StaticTypeName]then
Types[StaticTypeName]=StaticTypeName
TypeReport:Add(StaticTypeName)
end
end
return TypeReport:Text(Delimiter)
end
end
do
SET_CLIENT={
ClassName="SET_CLIENT",
Clients={},
Filter={
Coalitions=nil,
Categories=nil,
Types=nil,
Countries=nil,
ClientPrefixes=nil,
Zones=nil,
Playernames=nil,
Callsigns=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
plane=Unit.Category.AIRPLANE,
helicopter=Unit.Category.HELICOPTER,
ground=Unit.Category.GROUND_UNIT,
ship=Unit.Category.SHIP,
structure=Unit.Category.STRUCTURE,
},
},
}
function SET_CLIENT:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.CLIENTS))
self:FilterActive(false)
return self
end
function SET_CLIENT:AddClientsByName(AddClientNames)
local AddClientNamesArray=(type(AddClientNames)=="table")and AddClientNames or{AddClientNames}
for AddClientID,AddClientName in pairs(AddClientNamesArray)do
self:Add(AddClientName,CLIENT:FindByName(AddClientName))
end
return self
end
function SET_CLIENT:RemoveClientsByName(RemoveClientNames)
local RemoveClientNamesArray=(type(RemoveClientNames)=="table")and RemoveClientNames or{RemoveClientNames}
for RemoveClientID,RemoveClientName in pairs(RemoveClientNamesArray)do
self:Remove(RemoveClientName.ClientName)
end
return self
end
function SET_CLIENT:FindClient(ClientName)
local ClientFound=self.Set[ClientName]
return ClientFound
end
function SET_CLIENT:FilterCallsigns(Callsigns)
if not self.Filter.Callsigns then
self.Filter.Callsigns={}
end
if type(Callsigns)~="table"then
Callsigns={Callsigns}
end
for callsignID,callsign in pairs(Callsigns)do
self.Filter.Callsigns[callsign]=callsign
end
return self
end
function SET_CLIENT:FilterPlayernames(Playernames)
if not self.Filter.Playernames then
self.Filter.Playernames={}
end
if type(Playernames)~="table"then
Playernames={Playernames}
end
for PlayernameID,playername in pairs(Playernames)do
self.Filter.Playernames[playername]=playername
end
return self
end
function SET_CLIENT:FilterCoalitions(Coalitions)
if not self.Filter.Coalitions then
self.Filter.Coalitions={}
end
if type(Coalitions)~="table"then
Coalitions={Coalitions}
end
for CoalitionID,Coalition in pairs(Coalitions)do
self.Filter.Coalitions[Coalition]=Coalition
end
return self
end
function SET_CLIENT:FilterCategories(Categories)
if not self.Filter.Categories then
self.Filter.Categories={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_CLIENT:FilterTypes(Types)
if not self.Filter.Types then
self.Filter.Types={}
end
if type(Types)~="table"then
Types={Types}
end
for TypeID,Type in pairs(Types)do
self.Filter.Types[Type]=Type
end
return self
end
function SET_CLIENT:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_CLIENT:FilterPrefixes(Prefixes)
if not self.Filter.ClientPrefixes then
self.Filter.ClientPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.ClientPrefixes[Prefix]=Prefix
end
return self
end
function SET_CLIENT:FilterActive(Active)
Active=Active or not(Active==false)
self.Filter.Active=Active
return self
end
function SET_CLIENT:FilterZones(Zones)
if not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_CLIENT:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
end
return self
end
function SET_CLIENT:AddInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_CLIENT:FindInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_CLIENT:ForEachClient(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_CLIENT:ForEachClientInZone(ZoneObject,IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,ClientObject)
if ClientObject:IsInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_CLIENT:ForEachClientNotInZone(ZoneObject,IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,ClientObject)
if ClientObject:IsNotInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_CLIENT:CountAlive()
local Set=self:GetSet()
local CountU=0
for UnitID,UnitData in pairs(Set)do
if UnitData and UnitData:IsAlive()then
CountU=CountU+1
end
end
return CountU
end
function SET_CLIENT:GetAliveSet()
local AliveSet=SET_CLIENT:New()
for GroupName,GroupObject in pairs(self.Set)do
local GroupObject=GroupObject
if GroupObject and GroupObject:IsAlive()then
AliveSet:Add(GroupName,GroupObject)
end
end
return AliveSet.Set or{}
end
function SET_CLIENT:IsIncludeObject(MClient)
self:F2(MClient)
local MClientInclude=true
if MClient then
local MClientName=MClient.UnitName
if self.Filter.Active~=nil then
local MClientActive=false
if self.Filter.Active==false or(self.Filter.Active==true and MClient:IsActive()==true and MClient:IsAlive()==true)then
MClientActive=true
end
MClientInclude=MClientInclude and MClientActive
end
if self.Filter.Coalitions then
local MClientCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
local ClientCoalitionID=_DATABASE:GetCoalitionFromClientTemplate(MClientName)
self:T3({"Coalition:",ClientCoalitionID,self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==ClientCoalitionID then
MClientCoalition=true
end
end
self:T({"Evaluated Coalition",MClientCoalition})
MClientInclude=MClientInclude and MClientCoalition
end
if self.Filter.Categories then
local MClientCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
local ClientCategoryID=_DATABASE:GetCategoryFromClientTemplate(MClientName)
self:T3({"Category:",ClientCategoryID,self.FilterMeta.Categories[CategoryName],CategoryName})
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==ClientCategoryID then
MClientCategory=true
end
end
self:T({"Evaluated Category",MClientCategory})
MClientInclude=MClientInclude and MClientCategory
end
if self.Filter.Types then
local MClientType=false
for TypeID,TypeName in pairs(self.Filter.Types)do
self:T3({"Type:",MClient:GetTypeName(),TypeName})
if TypeName==MClient:GetTypeName()then
MClientType=true
end
end
self:T({"Evaluated Type",MClientType})
MClientInclude=MClientInclude and MClientType
end
if self.Filter.Countries then
local MClientCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
local ClientCountryID=_DATABASE:GetCountryFromClientTemplate(MClientName)
self:T3({"Country:",ClientCountryID,country.id[CountryName],CountryName})
if country.id[CountryName]and country.id[CountryName]==ClientCountryID then
MClientCountry=true
end
end
self:T({"Evaluated Country",MClientCountry})
MClientInclude=MClientInclude and MClientCountry
end
if self.Filter.ClientPrefixes then
local MClientPrefix=false
for ClientPrefixId,ClientPrefix in pairs(self.Filter.ClientPrefixes)do
self:T3({"Prefix:",string.find(MClient.UnitName,ClientPrefix,1),ClientPrefix})
if string.find(MClient.UnitName,ClientPrefix,1)then
MClientPrefix=true
end
end
self:T({"Evaluated Prefix",MClientPrefix})
MClientInclude=MClientInclude and MClientPrefix
end
if self.Filter.Zones then
local MClientZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
self:T3("Zone:",ZoneName)
local unit=MClient:GetClientGroupUnit()
if unit and unit:IsInZone(Zone)then
MClientZone=true
end
end
MClientInclude=MClientInclude and MClientZone
end
if self.Filter.Playernames then
local MClientPlayername=false
local playername=MClient:GetPlayerName()or"Unknown"
for _,_Playername in pairs(self.Filter.Playernames)do
if playername and string.find(playername,_Playername)then
MClientPlayername=true
end
end
self:T({"Evaluated Playername",MClientPlayername})
MClientInclude=MClientInclude and MClientPlayername
end
if self.Filter.Callsigns then
local MClientCallsigns=false
local callsign=MClient:GetCallsign()
for _,_Callsign in pairs(self.Filter.Callsigns)do
if callsign and string.find(callsign,_Callsign)then
MClientCallsigns=true
end
end
self:T({"Evaluated Callsign",MClientCallsigns})
MClientInclude=MClientInclude and MClientCallsigns
end
end
self:T2(MClientInclude)
return MClientInclude
end
end
do
SET_PLAYER={
ClassName="SET_PLAYER",
Clients={},
Filter={
Coalitions=nil,
Categories=nil,
Types=nil,
Countries=nil,
ClientPrefixes=nil,
Zones=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
plane=Unit.Category.AIRPLANE,
helicopter=Unit.Category.HELICOPTER,
ground=Unit.Category.GROUND_UNIT,
ship=Unit.Category.SHIP,
structure=Unit.Category.STRUCTURE,
},
},
}
function SET_PLAYER:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.PLAYERS))
return self
end
function SET_PLAYER:AddClientsByName(AddClientNames)
local AddClientNamesArray=(type(AddClientNames)=="table")and AddClientNames or{AddClientNames}
for AddClientID,AddClientName in pairs(AddClientNamesArray)do
self:Add(AddClientName,CLIENT:FindByName(AddClientName))
end
return self
end
function SET_PLAYER:RemoveClientsByName(RemoveClientNames)
local RemoveClientNamesArray=(type(RemoveClientNames)=="table")and RemoveClientNames or{RemoveClientNames}
for RemoveClientID,RemoveClientName in pairs(RemoveClientNamesArray)do
self:Remove(RemoveClientName.ClientName)
end
return self
end
function SET_PLAYER:FindClient(PlayerName)
local ClientFound=self.Set[PlayerName]
return ClientFound
end
function SET_PLAYER:FilterCoalitions(Coalitions)
if not self.Filter.Coalitions then
self.Filter.Coalitions={}
end
if type(Coalitions)~="table"then
Coalitions={Coalitions}
end
for CoalitionID,Coalition in pairs(Coalitions)do
self.Filter.Coalitions[Coalition]=Coalition
end
return self
end
function SET_PLAYER:FilterZones(Zones)
if not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_PLAYER:FilterCategories(Categories)
if not self.Filter.Categories then
self.Filter.Categories={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_PLAYER:FilterTypes(Types)
if not self.Filter.Types then
self.Filter.Types={}
end
if type(Types)~="table"then
Types={Types}
end
for TypeID,Type in pairs(Types)do
self.Filter.Types[Type]=Type
end
return self
end
function SET_PLAYER:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_PLAYER:FilterPrefixes(Prefixes)
if not self.Filter.ClientPrefixes then
self.Filter.ClientPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.ClientPrefixes[Prefix]=Prefix
end
return self
end
function SET_PLAYER:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
end
return self
end
function SET_PLAYER:AddInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_PLAYER:FindInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_PLAYER:ForEachPlayer(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_PLAYER:ForEachPlayerInZone(ZoneObject,IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,ClientObject)
if ClientObject:IsInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_PLAYER:ForEachPlayerNotInZone(ZoneObject,IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,ClientObject)
if ClientObject:IsNotInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_PLAYER:IsIncludeObject(MClient)
self:F2(MClient)
local MClientInclude=true
if MClient then
local MClientName=MClient.UnitName
if self.Filter.Coalitions then
local MClientCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
local ClientCoalitionID=_DATABASE:GetCoalitionFromClientTemplate(MClientName)
self:T3({"Coalition:",ClientCoalitionID,self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==ClientCoalitionID then
MClientCoalition=true
end
end
self:T({"Evaluated Coalition",MClientCoalition})
MClientInclude=MClientInclude and MClientCoalition
end
if self.Filter.Categories then
local MClientCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
local ClientCategoryID=_DATABASE:GetCategoryFromClientTemplate(MClientName)
self:T3({"Category:",ClientCategoryID,self.FilterMeta.Categories[CategoryName],CategoryName})
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==ClientCategoryID then
MClientCategory=true
end
end
self:T({"Evaluated Category",MClientCategory})
MClientInclude=MClientInclude and MClientCategory
end
if self.Filter.Types then
local MClientType=false
for TypeID,TypeName in pairs(self.Filter.Types)do
self:T3({"Type:",MClient:GetTypeName(),TypeName})
if TypeName==MClient:GetTypeName()then
MClientType=true
end
end
self:T({"Evaluated Type",MClientType})
MClientInclude=MClientInclude and MClientType
end
if self.Filter.Countries then
local MClientCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
local ClientCountryID=_DATABASE:GetCountryFromClientTemplate(MClientName)
self:T3({"Country:",ClientCountryID,country.id[CountryName],CountryName})
if country.id[CountryName]and country.id[CountryName]==ClientCountryID then
MClientCountry=true
end
end
self:T({"Evaluated Country",MClientCountry})
MClientInclude=MClientInclude and MClientCountry
end
if self.Filter.ClientPrefixes then
local MClientPrefix=false
for ClientPrefixId,ClientPrefix in pairs(self.Filter.ClientPrefixes)do
self:T3({"Prefix:",string.find(MClient.UnitName,ClientPrefix,1),ClientPrefix})
if string.find(MClient.UnitName,ClientPrefix,1)then
MClientPrefix=true
end
end
self:T({"Evaluated Prefix",MClientPrefix})
MClientInclude=MClientInclude and MClientPrefix
end
end
if self.Filter.Zones then
local MClientZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
self:T3("Zone:",ZoneName)
local unit=MClient:GetClientGroupUnit()
if unit and unit:IsInZone(Zone)then
MClientZone=true
end
end
MClientInclude=MClientInclude and MClientZone
end
self:T2(MClientInclude)
return MClientInclude
end
end
do
SET_AIRBASE={
ClassName="SET_AIRBASE",
Airbases={},
Filter={
Coalitions=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
airdrome=Airbase.Category.AIRDROME,
helipad=Airbase.Category.HELIPAD,
ship=Airbase.Category.SHIP,
},
},
}
function SET_AIRBASE:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.AIRBASES))
return self
end
function SET_AIRBASE:AddAirbase(airbase)
self:Add(airbase:GetName(),airbase)
return self
end
function SET_AIRBASE:AddAirbasesByName(AddAirbaseNames)
local AddAirbaseNamesArray=(type(AddAirbaseNames)=="table")and AddAirbaseNames or{AddAirbaseNames}
for AddAirbaseID,AddAirbaseName in pairs(AddAirbaseNamesArray)do
self:Add(AddAirbaseName,AIRBASE:FindByName(AddAirbaseName))
end
return self
end
function SET_AIRBASE:RemoveAirbasesByName(RemoveAirbaseNames)
local RemoveAirbaseNamesArray=(type(RemoveAirbaseNames)=="table")and RemoveAirbaseNames or{RemoveAirbaseNames}
for RemoveAirbaseID,RemoveAirbaseName in pairs(RemoveAirbaseNamesArray)do
self:Remove(RemoveAirbaseName)
end
return self
end
function SET_AIRBASE:FindAirbase(AirbaseName)
local AirbaseFound=self.Set[AirbaseName]
return AirbaseFound
end
function SET_AIRBASE:FindAirbaseInRange(Coordinate,Range)
local AirbaseFound=nil
for AirbaseName,AirbaseObject in pairs(self.Set)do
local AirbaseCoordinate=AirbaseObject:GetCoordinate()
local Distance=Coordinate:Get2DDistance(AirbaseCoordinate)
self:F({Distance=Distance})
if Distance<=Range then
AirbaseFound=AirbaseObject
break
end
end
return AirbaseFound
end
function SET_AIRBASE:GetRandomAirbase()
local RandomAirbase=self:GetRandom()
self:F({RandomAirbase=RandomAirbase:GetName()})
return RandomAirbase
end
function SET_AIRBASE:FilterCoalitions(Coalitions)
if not self.Filter.Coalitions then
self.Filter.Coalitions={}
end
if type(Coalitions)~="table"then
Coalitions={Coalitions}
end
for CoalitionID,Coalition in pairs(Coalitions)do
self.Filter.Coalitions[Coalition]=Coalition
end
return self
end
function SET_AIRBASE:FilterCategories(Categories)
if not self.Filter.Categories then
self.Filter.Categories={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_AIRBASE:FilterStart()
if _DATABASE then
self:HandleEvent(EVENTS.BaseCaptured)
self:HandleEvent(EVENTS.Dead)
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
else
self:RemoveAirbasesByName(ObjectName)
end
end
end
return self
end
function SET_AIRBASE:OnEventBaseCaptured(EventData)
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
else
self:RemoveAirbasesByName(ObjectName)
end
end
end
function SET_AIRBASE:OnEventDead(EventData)
local airbaseName,airbase=self:FindInDatabase(EventData)
if airbase and(airbase:IsShip()or airbase:IsHelipad())then
self:RemoveAirbasesByName(airbaseName)
end
end
function SET_AIRBASE:AddInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_AIRBASE:FindInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_AIRBASE:ForEachAirbase(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_AIRBASE:FindNearestAirbaseFromPointVec2(PointVec2)
self:F2(PointVec2)
local NearestAirbase=self:FindNearestObjectFromPointVec2(PointVec2)
return NearestAirbase
end
function SET_AIRBASE:IsIncludeObject(MAirbase)
self:F2(MAirbase)
local MAirbaseInclude=true
if MAirbase then
local MAirbaseName=MAirbase:GetName()
if self.Filter.Coalitions then
local MAirbaseCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
local AirbaseCoalitionID=_DATABASE:GetCoalitionFromAirbase(MAirbaseName)
self:T3({"Coalition:",AirbaseCoalitionID,self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==AirbaseCoalitionID then
MAirbaseCoalition=true
end
end
self:T({"Evaluated Coalition",MAirbaseCoalition})
MAirbaseInclude=MAirbaseInclude and MAirbaseCoalition
end
if self.Filter.Categories then
local MAirbaseCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
local AirbaseCategoryID=_DATABASE:GetCategoryFromAirbase(MAirbaseName)
self:T3({"Category:",AirbaseCategoryID,self.FilterMeta.Categories[CategoryName],CategoryName})
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==AirbaseCategoryID then
MAirbaseCategory=true
end
end
self:T({"Evaluated Category",MAirbaseCategory})
MAirbaseInclude=MAirbaseInclude and MAirbaseCategory
end
end
self:T2(MAirbaseInclude)
return MAirbaseInclude
end
end
do
SET_CARGO={
ClassName="SET_CARGO",
Cargos={},
Filter={
Coalitions=nil,
Types=nil,
Countries=nil,
ClientPrefixes=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
},
}
function SET_CARGO:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.CARGOS))
return self
end
function SET_CARGO:AddCargo(Cargo)
self:Add(Cargo:GetName(),Cargo)
return self
end
function SET_CARGO:AddCargosByName(AddCargoNames)
local AddCargoNamesArray=(type(AddCargoNames)=="table")and AddCargoNames or{AddCargoNames}
for AddCargoID,AddCargoName in pairs(AddCargoNamesArray)do
self:Add(AddCargoName,CARGO:FindByName(AddCargoName))
end
return self
end
function SET_CARGO:RemoveCargosByName(RemoveCargoNames)
local RemoveCargoNamesArray=(type(RemoveCargoNames)=="table")and RemoveCargoNames or{RemoveCargoNames}
for RemoveCargoID,RemoveCargoName in pairs(RemoveCargoNamesArray)do
self:Remove(RemoveCargoName.CargoName)
end
return self
end
function SET_CARGO:FindCargo(CargoName)
local CargoFound=self.Set[CargoName]
return CargoFound
end
function SET_CARGO:FilterCoalitions(Coalitions)
if not self.Filter.Coalitions then
self.Filter.Coalitions={}
end
if type(Coalitions)~="table"then
Coalitions={Coalitions}
end
for CoalitionID,Coalition in pairs(Coalitions)do
self.Filter.Coalitions[Coalition]=Coalition
end
return self
end
function SET_CARGO:FilterTypes(Types)
if not self.Filter.Types then
self.Filter.Types={}
end
if type(Types)~="table"then
Types={Types}
end
for TypeID,Type in pairs(Types)do
self.Filter.Types[Type]=Type
end
return self
end
function SET_CARGO:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_CARGO:FilterPrefixes(Prefixes)
if not self.Filter.CargoPrefixes then
self.Filter.CargoPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.CargoPrefixes[Prefix]=Prefix
end
return self
end
function SET_CARGO:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.NewCargo)
self:HandleEvent(EVENTS.DeleteCargo)
end
return self
end
function SET_CARGO:FilterStop()
self:UnHandleEvent(EVENTS.NewCargo)
self:UnHandleEvent(EVENTS.DeleteCargo)
return self
end
function SET_CARGO:AddInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_CARGO:FindInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_CARGO:ForEachCargo(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_CARGO:FindNearestCargoFromPointVec2(PointVec2)
self:F2(PointVec2)
local NearestCargo=self:FindNearestObjectFromPointVec2(PointVec2)
return NearestCargo
end
function SET_CARGO:FirstCargoWithState(State)
local FirstCargo=nil
for CargoName,Cargo in pairs(self.Set)do
if Cargo:Is(State)then
FirstCargo=Cargo
break
end
end
return FirstCargo
end
function SET_CARGO:FirstCargoWithStateAndNotDeployed(State)
local FirstCargo=nil
for CargoName,Cargo in pairs(self.Set)do
if Cargo:Is(State)and not Cargo:IsDeployed()then
FirstCargo=Cargo
break
end
end
return FirstCargo
end
function SET_CARGO:FirstCargoUnLoaded()
local FirstCargo=self:FirstCargoWithState("UnLoaded")
return FirstCargo
end
function SET_CARGO:FirstCargoUnLoadedAndNotDeployed()
local FirstCargo=self:FirstCargoWithStateAndNotDeployed("UnLoaded")
return FirstCargo
end
function SET_CARGO:FirstCargoLoaded()
local FirstCargo=self:FirstCargoWithState("Loaded")
return FirstCargo
end
function SET_CARGO:FirstCargoDeployed()
local FirstCargo=self:FirstCargoWithState("Deployed")
return FirstCargo
end
function SET_CARGO:IsIncludeObject(MCargo)
self:F2(MCargo)
local MCargoInclude=true
if MCargo then
local MCargoName=MCargo:GetName()
if self.Filter.Coalitions then
local MCargoCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
local CargoCoalitionID=MCargo:GetCoalition()
self:T3({"Coalition:",CargoCoalitionID,self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==CargoCoalitionID then
MCargoCoalition=true
end
end
self:F({"Evaluated Coalition",MCargoCoalition})
MCargoInclude=MCargoInclude and MCargoCoalition
end
if self.Filter.Types then
local MCargoType=false
for TypeID,TypeName in pairs(self.Filter.Types)do
self:T3({"Type:",MCargo:GetType(),TypeName})
if TypeName==MCargo:GetType()then
MCargoType=true
end
end
self:F({"Evaluated Type",MCargoType})
MCargoInclude=MCargoInclude and MCargoType
end
if self.Filter.CargoPrefixes then
local MCargoPrefix=false
for CargoPrefixId,CargoPrefix in pairs(self.Filter.CargoPrefixes)do
self:T3({"Prefix:",string.find(MCargo.Name,CargoPrefix,1),CargoPrefix})
if string.find(MCargo.Name,CargoPrefix,1)then
MCargoPrefix=true
end
end
self:F({"Evaluated Prefix",MCargoPrefix})
MCargoInclude=MCargoInclude and MCargoPrefix
end
end
self:T2(MCargoInclude)
return MCargoInclude
end
function SET_CARGO:OnEventNewCargo(EventData)
self:F({"New Cargo",EventData})
if EventData.Cargo then
if EventData.Cargo and self:IsIncludeObject(EventData.Cargo)then
self:Add(EventData.Cargo.Name,EventData.Cargo)
end
end
end
function SET_CARGO:OnEventDeleteCargo(EventData)
self:F3({EventData})
if EventData.Cargo then
local Cargo=_DATABASE:FindCargo(EventData.Cargo.Name)
if Cargo and Cargo.Name then
self:F({CargoNoDestroy=Cargo.NoDestroy})
if Cargo.NoDestroy then
else
self:Remove(Cargo.Name)
end
end
end
end
end
do
SET_ZONE={
ClassName="SET_ZONE",
Zones={},
Filter={
Prefixes=nil,
},
FilterMeta={
},
}
function SET_ZONE:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.ZONES))
return self
end
function SET_ZONE:AddZonesByName(AddZoneNames)
local AddZoneNamesArray=(type(AddZoneNames)=="table")and AddZoneNames or{AddZoneNames}
for AddAirbaseID,AddZoneName in pairs(AddZoneNamesArray)do
self:Add(AddZoneName,ZONE:FindByName(AddZoneName))
end
return self
end
function SET_ZONE:AddZone(Zone)
self:Add(Zone:GetName(),Zone)
return self
end
function SET_ZONE:RemoveZonesByName(RemoveZoneNames)
local RemoveZoneNamesArray=(type(RemoveZoneNames)=="table")and RemoveZoneNames or{RemoveZoneNames}
for RemoveZoneID,RemoveZoneName in pairs(RemoveZoneNamesArray)do
self:Remove(RemoveZoneName)
end
return self
end
function SET_ZONE:FindZone(ZoneName)
local ZoneFound=self.Set[ZoneName]
return ZoneFound
end
function SET_ZONE:GetRandomZone(margin)
local margin=margin or 100
if self:Count()~=0 then
local Index=self.Index
local ZoneFound=nil
local counter=0
while(not ZoneFound)or(counter<margin)do
local ZoneRandom=math.random(1,#Index)
ZoneFound=self.Set[Index[ZoneRandom]]:GetZoneMaybe()
counter=counter+1
end
return ZoneFound
end
return nil
end
function SET_ZONE:SetZoneProbability(ZoneName,ZoneProbability)
local Zone=self:FindZone(ZoneName)
Zone:SetZoneProbability(ZoneProbability)
end
function SET_ZONE:FilterPrefixes(Prefixes)
if not self.Filter.Prefixes then
self.Filter.Prefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.Prefixes[Prefix]=Prefix
end
return self
end
function SET_ZONE:FilterStart()
if _DATABASE then
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
else
self:RemoveZonesByName(ObjectName)
end
end
end
self:HandleEvent(EVENTS.NewZone)
self:HandleEvent(EVENTS.DeleteZone)
return self
end
function SET_ZONE:FilterStop()
self:UnHandleEvent(EVENTS.NewZone)
self:UnHandleEvent(EVENTS.DeleteZone)
return self
end
function SET_ZONE:AddInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_ZONE:FindInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_ZONE:ForEachZone(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_ZONE:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
for _,_zone in pairs(self.Set)do
local zone=_zone
zone:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
end
return self
end
function SET_ZONE:IsIncludeObject(MZone)
self:F2(MZone)
local MZoneInclude=true
if MZone then
local MZoneName=MZone:GetName()
if self.Filter.Prefixes then
local MZonePrefix=false
for ZonePrefixId,ZonePrefix in pairs(self.Filter.Prefixes)do
self:T2({"Prefix:",string.find(MZoneName,ZonePrefix,1),ZonePrefix})
if string.find(MZoneName,ZonePrefix,1)then
MZonePrefix=true
end
end
self:T({"Evaluated Prefix",MZonePrefix})
MZoneInclude=MZoneInclude and MZonePrefix
end
end
self:T2(MZoneInclude)
return MZoneInclude
end
function SET_ZONE:OnEventNewZone(EventData)
self:F({"New Zone",EventData})
if EventData.Zone then
if EventData.Zone and self:IsIncludeObject(EventData.Zone)then
self:Add(EventData.Zone.ZoneName,EventData.Zone)
end
end
end
function SET_ZONE:OnEventDeleteZone(EventData)
self:F3({EventData})
if EventData.Zone then
local Zone=_DATABASE:FindZone(EventData.Zone.ZoneName)
if Zone and Zone.ZoneName then
self:F({ZoneNoDestroy=Zone.NoDestroy})
if Zone.NoDestroy then
else
self:Remove(Zone.ZoneName)
end
end
end
end
function SET_ZONE:IsCoordinateInZone(Coordinate)
for _,Zone in pairs(self:GetSet())do
local Zone=Zone
if Zone:IsCoordinateInZone(Coordinate)then
return Zone
end
end
return nil
end
function SET_ZONE:GetClosestZone(Coordinate)
local dmin=math.huge
local zmin=nil
for _,Zone in pairs(self:GetSet())do
local Zone=Zone
local d=Zone:Get2DDistance(Coordinate)
if d<dmin then
dmin=d
zmin=Zone
end
end
return zmin,dmin
end
end
do
SET_ZONE_GOAL={
ClassName="SET_ZONE_GOAL",
Zones={},
Filter={
Prefixes=nil,
},
FilterMeta={
},
}
function SET_ZONE_GOAL:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.ZONES_GOAL))
return self
end
function SET_ZONE_GOAL:AddZone(Zone)
self:Add(Zone:GetName(),Zone)
return self
end
function SET_ZONE_GOAL:RemoveZonesByName(RemoveZoneNames)
local RemoveZoneNamesArray=(type(RemoveZoneNames)=="table")and RemoveZoneNames or{RemoveZoneNames}
for RemoveZoneID,RemoveZoneName in pairs(RemoveZoneNamesArray)do
self:Remove(RemoveZoneName)
end
return self
end
function SET_ZONE_GOAL:FindZone(ZoneName)
local ZoneFound=self.Set[ZoneName]
return ZoneFound
end
function SET_ZONE_GOAL:GetRandomZone()
if self:Count()~=0 then
local Index=self.Index
local ZoneFound=nil
while not ZoneFound do
local ZoneRandom=math.random(1,#Index)
ZoneFound=self.Set[Index[ZoneRandom]]:GetZoneMaybe()
end
return ZoneFound
end
return nil
end
function SET_ZONE_GOAL:SetZoneProbability(ZoneName,ZoneProbability)
local Zone=self:FindZone(ZoneName)
Zone:SetZoneProbability(ZoneProbability)
end
function SET_ZONE_GOAL:FilterPrefixes(Prefixes)
if not self.Filter.Prefixes then
self.Filter.Prefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.Prefixes[Prefix]=Prefix
end
return self
end
function SET_ZONE_GOAL:FilterStart()
if _DATABASE then
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
else
self:RemoveZonesByName(ObjectName)
end
end
end
self:HandleEvent(EVENTS.NewZoneGoal)
self:HandleEvent(EVENTS.DeleteZoneGoal)
return self
end
function SET_ZONE_GOAL:FilterStop()
self:UnHandleEvent(EVENTS.NewZoneGoal)
self:UnHandleEvent(EVENTS.DeleteZoneGoal)
return self
end
function SET_ZONE_GOAL:AddInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_ZONE_GOAL:FindInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_ZONE_GOAL:ForEachZone(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_ZONE_GOAL:IsIncludeObject(MZone)
self:F2(MZone)
local MZoneInclude=true
if MZone then
local MZoneName=MZone:GetName()
if self.Filter.Prefixes then
local MZonePrefix=false
for ZonePrefixId,ZonePrefix in pairs(self.Filter.Prefixes)do
self:T3({"Prefix:",string.find(MZoneName,ZonePrefix,1),ZonePrefix})
if string.find(MZoneName,ZonePrefix,1)then
MZonePrefix=true
end
end
self:T({"Evaluated Prefix",MZonePrefix})
MZoneInclude=MZoneInclude and MZonePrefix
end
end
self:T2(MZoneInclude)
return MZoneInclude
end
function SET_ZONE_GOAL:OnEventNewZoneGoal(EventData)
self:T({"New Zone Capture Coalition",EventData})
self:T({"Zone Capture Coalition",EventData.ZoneGoal})
if EventData.ZoneGoal then
if EventData.ZoneGoal and self:IsIncludeObject(EventData.ZoneGoal)then
self:T({"Adding Zone Capture Coalition",EventData.ZoneGoal.ZoneName,EventData.ZoneGoal})
self:Add(EventData.ZoneGoal.ZoneName,EventData.ZoneGoal)
end
end
end
function SET_ZONE_GOAL:OnEventDeleteZoneGoal(EventData)
self:F3({EventData})
if EventData.ZoneGoal then
local Zone=_DATABASE:FindZone(EventData.ZoneGoal.ZoneName)
if Zone and Zone.ZoneName then
self:F({ZoneNoDestroy=Zone.NoDestroy})
if Zone.NoDestroy then
else
self:Remove(Zone.ZoneName)
end
end
end
end
function SET_ZONE_GOAL:IsCoordinateInZone(Coordinate)
for _,Zone in pairs(self:GetSet())do
local Zone=Zone
if Zone:IsCoordinateInZone(Coordinate)then
return Zone
end
end
return nil
end
end
do
SET_OPSZONE={
ClassName="SET_OPSZONE",
Zones={},
Filter={
Prefixes=nil,
Coalitions=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
},
}
function SET_OPSZONE:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.OPSZONES))
return self
end
function SET_OPSZONE:AddZone(Zone)
self:Add(Zone:GetName(),Zone)
return self
end
function SET_OPSZONE:RemoveZonesByName(RemoveZoneNames)
local RemoveZoneNamesArray=(type(RemoveZoneNames)=="table")and RemoveZoneNames or{RemoveZoneNames}
for RemoveZoneID,RemoveZoneName in pairs(RemoveZoneNamesArray)do
self:Remove(RemoveZoneName)
end
return self
end
function SET_OPSZONE:FindZone(ZoneName)
local ZoneFound=self.Set[ZoneName]
return ZoneFound
end
function SET_OPSZONE:GetRandomZone()
if self:Count()~=0 then
local Index=self.Index
local ZoneFound=nil
while not ZoneFound do
local ZoneRandom=math.random(1,#Index)
ZoneFound=self.Set[Index[ZoneRandom]]:GetZoneMaybe()
end
return ZoneFound
end
return nil
end
function SET_OPSZONE:SetZoneProbability(ZoneName,Probability)
local Zone=self:FindZone(ZoneName)
Zone:SetZoneProbability(Probability)
return self
end
function SET_OPSZONE:FilterPrefixes(Prefixes)
if not self.Filter.Prefixes then
self.Filter.Prefixes={}
end
Prefixes=UTILS.EnsureTable(Prefixes,false)
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.Prefixes[Prefix]=Prefix
end
return self
end
function SET_OPSZONE:FilterCoalitions(Coalitions)
if not self.Filter.Coalitions then
self.Filter.Coalitions={}
end
Coalitions=UTILS.EnsureTable(Coalitions,false)
for CoalitionID,Coalition in pairs(Coalitions)do
self.Filter.Coalitions[Coalition]=Coalition
end
return self
end
function SET_OPSZONE:FilterOnce()
for ObjectName,Object in pairs(self.Database)do
self:Remove(ObjectName,true)
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
end
end
return self
end
function SET_OPSZONE:FilterClear()
local parent=self:GetParent(self,SET_OPSZONE)
parent:FilterClear()
return self
end
function SET_OPSZONE:FilterStart()
if _DATABASE then
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
else
self:RemoveZonesByName(ObjectName)
end
end
end
self:HandleEvent(EVENTS.NewZoneGoal)
self:HandleEvent(EVENTS.DeleteZoneGoal)
return self
end
function SET_OPSZONE:FilterStop()
self:UnHandleEvent(EVENTS.NewZoneGoal)
self:UnHandleEvent(EVENTS.DeleteZoneGoal)
return self
end
function SET_OPSZONE:AddInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_OPSZONE:FindInDatabase(Event)
self:F3({Event})
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_OPSZONE:ForEachZone(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_OPSZONE:IsIncludeObject(MZone)
self:F2(MZone)
local MZoneInclude=true
if MZone then
local MZoneName=MZone:GetName()
if self.Filter.Prefixes then
local MZonePrefix=false
for ZonePrefixId,ZonePrefix in pairs(self.Filter.Prefixes)do
self:T3({"Prefix:",string.find(MZoneName,ZonePrefix,1),ZonePrefix})
if string.find(MZoneName,ZonePrefix,1)then
MZonePrefix=true
break
end
end
self:T({"Evaluated Prefix",MZonePrefix})
MZoneInclude=MZoneInclude and MZonePrefix
end
if self.Filter.Coalitions then
local MGroupCoalition=false
local coalition=MZone:GetOwner()
for _,CoalitionName in pairs(self.Filter.Coalitions)do
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==coalition then
MGroupCoalition=true
break
end
end
MZoneInclude=MZoneInclude and MGroupCoalition
end
end
self:T2(MZoneInclude)
return MZoneInclude
end
function SET_OPSZONE:OnEventNewZoneGoal(EventData)
self:T({"New Zone Capture Coalition",EventData})
self:T({"Zone Capture Coalition",EventData.ZoneGoal})
if EventData.ZoneGoal then
if EventData.ZoneGoal and self:IsIncludeObject(EventData.ZoneGoal)then
self:T({"Adding Zone Capture Coalition",EventData.ZoneGoal.ZoneName,EventData.ZoneGoal})
self:Add(EventData.ZoneGoal.ZoneName,EventData.ZoneGoal)
end
end
end
function SET_OPSZONE:OnEventDeleteZoneGoal(EventData)
self:F3({EventData})
if EventData.ZoneGoal then
local Zone=_DATABASE:FindZone(EventData.ZoneGoal.ZoneName)
if Zone and Zone.ZoneName then
self:F({ZoneNoDestroy=Zone.NoDestroy})
if Zone.NoDestroy then
else
self:Remove(Zone.ZoneName)
end
end
end
end
function SET_OPSZONE:Start()
for _,_Zone in pairs(self:GetSet())do
local Zone=_Zone
if Zone:IsStopped()then
Zone:Start()
end
end
return self
end
function SET_OPSZONE:IsCoordinateInZone(Coordinate)
for _,_Zone in pairs(self:GetSet())do
local Zone=_Zone
if Zone:GetZone():IsCoordinateInZone(Coordinate)then
return Zone
end
end
return nil
end
function SET_OPSZONE:GetClosestZone(Coordinate,Coalitions)
Coalitions=UTILS.EnsureTable(Coalitions,true)
local dmin=math.huge
local zmin=nil
for _,_opszone in pairs(self:GetSet())do
local opszone=_opszone
local coal=opszone:GetOwner()
if opszone:IsStarted()and(Coalitions==nil or(Coalitions and UTILS.IsInTable(Coalitions,coal)))then
local d=opszone:GetZone():Get2DDistance(Coordinate)
if d<dmin then
dmin=d
zmin=opszone
end
end
end
return zmin,dmin
end
end
do
SET_OPSGROUP={
ClassName="SET_OPSGROUP",
Filter={
Coalitions=nil,
Categories=nil,
Countries=nil,
GroupPrefixes=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
plane=Group.Category.AIRPLANE,
helicopter=Group.Category.HELICOPTER,
ground=Group.Category.GROUND,
ship=Group.Category.SHIP,
},
},
}
function SET_OPSGROUP:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.GROUPS))
self:FilterActive(false)
return self
end
function SET_OPSGROUP:GetAliveSet()
local AliveSet=SET_OPSGROUP:New()
for GroupName,GroupObject in pairs(self.Set)do
local GroupObject=GroupObject
if GroupObject and GroupObject:IsAlive()then
AliveSet:Add(GroupName,GroupObject)
end
end
return AliveSet.Set or{}
end
function SET_OPSGROUP:Add(ObjectName,Object)
self:T({ObjectName=ObjectName,Object=Object})
if self.Set[ObjectName]then
self:Remove(ObjectName,true)
end
local object=nil
if Object:IsInstanceOf("GROUP")then
object=_DATABASE:FindOpsGroup(ObjectName)
if not object then
if Object:IsShip()then
object=NAVYGROUP:New(Object)
elseif Object:IsGround()then
object=ARMYGROUP:New(Object)
elseif Object:IsAir()then
object=FLIGHTGROUP:New(Object)
else
env.error("ERROR: Unknown category of group object!")
end
end
elseif Object:IsInstanceOf("OPSGROUP")then
object=Object
else
env.error("ERROR: Object must be a GROUP or OPSGROUP!")
end
self.Set[ObjectName]=object
table.insert(self.Index,ObjectName)
self:Added(ObjectName,object)
end
function SET_OPSGROUP:AddObject(Object)
self:Add(Object.groupname,Object)
end
function SET_OPSGROUP:AddGroup(group)
local groupname=group:GetName()
self:Add(groupname,group)
return self
end
function SET_OPSGROUP:AddGroupsByName(AddGroupNames)
local AddGroupNamesArray=(type(AddGroupNames)=="table")and AddGroupNames or{AddGroupNames}
for AddGroupID,AddGroupName in pairs(AddGroupNamesArray)do
self:Add(AddGroupName,GROUP:FindByName(AddGroupName))
end
return self
end
function SET_OPSGROUP:RemoveGroupsByName(RemoveGroupNames)
local RemoveGroupNamesArray=(type(RemoveGroupNames)=="table")and RemoveGroupNames or{RemoveGroupNames}
for RemoveGroupID,RemoveGroupName in pairs(RemoveGroupNamesArray)do
self:Remove(RemoveGroupName)
end
return self
end
function SET_OPSGROUP:FindGroup(GroupName)
local GroupFound=self.Set[GroupName]
return GroupFound
end
function SET_OPSGROUP:FindFlightGroup(GroupName)
local GroupFound=self:FindGroup(GroupName)
return GroupFound
end
function SET_OPSGROUP:FindArmyGroup(GroupName)
local GroupFound=self:FindGroup(GroupName)
return GroupFound
end
function SET_OPSGROUP:FindNavyGroup(GroupName)
local GroupFound=self:FindGroup(GroupName)
return GroupFound
end
function SET_OPSGROUP:FilterCoalitions(Coalitions,Clear)
if Clear or not self.Filter.Coalitions then
self.Filter.Coalitions={}
end
if type(Coalitions)~="table"then
Coalitions={Coalitions}
end
for CoalitionID,Coalition in pairs(Coalitions)do
self.Filter.Coalitions[Coalition]=Coalition
end
return self
end
function SET_OPSGROUP:FilterCategories(Categories,Clear)
if Clear or not self.Filter.Categories then
self.Filter.Categories={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_OPSGROUP:FilterCategoryGround()
self:FilterCategories("ground")
return self
end
function SET_OPSGROUP:FilterCategoryAirplane()
self:FilterCategories("plane")
return self
end
function SET_OPSGROUP:FilterCategoryAircraft()
self:FilterCategories({"plane","helicopter"})
return self
end
function SET_OPSGROUP:FilterCategoryHelicopter()
self:FilterCategories("helicopter")
return self
end
function SET_OPSGROUP:FilterCategoryShip()
self:FilterCategories("ship")
return self
end
function SET_OPSGROUP:FilterCountries(Countries,Clear)
if Clear or not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_OPSGROUP:FilterPrefixes(Prefixes,Clear)
if Clear or not self.Filter.GroupPrefixes then
self.Filter.GroupPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.GroupPrefixes[Prefix]=Prefix
end
return self
end
function SET_OPSGROUP:FilterActive(Active)
Active=Active or not(Active==false)
self.Filter.Active=Active
return self
end
function SET_OPSGROUP:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.RemoveUnit,self._EventOnDeadOrCrash)
end
return self
end
function SET_OPSGROUP:Activate(Delay)
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
local group=GroupData
if group and group:IsAlive()==false then
group:Activate(Delay)
end
end
return self
end
function SET_OPSGROUP:_EventOnDeadOrCrash(Event)
self:F({Event})
if Event.IniDCSUnit then
local ObjectName,Object=self:FindInDatabase(Event)
if ObjectName then
if Event.IniDCSGroup:getSize()==1 then
self:Remove(ObjectName)
end
end
end
end
function SET_OPSGROUP:AddInDatabase(Event)
if Event.IniObjectCategory==1 then
if not self.Database[Event.IniDCSGroupName]then
self.Database[Event.IniDCSGroupName]=GROUP:Register(Event.IniDCSGroupName)
end
end
return Event.IniDCSGroupName,self.Database[Event.IniDCSGroupName]
end
function SET_OPSGROUP:FindInDatabase(Event)
return Event.IniDCSGroupName,self.Database[Event.IniDCSGroupName]
end
function SET_OPSGROUP:ForEachGroup(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_OPSGROUP:IsIncludeObject(MGroup)
local MGroupInclude=true
if self.Filter.Active~=nil then
local MGroupActive=false
if self.Filter.Active==false or(self.Filter.Active==true and MGroup:IsActive()==true)then
MGroupActive=true
end
MGroupInclude=MGroupInclude and MGroupActive
end
if self.Filter.Coalitions then
local MGroupCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MGroup:GetCoalition()then
MGroupCoalition=true
end
end
MGroupInclude=MGroupInclude and MGroupCoalition
end
if self.Filter.Categories then
local MGroupCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MGroup:GetCategory()then
MGroupCategory=true
end
end
MGroupInclude=MGroupInclude and MGroupCategory
end
if self.Filter.Countries then
local MGroupCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
if country.id[CountryName]==MGroup:GetCountry()then
MGroupCountry=true
end
end
MGroupInclude=MGroupInclude and MGroupCountry
end
if self.Filter.GroupPrefixes then
local MGroupPrefix=false
for GroupPrefixId,GroupPrefix in pairs(self.Filter.GroupPrefixes)do
if string.find(MGroup:GetName(),GroupPrefix:gsub("-","%%-"),1)then
MGroupPrefix=true
end
end
MGroupInclude=MGroupInclude and MGroupPrefix
end
return MGroupInclude
end
end
do
SET_SCENERY={
ClassName="SET_SCENERY",
Scenerys={},
Filter={
SceneryPrefixes=nil,
Zones=nil,
},
}
function SET_SCENERY:New(ZoneSet)
local zoneset={}
local self=BASE:Inherit(self,SET_BASE:New(zoneset))
local zonenames={}
if ZoneSet then
for _,_zone in pairs(ZoneSet.Set)do
self:T("Zone type handed: "..tostring(_zone.ClassName))
table.insert(zonenames,_zone:GetName())
end
self:AddSceneryByName(zonenames)
end
return self
end
function SET_SCENERY:NewFromZone(Zone)
local zone=Zone
if type(Zone)=="string"then
zone=ZONE:FindByName(Zone)
end
zone:Scan({Object.Category.SCENERY})
return zone:GetScannedSetScenery()
end
function SET_SCENERY:AddScenery(AddScenery)
self:F2(AddScenery:GetName())
self:Add(AddScenery:GetName(),AddScenery)
return self
end
function SET_SCENERY:AddSceneryByName(AddSceneryNames)
local AddSceneryNamesArray=(type(AddSceneryNames)=="table")and AddSceneryNames or{AddSceneryNames}
self:T(AddSceneryNamesArray)
for AddSceneryID,AddSceneryName in pairs(AddSceneryNamesArray)do
self:Add(AddSceneryName,SCENERY:FindByZoneName(AddSceneryName))
end
return self
end
function SET_SCENERY:RemoveSceneryByName(RemoveSceneryNames)
local RemoveSceneryNamesArray=(type(RemoveSceneryNames)=="table")and RemoveSceneryNames or{RemoveSceneryNames}
for RemoveSceneryID,RemoveSceneryName in pairs(RemoveSceneryNamesArray)do
self:Remove(RemoveSceneryName)
end
return self
end
function SET_SCENERY:FindScenery(SceneryName)
local SceneryFound=self.Set[SceneryName]
return SceneryFound
end
function SET_SCENERY:FilterZones(Zones)
if not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_SCENERY:FilterPrefixes(Prefixes)
if not self.Filter.SceneryPrefixes then
self.Filter.SceneryPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.SceneryPrefixes[Prefix]=Prefix
end
return self
end
function SET_SCENERY:CountAlive()
local Set=self:GetSet()
local CountU=0
for UnitID,UnitData in pairs(Set)do
if UnitData and UnitData:IsAlive()then
CountU=CountU+1
end
end
return CountU
end
function SET_SCENERY:GetAliveSet()
self:F2()
local AliveSet=SET_SCENERY:New()
for GroupName,GroupObject in pairs(self.Set)do
local GroupObject=GroupObject
if GroupObject then
if GroupObject:IsAlive()then
AliveSet:Add(GroupName,GroupObject)
end
end
end
return AliveSet.Set or{},AliveSet
end
function SET_SCENERY:ForEachScenery(IteratorFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_SCENERY:GetCoordinate()
local Coordinate=self:GetRandom():GetCoordinate()
local x1=Coordinate.x
local x2=Coordinate.x
local y1=Coordinate.y
local y2=Coordinate.y
local z1=Coordinate.z
local z2=Coordinate.z
for SceneryName,SceneryData in pairs(self:GetSet())do
local Scenery=SceneryData
local Coordinate=Scenery:GetCoordinate()
x1=(Coordinate.x<x1)and Coordinate.x or x1
x2=(Coordinate.x>x2)and Coordinate.x or x2
y1=(Coordinate.y<y1)and Coordinate.y or y1
y2=(Coordinate.y>y2)and Coordinate.y or y2
z1=(Coordinate.y<z1)and Coordinate.z or z1
z2=(Coordinate.y>z2)and Coordinate.z or z2
end
Coordinate.x=(x2-x1)/2+x1
Coordinate.y=(y2-y1)/2+y1
Coordinate.z=(z2-z1)/2+z1
self:F({Coordinate=Coordinate})
return Coordinate
end
function SET_SCENERY:IsIncludeObject(MScenery)
self:F2(MScenery)
return true
end
end
do
COORDINATE={
ClassName="COORDINATE",
}
COORDINATE.WaypointAltType={
BARO="BARO",
RADIO="RADIO",
}
COORDINATE.WaypointAction={
TurningPoint="Turning Point",
FlyoverPoint="Fly Over Point",
FromParkingArea="From Parking Area",
FromParkingAreaHot="From Parking Area Hot",
FromGroundAreaHot="From Ground Area Hot",
FromGroundArea="From Ground Area",
FromRunway="From Runway",
Landing="Landing",
LandingReFuAr="LandingReFuAr",
}
COORDINATE.WaypointType={
TakeOffParking="TakeOffParking",
TakeOffParkingHot="TakeOffParkingHot",
TakeOff="TakeOffParkingHot",
TakeOffGroundHot="TakeOffGroundHot",
TakeOffGround="TakeOffGround",
TurningPoint="Turning Point",
Land="Land",
LandingReFuAr="LandingReFuAr",
}
function COORDINATE:New(x,y,z)
local self=BASE:Inherit(self,BASE:New())
self.x=x
self.y=y
self.z=z
return self
end
function COORDINATE:NewFromCoordinate(Coordinate)
local self=BASE:Inherit(self,BASE:New())
self.x=Coordinate.x
self.y=Coordinate.y
self.z=Coordinate.z
return self
end
function COORDINATE:NewFromVec2(Vec2,LandHeightAdd)
local LandHeight=land.getHeight(Vec2)
LandHeightAdd=LandHeightAdd or 0
LandHeight=LandHeight+LandHeightAdd
local self=self:New(Vec2.x,LandHeight,Vec2.y)
return self
end
function COORDINATE:NewFromVec3(Vec3)
local self=self:New(Vec3.x,Vec3.y,Vec3.z)
self:F2(self)
return self
end
function COORDINATE:NewFromWaypoint(Waypoint)
local self=self:New(Waypoint.x,Waypoint.alt,Waypoint.y)
return self
end
function COORDINATE:GetCoordinate()
return self
end
function COORDINATE:GetVec3()
return{x=self.x,y=self.y,z=self.z}
end
function COORDINATE:GetVec2()
return{x=self.x,y=self.z}
end
function COORDINATE:UpdateFromVec3(Vec3)
self.x=Vec3.x
self.y=Vec3.y
self.z=Vec3.z
return self
end
function COORDINATE:UpdateFromCoordinate(Coordinate)
self.x=Coordinate.x
self.y=Coordinate.y
self.z=Coordinate.z
return self
end
function COORDINATE:UpdateFromVec2(Vec2)
self.x=Vec2.x
self.z=Vec2.y
return self
end
function COORDINATE:GetMagneticDeclination(Month,Year)
local decl=UTILS.GetMagneticDeclination()
if require then
local magvar=require('magvar')
if magvar then
local date,year,month,day=UTILS.GetDCSMissionDate()
magvar.init(Month or month,Year or year)
local lat,lon=self:GetLLDDM()
decl=magvar.get_mag_decl(lat,lon)
if decl then
decl=math.deg(decl)
end
end
else
self:T("The require package is not available. Using constant value for magnetic declination")
end
return decl
end
function COORDINATE:NewFromLLDD(latitude,longitude,altitude)
local vec3=coord.LLtoLO(latitude,longitude)
local _coord=self:NewFromVec3(vec3)
if altitude==nil then
_coord.y=self:GetLandHeight()
else
_coord.y=altitude
end
return _coord
end
function COORDINATE:IsAtCoordinate2D(Coordinate,Precision)
self:F({Coordinate=Coordinate:GetVec2()})
self:F({self=self:GetVec2()})
local x=Coordinate.x
local z=Coordinate.z
return x-Precision<=self.x and x+Precision>=self.x and z-Precision<=self.z and z+Precision>=self.z
end
function COORDINATE:ScanObjects(radius,scanunits,scanstatics,scanscenery)
self:F(string.format("Scanning in radius %.1f m.",radius or 100))
local SphereSearch={
id=world.VolumeType.SPHERE,
params={
point=self:GetVec3(),
radius=radius,
}
}
radius=radius or 100
if scanunits==nil then
scanunits=true
end
if scanstatics==nil then
scanstatics=true
end
if scanscenery==nil then
scanscenery=false
end
local scanobjects={}
if scanunits then
table.insert(scanobjects,Object.Category.UNIT)
end
if scanstatics then
table.insert(scanobjects,Object.Category.STATIC)
end
if scanscenery then
table.insert(scanobjects,Object.Category.SCENERY)
end
local Units={}
local Statics={}
local Scenery={}
local gotstatics=false
local gotunits=false
local gotscenery=false
local function EvaluateZone(ZoneObject)
BASE:T({ZoneObject})
if ZoneObject then
local ObjectCategory=ZoneObject:getCategory()
if ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()then
table.insert(Units,UNIT:Find(ZoneObject))
gotunits=true
elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist()then
table.insert(Statics,ZoneObject)
gotstatics=true
elseif ObjectCategory==Object.Category.SCENERY then
table.insert(Scenery,ZoneObject)
gotscenery=true
end
end
return true
end
world.searchObjects(scanobjects,SphereSearch,EvaluateZone)
for _,unit in pairs(Units)do
self:T(string.format("Scan found unit %s",unit:GetName()))
end
for _,static in pairs(Statics)do
self:T(string.format("Scan found static %s",static:getName()))
_DATABASE:AddStatic(static:getName())
end
for _,scenery in pairs(Scenery)do
self:T(string.format("Scan found scenery %s typename=%s",scenery:getName(),scenery:getTypeName()))
end
return gotunits,gotstatics,gotscenery,Units,Statics,Scenery
end
function COORDINATE:ScanUnits(radius)
local _,_,_,units=self:ScanObjects(radius,true,false,false)
local set=SET_UNIT:New()
for _,unit in pairs(units)do
set:AddUnit(unit)
end
return set
end
function COORDINATE:FindClosestUnit(radius)
local units=self:ScanUnits(radius)
local umin=nil
local dmin=math.huge
for _,_unit in pairs(units.Set)do
local unit=_unit
local coordinate=unit:GetCoordinate()
local d=self:Get2DDistance(coordinate)
if d<dmin then
dmin=d
umin=unit
end
end
return umin
end
function COORDINATE:ScanScenery(radius)
local _,_,_,_,_,scenerys=self:ScanObjects(radius,false,false,true)
local set={}
for _,_scenery in pairs(scenerys)do
local scenery=_scenery
local name=scenery:getName()
local s=SCENERY:Register(name,scenery)
table.insert(set,s)
end
return set
end
function COORDINATE:FindClosestScenery(radius)
local sceneries=self:ScanScenery(radius)
local umin=nil
local dmin=math.huge
for _,_scenery in pairs(sceneries)do
local scenery=_scenery
local coordinate=scenery:GetCoordinate()
local d=self:Get2DDistance(coordinate)
if d<dmin then
dmin=d
umin=scenery
end
end
return umin
end
function COORDINATE:DistanceFromPointVec2(PointVec2Reference)
self:F2(PointVec2Reference)
local Distance=((PointVec2Reference.x-self.x)^2+(PointVec2Reference.z-self.z)^2)^0.5
self:T2(Distance)
return Distance
end
function COORDINATE:Translate(Distance,Angle,Keepalt,Overwrite)
local alpha=math.rad((Angle or 0))
local x=Distance*math.cos(alpha)+self.x
local z=Distance*math.sin(alpha)+self.z
local y=Keepalt and self.y or land.getHeight({x=x,y=z})
if Overwrite then
self.x=x
self.y=y
self.z=z
return self
else
local coord=COORDINATE:New(x,y,z)
return coord
end
end
function COORDINATE:Rotate2D(Angle)
if not Angle then
return self
end
local phi=math.rad(Angle)
local X=self.z
local Y=self.x
local x=X*math.cos(phi)-Y*math.sin(phi)
local y=X*math.sin(phi)+Y*math.cos(phi)
local coord=COORDINATE:NewFromVec3({x=y,y=self.y,z=x})
return coord
end
function COORDINATE:GetRandomVec2InRadius(OuterRadius,InnerRadius)
self:F2({OuterRadius,InnerRadius})
local Theta=2*math.pi*math.random()
local Radials=math.random()+math.random()
if Radials>1 then
Radials=2-Radials
end
local RadialMultiplier
if InnerRadius and InnerRadius<=OuterRadius then
RadialMultiplier=(OuterRadius-InnerRadius)*Radials+InnerRadius
else
RadialMultiplier=OuterRadius*Radials
end
local RandomVec2
if OuterRadius>0 then
RandomVec2={x=math.cos(Theta)*RadialMultiplier+self.x,y=math.sin(Theta)*RadialMultiplier+self.z}
else
RandomVec2={x=self.x,y=self.z}
end
return RandomVec2
end
function COORDINATE:GetRandomCoordinateInRadius(OuterRadius,InnerRadius)
self:F2({OuterRadius,InnerRadius})
local coord=COORDINATE:NewFromVec2(self:GetRandomVec2InRadius(OuterRadius,InnerRadius))
return coord
end
function COORDINATE:GetRandomVec3InRadius(OuterRadius,InnerRadius)
local RandomVec2=self:GetRandomVec2InRadius(OuterRadius,InnerRadius)
local y=self.y+math.random(InnerRadius,OuterRadius)
local RandomVec3={x=RandomVec2.x,y=y,z=RandomVec2.y}
return RandomVec3
end
function COORDINATE:GetLandHeight()
local Vec2={x=self.x,y=self.z}
return land.getHeight(Vec2)
end
function COORDINATE:SetHeading(Heading)
self.Heading=Heading
end
function COORDINATE:GetHeading()
return self.Heading
end
function COORDINATE:SetVelocity(Velocity)
self.Velocity=Velocity
end
function COORDINATE:GetVelocity()
local Velocity=self.Velocity
return Velocity or 0
end
function COORDINATE:GetName()
local name=self:ToStringMGRS()
return name
end
function COORDINATE:GetMovingText(Settings)
return self:GetVelocityText(Settings)..", "..self:GetHeadingText(Settings)
end
function COORDINATE:GetDirectionVec3(TargetCoordinate)
if TargetCoordinate then
return{x=TargetCoordinate.x-self.x,y=TargetCoordinate.y-self.y,z=TargetCoordinate.z-self.z}
else
return{x=0,y=0,z=0}
end
end
function COORDINATE:GetNorthCorrectionRadians()
local TargetVec3=self:GetVec3()
local lat,lon=coord.LOtoLL(TargetVec3)
local north_posit=coord.LLtoLO(lat+1,lon)
return math.atan2(north_posit.z-TargetVec3.z,north_posit.x-TargetVec3.x)
end
function COORDINATE:GetAngleRadians(DirectionVec3)
local DirectionRadians=math.atan2(DirectionVec3.z,DirectionVec3.x)
if DirectionRadians<0 then
DirectionRadians=DirectionRadians+2*math.pi
end
return DirectionRadians
end
function COORDINATE:GetAngleDegrees(DirectionVec3)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Angle=UTILS.ToDegree(AngleRadians)
return Angle
end
function COORDINATE:GetIntermediateCoordinate(ToCoordinate,Fraction)
local f=Fraction or 0.5
local vec=UTILS.VecSubstract(ToCoordinate,self)
if f>1 then
local norm=UTILS.VecNorm(vec)
f=Fraction/norm
end
vec.x=f*vec.x
vec.y=f*vec.y
vec.z=f*vec.z
vec=UTILS.VecAdd(self,vec)
local coord=COORDINATE:New(vec.x,vec.y,vec.z)
return coord
end
function COORDINATE:Get2DDistance(TargetCoordinate)
if not TargetCoordinate then return 1000000 end
local a={x=TargetCoordinate.x-self.x,y=0,z=TargetCoordinate.z-self.z}
local norm=UTILS.VecNorm(a)
return norm
end
function COORDINATE:GetTemperature(height)
self:F2(height)
local y=height or self.y
local point={x=self.x,y=height or self.y,z=self.z}
local T,P=atmosphere.getTemperatureAndPressure(point)
return T-273.15
end
function COORDINATE:GetTemperatureText(height,Settings)
local DegreesCelcius=self:GetTemperature(height)
local Settings=Settings or _SETTINGS
if DegreesCelcius then
if Settings:IsMetric()then
return string.format(" %-2.2f °C",DegreesCelcius)
else
return string.format(" %-2.2f °F",UTILS.CelsiusToFahrenheit(DegreesCelcius))
end
else
return" no temperature"
end
return nil
end
function COORDINATE:GetPressure(height)
local point={x=self.x,y=height or self.y,z=self.z}
local T,P=atmosphere.getTemperatureAndPressure(point)
return P/100
end
function COORDINATE:GetPressureText(height,Settings)
local Pressure_hPa=self:GetPressure(height)
local Pressure_mmHg=Pressure_hPa*0.7500615613030
local Pressure_inHg=Pressure_hPa*0.0295299830714
local Settings=Settings or _SETTINGS
if Pressure_hPa then
if Settings:IsMetric()then
return string.format(" %4.1f hPa (%3.1f mmHg)",Pressure_hPa,Pressure_mmHg)
else
return string.format(" %4.1f hPa (%3.2f inHg)",Pressure_hPa,Pressure_inHg)
end
else
return" no pressure"
end
return nil
end
function COORDINATE:HeadingTo(ToCoordinate)
local dz=ToCoordinate.z-self.z
local dx=ToCoordinate.x-self.x
local heading=math.deg(math.atan2(dz,dx))
if heading<0 then
heading=360+heading
end
return heading
end
function COORDINATE:GetWind(height)
local landheight=self:GetLandHeight()+0.1
local point={x=self.x,y=math.max(height or self.y,landheight),z=self.z}
local wind=atmosphere.getWind(point)
local direction=math.deg(math.atan2(wind.z,wind.x))
if direction<0 then
direction=360+direction
end
if direction>180 then
direction=direction-180
else
direction=direction+180
end
local strength=math.sqrt((wind.x)^2+(wind.z)^2)
return direction,strength
end
function COORDINATE:GetWindWithTurbulenceVec3(height)
local landheight=self:GetLandHeight()+0.1
local point={x=self.x,y=math.max(height or self.y,landheight),z=self.z}
local vec3=atmosphere.getWindWithTurbulence(point)
return vec3
end
function COORDINATE:GetWindText(height,Settings)
local Direction,Strength=self:GetWind(height)
local Settings=Settings or _SETTINGS
if Direction and Strength then
if Settings:IsMetric()then
return string.format(" %d ° at %3.2f mps",Direction,UTILS.MpsToKmph(Strength))
else
return string.format(" %d ° at %3.2f kps",Direction,UTILS.MpsToKnots(Strength))
end
else
return" no wind"
end
return nil
end
function COORDINATE:Get3DDistance(TargetCoordinate)
local TargetVec3=TargetCoordinate:GetVec3()
local SourceVec3=self:GetVec3()
return((TargetVec3.x-SourceVec3.x)^2+(TargetVec3.y-SourceVec3.y)^2+(TargetVec3.z-SourceVec3.z)^2)^0.5
end
function COORDINATE:GetBearingText(AngleRadians,Precision,Settings,MagVar)
local Settings=Settings or _SETTINGS
local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),Precision)
local s=string.format('%03d°',AngleDegrees)
if MagVar then
local variation=UTILS.GetMagneticDeclination()or 0
local AngleMagnetic=AngleDegrees-variation
if AngleMagnetic<0 then AngleMagnetic=360-AngleMagnetic end
s=string.format('%03d°M|%03d°',AngleMagnetic,AngleDegrees)
end
return s
end
function COORDINATE:GetDistanceText(Distance,Settings,Language,Precision)
local Settings=Settings or _SETTINGS
local Language=Language or Settings.Locale or _SETTINGS.Locale or"EN"
Language=string.lower(Language)
local Precision=Precision or 0
local DistanceText
if Settings:IsMetric()then
if Language=="en"then
DistanceText=" for "..UTILS.Round(Distance/1000,Precision).." km"
elseif Language=="ru"then
DistanceText=" за "..UTILS.Round(Distance/1000,Precision).." километров"
end
else
if Language=="en"then
DistanceText=" for "..UTILS.Round(UTILS.MetersToNM(Distance),Precision).." miles"
elseif Language=="ru"then
DistanceText=" за "..UTILS.Round(UTILS.MetersToNM(Distance),Precision).." миль"
end
end
return DistanceText
end
function COORDINATE:GetAltitudeText(Settings,Language)
local Altitude=self.y
local Settings=Settings or _SETTINGS
local Language=Language or Settings.Locale or _SETTINGS.Locale or"EN"
Language=string.lower(Language)
if Altitude~=0 then
if Settings:IsMetric()then
if Language=="en"then
return" at "..UTILS.Round(self.y,-3).." meters"
elseif Language=="ru"then
return" в "..UTILS.Round(self.y,-3).." метры"
end
else
if Language=="en"then
return" at "..UTILS.Round(UTILS.MetersToFeet(self.y),-3).." feet"
elseif Language=="ru"then
return" в "..UTILS.Round(self.y,-3).." ноги"
end
end
else
return""
end
end
function COORDINATE:GetVelocityText(Settings)
local Velocity=self:GetVelocity()
local Settings=Settings or _SETTINGS
if Velocity then
if Settings:IsMetric()then
return string.format(" moving at %d km/h",UTILS.MpsToKmph(Velocity))
else
return string.format(" moving at %d mi/h",UTILS.MpsToKmph(Velocity)/1.852)
end
else
return" stationary"
end
end
function COORDINATE:GetHeadingText(Settings)
local Heading=self:GetHeading()
if Heading then
return string.format(" bearing %3d°",Heading)
else
return" bearing unknown"
end
end
function COORDINATE:GetBRText(AngleRadians,Distance,Settings,Language,MagVar)
local Settings=Settings or _SETTINGS
local BearingText=self:GetBearingText(AngleRadians,0,Settings,MagVar)
local DistanceText=self:GetDistanceText(Distance,Settings,Language,0)
local BRText=BearingText..DistanceText
return BRText
end
function COORDINATE:GetBRAText(AngleRadians,Distance,Settings,Language,MagVar)
local Settings=Settings or _SETTINGS
local BearingText=self:GetBearingText(AngleRadians,0,Settings,MagVar)
local DistanceText=self:GetDistanceText(Distance,Settings,Language,0)
local AltitudeText=self:GetAltitudeText(Settings,Language)
local BRAText=BearingText..DistanceText..AltitudeText
return BRAText
end
function COORDINATE:SetAltitude(altitude,asl)
local alt=altitude
if asl then
alt=altitude
else
alt=self:GetLandHeight()+altitude
end
self.y=alt
return self
end
function COORDINATE:SetAtLandheight()
local alt=self:GetLandHeight()
self.y=alt
return self
end
function COORDINATE:WaypointAir(AltType,Type,Action,Speed,SpeedLocked,airbase,DCSTasks,description,timeReFuAr)
self:F2({AltType,Type,Action,Speed,SpeedLocked})
AltType=AltType or"RADIO"
if SpeedLocked==nil then
SpeedLocked=true
end
Speed=Speed or 500
local RoutePoint={}
RoutePoint.x=self.x
RoutePoint.y=self.z
RoutePoint.alt=self.y
RoutePoint.alt_type=AltType
RoutePoint.type=Type or nil
RoutePoint.action=Action or nil
RoutePoint.speed=Speed/3.6
RoutePoint.speed_locked=SpeedLocked
RoutePoint.ETA=0
RoutePoint.ETA_locked=false
RoutePoint.name=description
if airbase then
local AirbaseID=airbase:GetID()
local AirbaseCategory=airbase:GetAirbaseCategory()
if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then
RoutePoint.linkUnit=AirbaseID
RoutePoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.AIRDROME then
RoutePoint.airdromeId=AirbaseID
else
self:E("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!")
end
end
if Type==COORDINATE.WaypointType.LandingReFuAr then
RoutePoint.timeReFuAr=timeReFuAr or 10
end
RoutePoint.task={}
RoutePoint.task.id="ComboTask"
RoutePoint.task.params={}
RoutePoint.task.params.tasks=DCSTasks or{}
self:T({RoutePoint=RoutePoint})
return RoutePoint
end
function COORDINATE:WaypointAirTurningPoint(AltType,Speed,DCSTasks,description)
return self:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,description)
end
function COORDINATE:WaypointAirFlyOverPoint(AltType,Speed)
return self:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.FlyoverPoint,Speed)
end
function COORDINATE:WaypointAirTakeOffParkingHot(AltType,Speed)
return self:WaypointAir(AltType,COORDINATE.WaypointType.TakeOffParkingHot,COORDINATE.WaypointAction.FromParkingAreaHot,Speed)
end
function COORDINATE:WaypointAirTakeOffParking(AltType,Speed)
return self:WaypointAir(AltType,COORDINATE.WaypointType.TakeOffParking,COORDINATE.WaypointAction.FromParkingArea,Speed)
end
function COORDINATE:WaypointAirTakeOffRunway(AltType,Speed)
return self:WaypointAir(AltType,COORDINATE.WaypointType.TakeOff,COORDINATE.WaypointAction.FromRunway,Speed)
end
function COORDINATE:WaypointAirLanding(Speed,airbase,DCSTasks,description)
return self:WaypointAir(nil,COORDINATE.WaypointType.Land,COORDINATE.WaypointAction.Landing,Speed,false,airbase,DCSTasks,description)
end
function COORDINATE:WaypointAirLandingReFu(Speed,airbase,timeReFuAr,DCSTasks,description)
return self:WaypointAir(nil,COORDINATE.WaypointType.LandingReFuAr,COORDINATE.WaypointAction.LandingReFuAr,Speed,false,airbase,DCSTasks,description,timeReFuAr or 10)
end
function COORDINATE:WaypointGround(Speed,Formation,DCSTasks)
self:F2({Speed,Formation,DCSTasks})
local RoutePoint={}
RoutePoint.x=self.x
RoutePoint.y=self.z
RoutePoint.alt=self:GetLandHeight()+1
RoutePoint.alt_type=COORDINATE.WaypointAltType.BARO
RoutePoint.type="Turning Point"
RoutePoint.action=Formation or"Off Road"
RoutePoint.formation_template=""
RoutePoint.ETA=0
RoutePoint.ETA_locked=false
RoutePoint.speed=(Speed or 20)/3.6
RoutePoint.speed_locked=true
RoutePoint.task={}
RoutePoint.task.id="ComboTask"
RoutePoint.task.params={}
RoutePoint.task.params.tasks=DCSTasks or{}
return RoutePoint
end
function COORDINATE:WaypointNaval(Speed,Depth,DCSTasks)
self:F2({Speed,Depth,DCSTasks})
local RoutePoint={}
RoutePoint.x=self.x
RoutePoint.y=self.z
RoutePoint.alt=Depth or self.y
RoutePoint.alt_type="BARO"
RoutePoint.type="Turning Point"
RoutePoint.action="Turning Point"
RoutePoint.formation_template=""
RoutePoint.ETA=0
RoutePoint.ETA_locked=false
RoutePoint.speed=(Speed or 20)/3.6
RoutePoint.speed_locked=true
RoutePoint.task={}
RoutePoint.task.id="ComboTask"
RoutePoint.task.params={}
RoutePoint.task.params.tasks=DCSTasks or{}
return RoutePoint
end
function COORDINATE:GetClosestAirbase(Category,Coalition)
local airbases=AIRBASE.GetAllAirbases(Coalition)
local closest=nil
local distmin=nil
for _,_airbase in pairs(airbases)do
local airbase=_airbase
if airbase then
local category=airbase:GetAirbaseCategory()
if Category and Category==category or Category==nil then
local dist=self:Get2DDistance(airbase:GetCoordinate())
if closest==nil then
distmin=dist
closest=airbase
else
if dist<distmin then
distmin=dist
closest=airbase
end
end
end
end
end
return closest,distmin
end
function COORDINATE:GetClosestAirbase2(Category,Coalition)
local closest,distmin=self:GetClosestAirbase(Category,Coalition)
return closest,distmin
end
function COORDINATE:GetClosestParkingSpot(airbase,terminaltype,free)
local airbases={}
if airbase then
table.insert(airbases,airbase)
else
airbases=AIRBASE.GetAllAirbases()
end
local _closest=nil
local _termID=nil
local _distmin=nil
local spot=nil
for _,_airbase in pairs(airbases)do
local mybase=_airbase
local parkingdata=mybase:GetParkingSpotsTable(terminaltype)
for _,_spot in pairs(parkingdata)do
if(free==true and _spot.Free==true)or(free==false and _spot.Free==false)or free==nil then
local _coord=_spot.Coordinate
local _dist=self:Get2DDistance(_coord)
if _distmin==nil then
_closest=_coord
_distmin=_dist
_termID=_spot.TerminalID
spot=_spot
else
if _dist<_distmin then
_distmin=_dist
_closest=_coord
_termID=_spot.TerminalID
spot=_spot
end
end
end
end
end
return _closest,_termID,_distmin,spot
end
function COORDINATE:GetClosestFreeParkingSpot(airbase,terminaltype)
return self:GetClosestParkingSpot(airbase,terminaltype,true)
end
function COORDINATE:GetClosestOccupiedParkingSpot(airbase,terminaltype)
return self:GetClosestParkingSpot(airbase,terminaltype,false)
end
function COORDINATE:GetClosestPointToRoad(Railroad)
local roadtype="roads"
if Railroad==true then
roadtype="railroads"
end
local x,y=land.getClosestPointOnRoads(roadtype,self.x,self.z)
local coord=nil
if x and y then
local vec2={x=x,y=y}
coord=COORDINATE:NewFromVec2(vec2)
end
return coord
end
function COORDINATE:GetPathOnRoad(ToCoord,IncludeEndpoints,Railroad,MarkPath,SmokePath)
local RoadType="roads"
if Railroad==true then
RoadType="railroads"
end
local path=land.findPathOnRoads(RoadType,self.x,self.z,ToCoord.x,ToCoord.z)
local Path={}
local Way=0
if IncludeEndpoints then
Path[1]=self
end
local GotPath=true
if path then
for _i,_vec2 in ipairs(path)do
local coord=COORDINATE:NewFromVec2(_vec2)
Path[#Path+1]=coord
end
else
self:E("Path is nil. No valid path on road could be found.")
GotPath=false
end
if IncludeEndpoints then
Path[#Path+1]=ToCoord
end
if MarkPath or SmokePath then
for i,c in pairs(Path)do
local coord=c
if MarkPath then
coord:MarkToAll(string.format("Path segment %d",i))
end
if SmokePath then
if i==1 or i==#Path then
coord:SmokeBlue()
else
coord:SmokeGreen()
end
end
end
end
if#Path>=2 then
for i=1,#Path-1 do
Way=Way+Path[i+1]:Get2DDistance(Path[i])
end
else
return nil,nil,false
end
return Path,Way,GotPath
end
function COORDINATE:GetSurfaceType()
local vec2=self:GetVec2()
local surface=land.getSurfaceType(vec2)
return surface
end
function COORDINATE:IsSurfaceTypeLand()
return self:GetSurfaceType()==land.SurfaceType.LAND
end
function COORDINATE:IsSurfaceTypeLand()
return self:GetSurfaceType()==land.SurfaceType.LAND
end
function COORDINATE:IsSurfaceTypeRoad()
return self:GetSurfaceType()==land.SurfaceType.ROAD
end
function COORDINATE:IsSurfaceTypeRunway()
return self:GetSurfaceType()==land.SurfaceType.RUNWAY
end
function COORDINATE:IsSurfaceTypeShallowWater()
return self:GetSurfaceType()==land.SurfaceType.SHALLOW_WATER
end
function COORDINATE:IsSurfaceTypeWater()
return self:GetSurfaceType()==land.SurfaceType.WATER
end
function COORDINATE:Explosion(ExplosionIntensity,Delay)
ExplosionIntensity=ExplosionIntensity or 100
if Delay and Delay>0 then
self:ScheduleOnce(Delay,self.Explosion,self,ExplosionIntensity)
else
trigger.action.explosion(self:GetVec3(),ExplosionIntensity)
end
return self
end
function COORDINATE:IlluminationBomb(Power,Delay)
Power=Power or 1000
if Delay and Delay>0 then
self:ScheduleOnce(Delay,self.IlluminationBomb,self,Power)
else
trigger.action.illuminationBomb(self:GetVec3(),Power)
end
return self
end
function COORDINATE:Smoke(SmokeColor)
self:F2({SmokeColor})
trigger.action.smoke(self:GetVec3(),SmokeColor)
end
function COORDINATE:SmokeGreen()
self:F2()
self:Smoke(SMOKECOLOR.Green)
end
function COORDINATE:SmokeRed()
self:F2()
self:Smoke(SMOKECOLOR.Red)
end
function COORDINATE:SmokeWhite()
self:F2()
self:Smoke(SMOKECOLOR.White)
end
function COORDINATE:SmokeOrange()
self:F2()
self:Smoke(SMOKECOLOR.Orange)
end
function COORDINATE:SmokeBlue()
self:F2()
self:Smoke(SMOKECOLOR.Blue)
end
function COORDINATE:BigSmokeAndFire(preset,density,name)
self:F2({preset=preset,density=density})
density=density or 0.5
self.firename=name or"Fire-"..math.random(1,10000)
trigger.action.effectSmokeBig(self:GetVec3(),preset,density,self.firename)
end
function COORDINATE:StopBigSmokeAndFire(name)
name=name or self.firename
trigger.action.effectSmokeStop(name)
end
function COORDINATE:BigSmokeAndFireSmall(density,name)
self:F2({density=density})
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire,density,name)
end
function COORDINATE:BigSmokeAndFireMedium(density,name)
self:F2({density=density})
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire,density,name)
end
function COORDINATE:BigSmokeAndFireLarge(density,name)
self:F2({density=density})
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire,density,name)
end
function COORDINATE:BigSmokeAndFireHuge(density,name)
self:F2({density=density})
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire,density,name)
end
function COORDINATE:BigSmokeSmall(density,name)
self:F2({density=density})
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke,density,name)
end
function COORDINATE:BigSmokeMedium(density,name)
self:F2({density=density})
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke,density,name)
end
function COORDINATE:BigSmokeLarge(density,name)
self:F2({density=density})
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke,density,name)
end
function COORDINATE:BigSmokeHuge(density,name)
self:F2({density=density})
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke,density,name)
end
function COORDINATE:Flare(FlareColor,Azimuth)
self:F2({FlareColor})
trigger.action.signalFlare(self:GetVec3(),FlareColor,Azimuth and Azimuth or 0)
end
function COORDINATE:FlareWhite(Azimuth)
self:F2(Azimuth)
self:Flare(FLARECOLOR.White,Azimuth)
end
function COORDINATE:FlareYellow(Azimuth)
self:F2(Azimuth)
self:Flare(FLARECOLOR.Yellow,Azimuth)
end
function COORDINATE:FlareGreen(Azimuth)
self:F2(Azimuth)
self:Flare(FLARECOLOR.Green,Azimuth)
end
function COORDINATE:FlareRed(Azimuth)
self:F2(Azimuth)
self:Flare(FLARECOLOR.Red,Azimuth)
end
do
function COORDINATE:MarkToAll(MarkText,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local text=Text or""
trigger.action.markToAll(MarkID,MarkText,self:GetVec3(),ReadOnly,text)
return MarkID
end
function COORDINATE:MarkToCoalition(MarkText,Coalition,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local text=Text or""
trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),Coalition,ReadOnly,text)
return MarkID
end
function COORDINATE:MarkToCoalitionRed(MarkText,ReadOnly,Text)
return self:MarkToCoalition(MarkText,coalition.side.RED,ReadOnly,Text)
end
function COORDINATE:MarkToCoalitionBlue(MarkText,ReadOnly,Text)
return self:MarkToCoalition(MarkText,coalition.side.BLUE,ReadOnly,Text)
end
function COORDINATE:MarkToGroup(MarkText,MarkGroup,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local text=Text or""
trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),MarkGroup:GetID(),ReadOnly,text)
return MarkID
end
function COORDINATE:RemoveMark(MarkID)
trigger.action.removeMark(MarkID)
end
function COORDINATE:LineToAll(Endpoint,Coalition,Color,Alpha,LineType,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local vec3=Endpoint:GetVec3()
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
trigger.action.lineToAll(Coalition,MarkID,self:GetVec3(),vec3,Color,LineType,ReadOnly,Text or"")
return MarkID
end
function COORDINATE:CircleToAll(Radius,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local vec3=self:GetVec3()
Radius=Radius or 1000
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.15
trigger.action.circleToAll(Coalition,MarkID,vec3,Radius,Color,FillColor,LineType,ReadOnly,Text or"")
return MarkID
end
end
function COORDINATE:RectToAll(Endpoint,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local vec3=Endpoint:GetVec3()
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.15
trigger.action.rectToAll(Coalition,MarkID,self:GetVec3(),vec3,Color,FillColor,LineType,ReadOnly,Text or"")
return MarkID
end
function COORDINATE:QuadToAll(Coord2,Coord3,Coord4,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local point1=self:GetVec3()
local point2=Coord2:GetVec3()
local point3=Coord3:GetVec3()
local point4=Coord4:GetVec3()
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.15
trigger.action.quadToAll(Coalition,MarkID,point1,point2,point3,point4,Color,FillColor,LineType,ReadOnly,Text or"")
return MarkID
end
function COORDINATE:MarkupToAllFreeForm(Coordinates,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.15
local vecs={}
vecs[1]=self:GetVec3()
for i,coord in ipairs(Coordinates)do
vecs[i+1]=coord:GetVec3()
end
if#vecs<3 then
self:E("ERROR: A free form polygon needs at least three points!")
elseif#vecs==3 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==4 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==5 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==6 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==7 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==8 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==9 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==10 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==11 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],
vecs[11],
Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==12 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],
vecs[11],vecs[12],
Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==13 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],
vecs[11],vecs[12],vecs[13],
Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==14 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],
vecs[11],vecs[12],vecs[13],vecs[14],
Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==15 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],
vecs[11],vecs[12],vecs[13],vecs[14],vecs[15],
Color,FillColor,LineType,ReadOnly,Text or"")
else
self:E("ERROR: Currently a free form polygon can only have 15 points in total!")
trigger.action.markupToAll(7,Coalition,MarkID,unpack(vecs),Color,FillColor,LineType,ReadOnly,Text or"")
end
return MarkID
end
function COORDINATE:TextToAll(Text,Coalition,Color,Alpha,FillColor,FillAlpha,FontSize,ReadOnly)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.3
FontSize=FontSize or 14
trigger.action.textToAll(Coalition,MarkID,self:GetVec3(),Color,FillColor,FontSize,ReadOnly,Text or"Hello World")
return MarkID
end
function COORDINATE:ArrowToAll(Endpoint,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local vec3=Endpoint:GetVec3()
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.15
trigger.action.arrowToAll(Coalition,MarkID,vec3,self:GetVec3(),Color,FillColor,LineType,ReadOnly,Text or"")
return MarkID
end
function COORDINATE:IsLOS(ToCoordinate,Offset)
Offset=Offset or 2
local FromVec3=self:GetVec3()
FromVec3.y=FromVec3.y+Offset
local ToVec3=ToCoordinate:GetVec3()
ToVec3.y=ToVec3.y+Offset
local IsLOS=land.isVisible(FromVec3,ToVec3)
return IsLOS
end
function COORDINATE:IsInRadius(Coordinate,Radius)
local InVec2=self:GetVec2()
local Vec2=Coordinate:GetVec2()
local InRadius=UTILS.IsInRadius(InVec2,Vec2,Radius)
return InRadius
end
function COORDINATE:IsInSphere(Coordinate,Radius)
local InVec3=self:GetVec3()
local Vec3=Coordinate:GetVec3()
local InSphere=UTILS.IsInSphere(InVec3,Vec3,Radius)
return InSphere
end
function COORDINATE:GetSunriseAtDate(Day,Month,Year,InSeconds)
local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day)
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff)
if InSeconds then
return sunrise
else
return UTILS.SecondsToClock(sunrise,true)
end
end
function COORDINATE:GetSunriseAtDayOfYear(DayOfYear,InSeconds)
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff)
if InSeconds then
return sunrise
else
return UTILS.SecondsToClock(sunrise,true)
end
end
function COORDINATE:GetSunrise(InSeconds)
local DayOfYear=UTILS.GetMissionDayOfYear()
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff)
local date=UTILS.GetDCSMissionDate()
if InSeconds then
return sunrise
else
return UTILS.SecondsToClock(sunrise,true)
end
end
function COORDINATE:GetMinutesToSunrise(OnlyToday)
local time=UTILS.SecondsOfToday()
local sunrise=nil
local delta=nil
if OnlyToday then
sunrise=self:GetSunrise(true)
delta=sunrise-time
else
local DayOfYear=UTILS.GetMissionDayOfYear()+1
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff)
delta=sunrise+UTILS.SecondsToMidnight()
end
return delta/60
end
function COORDINATE:IsDay(Clock)
if Clock then
local Time=UTILS.ClockToSeconds(Clock)
local clock=UTILS.Split(Clock,"+")[1]
local DayOfYear=UTILS.GetMissionDayOfYear(Time)
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff)
local sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff)
local time=UTILS.ClockToSeconds(clock)
if time>sunrise and time<=sunset then
return true
else
return false
end
else
local sunrise=self:GetSunrise(true)
local sunset=self:GetSunset(true)
local time=UTILS.SecondsOfToday()
if time>sunrise and time<=sunset then
return true
else
return false
end
end
end
function COORDINATE:IsNight(Clock)
return not self:IsDay(Clock)
end
function COORDINATE:GetSunsetAtDate(Day,Month,Year,InSeconds)
local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day)
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff)
if InSeconds then
return sunset
else
return UTILS.SecondsToClock(sunset,true)
end
end
function COORDINATE:GetSunset(InSeconds)
local DayOfYear=UTILS.GetMissionDayOfYear()
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff)
local date=UTILS.GetDCSMissionDate()
if InSeconds then
return sunrise
else
return UTILS.SecondsToClock(sunrise,true)
end
end
function COORDINATE:GetMinutesToSunset(OnlyToday)
local time=UTILS.SecondsOfToday()
local sunset=nil
local delta=nil
if OnlyToday then
sunset=self:GetSunset(true)
delta=sunset-time
else
local DayOfYear=UTILS.GetMissionDayOfYear()+1
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff)
delta=sunset+UTILS.SecondsToMidnight()
end
return delta/60
end
function COORDINATE:ToStringBR(FromCoordinate,Settings,MagVar)
local DirectionVec3=FromCoordinate:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=self:Get2DDistance(FromCoordinate)
return"BR, "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar)
end
function COORDINATE:ToStringBRA(FromCoordinate,Settings,MagVar)
local DirectionVec3=FromCoordinate:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=FromCoordinate:Get2DDistance(self)
local Altitude=self:GetAltitudeText()
return"BRA, "..self:GetBRAText(AngleRadians,Distance,Settings,nil,MagVar)
end
function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades,SSML,Angels,Zeros)
local BRAANATO="Merged."
local currentCoord=FromCoordinate
local DirectionVec3=FromCoordinate:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local bearing=UTILS.Round(UTILS.ToDegree(AngleRadians),0)
local rangeMetres=self:Get2DDistance(currentCoord)
local rangeNM=UTILS.Round(UTILS.MetersToNM(rangeMetres),0)
local aspect=self:ToStringAspect(currentCoord)
local alt=UTILS.Round(UTILS.MetersToFeet(self.y)/1000,0)
local alttext=string.format("%d thousand",alt)
if Angels then
alttext=string.format("Angels %d",alt)
end
if alt<1 then
alttext="very low"
end
local track=UTILS.BearingToCardinal(bearing)or"North"
if rangeNM>3 then
if SSML then
if Zeros then
bearing=string.format("%03d",bearing)
local AngleDegText=string.gsub(bearing,"%d","%1 ")
AngleDegText=string.gsub(AngleDegText," $","")
AngleDegText=string.gsub(AngleDegText,"0","zero")
if aspect==""then
BRAANATO=string.format("brah %s, %d miles, %s, Track %s",AngleDegText,rangeNM,alttext,track)
else
BRAANATO=string.format("brah %s, %d miles, %s, %s, Track %s",AngleDegText,rangeNM,alttext,aspect,track)
end
else
if aspect==""then
BRAANATO=string.format("brah <say-as interpret-as='characters'>%03d</say-as>, %d miles, %s, Track %s",bearing,rangeNM,alttext,track)
else
BRAANATO=string.format("brah <say-as interpret-as='characters'>%03d</say-as>, %d miles, %s, %s, Track %s",bearing,rangeNM,alttext,aspect,track)
end
end
if Bogey and Spades then
BRAANATO=BRAANATO..", Bogey, Spades."
elseif Bogey then
BRAANATO=BRAANATO..", Bogey."
elseif Spades then
BRAANATO=BRAANATO..", Spades."
else
BRAANATO=BRAANATO.."."
end
else
if aspect==""then
BRAANATO=string.format("BRA %03d, %d miles, %s, Track %s",bearing,rangeNM,alttext,track)
else
BRAANATO=string.format("BRAA %03d, %d miles, %s, %s, Track %s",bearing,rangeNM,alttext,aspect,track)
end
if Bogey and Spades then
BRAANATO=BRAANATO..", Bogey, Spades."
elseif Bogey then
BRAANATO=BRAANATO..", Bogey."
elseif Spades then
BRAANATO=BRAANATO..", Spades."
else
BRAANATO=BRAANATO.."."
end
end
end
return BRAANATO
end
function COORDINATE:ToStringBULLS(Coalition,Settings,MagVar)
local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition))
local DirectionVec3=BullsCoordinate:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=self:Get2DDistance(BullsCoordinate)
local Altitude=self:GetAltitudeText()
return"BULLS, "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar)
end
function COORDINATE:ToStringAspect(TargetCoordinate)
local Heading=self.Heading
local DirectionVec3=self:GetDirectionVec3(TargetCoordinate)
local Angle=self:GetAngleDegrees(DirectionVec3)
if Heading then
local Aspect=Angle-Heading
if Aspect>-135 and Aspect<=-45 then
return"Flanking"
end
if Aspect>-45 and Aspect<=45 then
return"Hot"
end
if Aspect>45 and Aspect<=135 then
return"Flanking"
end
if Aspect>135 or Aspect<=-135 then
return"Cold"
end
end
return""
end
function COORDINATE:GetLLDDM()
return coord.LOtoLL(self:GetVec3())
end
function COORDINATE:ToStringLLDMS(Settings)
local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy
local lat,lon=coord.LOtoLL(self:GetVec3())
return"LL DMS "..UTILS.tostringLL(lat,lon,LL_Accuracy,true)
end
function COORDINATE:ToStringLLDDM(Settings)
local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy
local lat,lon=coord.LOtoLL(self:GetVec3())
return"LL DDM "..UTILS.tostringLL(lat,lon,LL_Accuracy,false)
end
function COORDINATE:ToStringMGRS(Settings)
local MGRS_Accuracy=Settings and Settings.MGRS_Accuracy or _SETTINGS.MGRS_Accuracy
local lat,lon=coord.LOtoLL(self:GetVec3())
local MGRS=coord.LLtoMGRS(lat,lon)
return"MGRS "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy)
end
function COORDINATE:ToStringFromRP(ReferenceCoord,ReferenceName,Controllable,Settings,MagVar)
self:F2({ReferenceCoord=ReferenceCoord,ReferenceName=ReferenceName})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
local IsAir=Controllable and Controllable:IsAirPlane()or false
if IsAir then
local DirectionVec3=ReferenceCoord:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=self:Get2DDistance(ReferenceCoord)
return"Targets are the last seen "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName
else
local DirectionVec3=ReferenceCoord:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=self:Get2DDistance(ReferenceCoord)
return"Target are located "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName
end
return nil
end
function COORDINATE:ToStringFromRPShort(ReferenceCoord,ReferenceName,Controllable,Settings,MagVar)
self:F2({ReferenceCoord=ReferenceCoord,ReferenceName=ReferenceName})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
local IsAir=Controllable and Controllable:IsAirPlane()or false
if IsAir then
local DirectionVec3=ReferenceCoord:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=self:Get2DDistance(ReferenceCoord)
return self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName
else
local DirectionVec3=ReferenceCoord:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=self:Get2DDistance(ReferenceCoord)
return self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName
end
return nil
end
function COORDINATE:ToStringA2G(Controllable,Settings,MagVar)
self:F2({Controllable=Controllable and Controllable:GetName()})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
if Settings:IsA2G_BR()then
if Controllable then
local Coordinate=Controllable:GetCoordinate()
return Controllable and self:ToStringBR(Coordinate,Settings,MagVar)or self:ToStringMGRS(Settings)
else
return self:ToStringMGRS(Settings)
end
end
if Settings:IsA2G_LL_DMS()then
return self:ToStringLLDMS(Settings)
end
if Settings:IsA2G_LL_DDM()then
return self:ToStringLLDDM(Settings)
end
if Settings:IsA2G_MGRS()then
return self:ToStringMGRS(Settings)
end
return nil
end
function COORDINATE:ToStringA2A(Controllable,Settings,MagVar)
self:F2({Controllable=Controllable and Controllable:GetName()})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
if Settings:IsA2A_BRAA()then
if Controllable then
local Coordinate=Controllable:GetCoordinate()
return self:ToStringBRA(Coordinate,Settings,MagVar)
else
return self:ToStringMGRS(Settings)
end
end
if Settings:IsA2A_BULLS()then
local Coalition=Controllable:GetCoalition()
return self:ToStringBULLS(Coalition,Settings,MagVar)
end
if Settings:IsA2A_LL_DMS()then
return self:ToStringLLDMS(Settings)
end
if Settings:IsA2A_LL_DDM()then
return self:ToStringLLDDM(Settings)
end
if Settings:IsA2A_MGRS()then
return self:ToStringMGRS(Settings)
end
return nil
end
function COORDINATE:ToString(Controllable,Settings,Task)
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
local ModeA2A=nil
if Task then
if Task:IsInstanceOf(TASK_A2A)then
ModeA2A=true
else
if Task:IsInstanceOf(TASK_A2G)then
ModeA2A=false
else
if Task:IsInstanceOf(TASK_CARGO)then
ModeA2A=false
end
if Task:IsInstanceOf(TASK_CAPTURE_ZONE)then
ModeA2A=false
end
end
end
end
if ModeA2A==nil then
local IsAir=Controllable and(Controllable:IsAirPlane()or Controllable:IsHelicopter())or false
if IsAir then
ModeA2A=true
else
ModeA2A=false
end
end
if ModeA2A==true then
return self:ToStringA2A(Controllable,Settings)
else
return self:ToStringA2G(Controllable,Settings)
end
return nil
end
function COORDINATE:ToStringPressure(Controllable,Settings)
self:F2({Controllable=Controllable and Controllable:GetName()})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
return self:GetPressureText(nil,Settings)
end
function COORDINATE:ToStringWind(Controllable,Settings)
self:F2({Controllable=Controllable and Controllable:GetName()})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
return self:GetWindText(nil,Settings)
end
function COORDINATE:ToStringTemperature(Controllable,Settings)
self:F2({Controllable=Controllable and Controllable:GetName()})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
return self:GetTemperatureText(nil,Settings)
end
end
do
POINT_VEC3={
ClassName="POINT_VEC3",
Metric=true,
RoutePointAltType={
BARO="BARO",
},
RoutePointType={
TakeOffParking="TakeOffParking",
TurningPoint="Turning Point",
},
RoutePointAction={
FromParkingArea="From Parking Area",
TurningPoint="Turning Point",
},
}
function POINT_VEC3:New(x,y,z)
local self=BASE:Inherit(self,COORDINATE:New(x,y,z))
self:F2(self)
return self
end
function POINT_VEC3:NewFromVec2(Vec2,LandHeightAdd)
local self=BASE:Inherit(self,COORDINATE:NewFromVec2(Vec2,LandHeightAdd))
self:F2(self)
return self
end
function POINT_VEC3:NewFromVec3(Vec3)
local self=BASE:Inherit(self,COORDINATE:NewFromVec3(Vec3))
self:F2(self)
return self
end
function POINT_VEC3:GetX()
return self.x
end
function POINT_VEC3:GetY()
return self.y
end
function POINT_VEC3:GetZ()
return self.z
end
function POINT_VEC3:SetX(x)
self.x=x
return self
end
function POINT_VEC3:SetY(y)
self.y=y
return self
end
function POINT_VEC3:SetZ(z)
self.z=z
return self
end
function POINT_VEC3:AddX(x)
self.x=self.x+x
return self
end
function POINT_VEC3:AddY(y)
self.y=self.y+y
return self
end
function POINT_VEC3:AddZ(z)
self.z=self.z+z
return self
end
function POINT_VEC3:GetRandomPointVec3InRadius(OuterRadius,InnerRadius)
return POINT_VEC3:NewFromVec3(self:GetRandomVec3InRadius(OuterRadius,InnerRadius))
end
end
do
POINT_VEC2={
ClassName="POINT_VEC2",
}
function POINT_VEC2:New(x,y,LandHeightAdd)
local LandHeight=land.getHeight({["x"]=x,["y"]=y})
LandHeightAdd=LandHeightAdd or 0
LandHeight=LandHeight+LandHeightAdd
local self=BASE:Inherit(self,COORDINATE:New(x,LandHeight,y))
self:F2(self)
return self
end
function POINT_VEC2:NewFromVec2(Vec2,LandHeightAdd)
local LandHeight=land.getHeight(Vec2)
LandHeightAdd=LandHeightAdd or 0
LandHeight=LandHeight+LandHeightAdd
local self=BASE:Inherit(self,COORDINATE:NewFromVec2(Vec2,LandHeightAdd))
self:F2(self)
return self
end
function POINT_VEC2:NewFromVec3(Vec3)
local self=BASE:Inherit(self,COORDINATE:NewFromVec3(Vec3))
self:F2(self)
return self
end
function POINT_VEC2:GetX()
return self.x
end
function POINT_VEC2:GetY()
return self.z
end
function POINT_VEC2:SetX(x)
self.x=x
return self
end
function POINT_VEC2:SetY(y)
self.z=y
return self
end
function POINT_VEC2:GetLat()
return self.x
end
function POINT_VEC2:SetLat(x)
self.x=x
return self
end
function POINT_VEC2:GetLon()
return self.z
end
function POINT_VEC2:SetLon(z)
self.z=z
return self
end
function POINT_VEC2:GetAlt()
return self.y~=0 or land.getHeight({x=self.x,y=self.z})
end
function POINT_VEC2:SetAlt(Altitude)
self.y=Altitude or land.getHeight({x=self.x,y=self.z})
return self
end
function POINT_VEC2:AddX(x)
self.x=self.x+x
return self
end
function POINT_VEC2:AddY(y)
self.z=self.z+y
return self
end
function POINT_VEC2:AddAlt(Altitude)
self.y=land.getHeight({x=self.x,y=self.z})+Altitude or 0
return self
end
function POINT_VEC2:GetRandomPointVec2InRadius(OuterRadius,InnerRadius)
self:F2({OuterRadius,InnerRadius})
return POINT_VEC2:NewFromVec2(self:GetRandomVec2InRadius(OuterRadius,InnerRadius))
end
function POINT_VEC2:DistanceFromPointVec2(PointVec2Reference)
self:F2(PointVec2Reference)
local Distance=((PointVec2Reference.x-self.x)^2+(PointVec2Reference.z-self.z)^2)^0.5
self:T2(Distance)
return Distance
end
end
do
VELOCITY={
ClassName="VELOCITY",
}
function VELOCITY:New(VelocityMps)
local self=BASE:Inherit(self,BASE:New())
self:F({})
self.Velocity=VelocityMps
return self
end
function VELOCITY:Set(VelocityMps)
self.Velocity=VelocityMps
return self
end
function VELOCITY:Get()
return self.Velocity
end
function VELOCITY:SetKmph(VelocityKmph)
self.Velocity=UTILS.KmphToMps(VelocityKmph)
return self
end
function VELOCITY:GetKmph()
return UTILS.MpsToKmph(self.Velocity)
end
function VELOCITY:SetMiph(VelocityMiph)
self.Velocity=UTILS.MiphToMps(VelocityMiph)
return self
end
function VELOCITY:GetMiph()
return UTILS.MpsToMiph(self.Velocity)
end
function VELOCITY:GetText(Settings)
local Settings=Settings or _SETTINGS
if self.Velocity~=0 then
if Settings:IsMetric()then
return string.format("%d km/h",UTILS.MpsToKmph(self.Velocity))
else
return string.format("%d mi/h",UTILS.MpsToMiph(self.Velocity))
end
else
return"stationary"
end
end
function VELOCITY:ToString(VelocityGroup,Settings)
self:F({Group=VelocityGroup and VelocityGroup:GetName()})
local Settings=Settings or(VelocityGroup and _DATABASE:GetPlayerSettings(VelocityGroup:GetPlayerName()))or _SETTINGS
return self:GetText(Settings)
end
end
do
VELOCITY_POSITIONABLE={
ClassName="VELOCITY_POSITIONABLE",
}
function VELOCITY_POSITIONABLE:New(Positionable)
local self=BASE:Inherit(self,VELOCITY:New())
self:F({})
self.Positionable=Positionable
return self
end
function VELOCITY_POSITIONABLE:Get()
return self.Positionable:GetVelocityMPS()or 0
end
function VELOCITY_POSITIONABLE:GetKmph()
return UTILS.MpsToKmph(self.Positionable:GetVelocityMPS()or 0)
end
function VELOCITY_POSITIONABLE:GetMiph()
return UTILS.MpsToMiph(self.Positionable:GetVelocityMPS()or 0)
end
function VELOCITY_POSITIONABLE:ToString()
self:F({Group=self.Positionable and self.Positionable:GetName()})
local Settings=Settings or(self.Positionable and _DATABASE:GetPlayerSettings(self.Positionable:GetPlayerName()))or _SETTINGS
self.Velocity=self.Positionable:GetVelocityMPS()
return self:GetText(Settings)
end
end
MESSAGE={
ClassName="MESSAGE",
MessageCategory=0,
MessageID=0,
}
MESSAGE.Type={
Update="Update",
Information="Information",
Briefing="Briefing Report",
Overview="Overview Report",
Detailed="Detailed Report",
}
function MESSAGE:New(MessageText,MessageDuration,MessageCategory,ClearScreen)
local self=BASE:Inherit(self,BASE:New())
self:F({MessageText,MessageDuration,MessageCategory})
self.MessageType=nil
if MessageCategory and MessageCategory~=""then
if MessageCategory:sub(-1)~="\n"then
self.MessageCategory=MessageCategory..": "
else
self.MessageCategory=MessageCategory:sub(1,-2)..":\n"
end
else
self.MessageCategory=""
end
self.ClearScreen=false
if ClearScreen~=nil then
self.ClearScreen=ClearScreen
end
self.MessageDuration=MessageDuration or 5
self.MessageTime=timer.getTime()
self.MessageText=MessageText:gsub("^\n","",1):gsub("\n$","",1)
self.MessageSent=false
self.MessageGroup=false
self.MessageCoalition=false
return self
end
function MESSAGE:NewType(MessageText,MessageType,ClearScreen)
local self=BASE:Inherit(self,BASE:New())
self:F({MessageText})
self.MessageType=MessageType
self.ClearScreen=false
if ClearScreen~=nil then
self.ClearScreen=ClearScreen
end
self.MessageTime=timer.getTime()
self.MessageText=MessageText:gsub("^\n","",1):gsub("\n$","",1)
return self
end
function MESSAGE:Clear()
self:F()
self.ClearScreen=true
return self
end
function MESSAGE:ToClient(Client,Settings)
self:F(Client)
if Client and Client:GetClientGroupID()then
if self.MessageType then
local Settings=Settings or(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS
self.MessageDuration=Settings:GetMessageTime(self.MessageType)
self.MessageCategory=""
end
local Unit=Client:GetClient()
if self.MessageDuration~=0 then
local ClientGroupID=Client:GetClientGroupID()
self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
trigger.action.outTextForUnit(Unit:GetID(),self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
end
end
return self
end
function MESSAGE:ToGroup(Group,Settings)
self:F(Group.GroupName)
if Group then
if self.MessageType then
local Settings=Settings or(Group and _DATABASE:GetPlayerSettings(Group:GetPlayerName()))or _SETTINGS
self.MessageDuration=Settings:GetMessageTime(self.MessageType)
self.MessageCategory=""
end
if self.MessageDuration~=0 then
self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
trigger.action.outTextForGroup(Group:GetID(),self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
end
end
return self
end
function MESSAGE:ToUnit(Unit,Settings)
self:F(Unit.IdentifiableName)
if Unit then
if self.MessageType then
local Settings=Settings or(Unit and _DATABASE:GetPlayerSettings(Unit:GetPlayerName()))or _SETTINGS
self.MessageDuration=Settings:GetMessageTime(self.MessageType)
self.MessageCategory=""
end
if self.MessageDuration~=0 then
self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
trigger.action.outTextForUnit(Unit:GetID(),self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
end
end
return self
end
function MESSAGE:ToCountry(Country,Settings)
self:F(Country)
if Country then
if self.MessageType then
local Settings=Settings or _SETTINGS
self.MessageDuration=Settings:GetMessageTime(self.MessageType)
self.MessageCategory=""
end
if self.MessageDuration~=0 then
self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
trigger.action.outTextForCountry(Country,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
end
end
return self
end
function MESSAGE:ToCountryIf(Country,Condition,Settings)
self:F(Country)
if Country and Condition==true then
self:ToCountry(Country,Settings)
end
return self
end
function MESSAGE:ToBlue()
self:F()
self:ToCoalition(coalition.side.BLUE)
return self
end
function MESSAGE:ToRed()
self:F()
self:ToCoalition(coalition.side.RED)
return self
end
function MESSAGE:ToCoalition(CoalitionSide,Settings)
self:F(CoalitionSide)
if self.MessageType then
local Settings=Settings or _SETTINGS
self.MessageDuration=Settings:GetMessageTime(self.MessageType)
self.MessageCategory=""
end
if CoalitionSide then
if self.MessageDuration~=0 then
self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
trigger.action.outTextForCoalition(CoalitionSide,self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
end
end
return self
end
function MESSAGE:ToCoalitionIf(CoalitionSide,Condition)
self:F(CoalitionSide)
if Condition and Condition==true then
self:ToCoalition(CoalitionSide)
end
return self
end
function MESSAGE:ToAll(Settings)
self:F()
if self.MessageType then
local Settings=Settings or _SETTINGS
self.MessageDuration=Settings:GetMessageTime(self.MessageType)
self.MessageCategory=""
end
if self.MessageDuration~=0 then
self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
trigger.action.outText(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
end
return self
end
function MESSAGE:ToAllIf(Condition)
if Condition and Condition==true then
self:ToAll()
end
return self
end
function MESSAGE:ToLog()
env.info(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""))
return self
end
function MESSAGE:ToLogIf(Condition)
if Condition and Condition==true then
env.info(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""))
end
return self
end
do
FSM={
ClassName="FSM",
}
function FSM:New()
self=BASE:Inherit(self,BASE:New())
self.options=options or{}
self.options.subs=self.options.subs or{}
self.current=self.options.initial or'none'
self.Events={}
self.subs={}
self.endstates={}
self.Scores={}
self._StartState="none"
self._Transitions={}
self._Processes={}
self._EndStates={}
self._Scores={}
self._EventSchedules={}
self.CallScheduler=SCHEDULER:New(self)
return self
end
function FSM:SetStartState(State)
self._StartState=State
self.current=State
end
function FSM:GetStartState()
return self._StartState or{}
end
function FSM:AddTransition(From,Event,To)
local Transition={}
Transition.From=From
Transition.Event=Event
Transition.To=To
self._Transitions[Transition]=Transition
self:_eventmap(self.Events,Transition)
end
function FSM:GetTransitions()
return self._Transitions or{}
end
function FSM:AddProcess(From,Event,Process,ReturnEvents)
local Sub={}
Sub.From=From
Sub.Event=Event
Sub.fsm=Process
Sub.StartEvent="Start"
Sub.ReturnEvents=ReturnEvents
self._Processes[Sub]=Sub
self:_submap(self.subs,Sub,nil)
self:AddTransition(From,Event,From)
return Process
end
function FSM:GetProcesses()
self:F({Processes=self._Processes})
return self._Processes or{}
end
function FSM:GetProcess(From,Event)
for ProcessID,Process in pairs(self:GetProcesses())do
if Process.From==From and Process.Event==Event then
return Process.fsm
end
end
error("Sub-Process from state "..From.." with event "..Event.." not found!")
end
function FSM:SetProcess(From,Event,Fsm)
for ProcessID,Process in pairs(self:GetProcesses())do
if Process.From==From and Process.Event==Event then
Process.fsm=Fsm
return true
end
end
error("Sub-Process from state "..From.." with event "..Event.." not found!")
end
function FSM:AddEndState(State)
self._EndStates[State]=State
self.endstates[State]=State
end
function FSM:GetEndStates()
return self._EndStates or{}
end
function FSM:AddScore(State,ScoreText,Score)
self:F({State,ScoreText,Score})
self._Scores[State]=self._Scores[State]or{}
self._Scores[State].ScoreText=ScoreText
self._Scores[State].Score=Score
return self
end
function FSM:AddScoreProcess(From,Event,State,ScoreText,Score)
self:F({From,Event,State,ScoreText,Score})
local Process=self:GetProcess(From,Event)
Process._Scores[State]=Process._Scores[State]or{}
Process._Scores[State].ScoreText=ScoreText
Process._Scores[State].Score=Score
return Process
end
function FSM:GetScores()
return self._Scores or{}
end
function FSM:GetSubs()
return self.options.subs
end
function FSM:LoadCallBacks(CallBackTable)
for name,callback in pairs(CallBackTable or{})do
self[name]=callback
end
end
function FSM:_eventmap(Events,EventStructure)
local Event=EventStructure.Event
local __Event="__"..EventStructure.Event
self[Event]=self[Event]or self:_create_transition(Event)
self[__Event]=self[__Event]or self:_delayed_transition(Event)
Events[Event]=self.Events[Event]or{map={}}
self:_add_to_map(Events[Event].map,EventStructure)
end
function FSM:_submap(subs,sub,name)
subs[sub.From]=subs[sub.From]or{}
subs[sub.From][sub.Event]=subs[sub.From][sub.Event]or{}
subs[sub.From][sub.Event][sub]={}
subs[sub.From][sub.Event][sub].fsm=sub.fsm
subs[sub.From][sub.Event][sub].StartEvent=sub.StartEvent
subs[sub.From][sub.Event][sub].ReturnEvents=sub.ReturnEvents or{}
subs[sub.From][sub.Event][sub].name=name
subs[sub.From][sub.Event][sub].fsmparent=self
end
function FSM:_call_handler(step,trigger,params,EventName)
local handler=step..trigger
if self[handler]then
self._EventSchedules[EventName]=nil
local ErrorHandler=function(errmsg)
env.info("Error in SCHEDULER function:"..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
local Result,Value=xpcall(function()
return self[handler](self,unpack(params))
end,ErrorHandler)
return Value
end
end
function FSM._handler(self,EventName,...)
local Can,To=self:can(EventName)
if To=="*"then
To=self.current
end
if Can then
local From=self.current
local Params={From,EventName,To,...}
if self["onleave"..From]or
self["OnLeave"..From]or
self["onbefore"..EventName]or
self["OnBefore"..EventName]or
self["onafter"..EventName]or
self["OnAfter"..EventName]or
self["onenter"..To]or
self["OnEnter"..To]then
if self:_call_handler("onbefore",EventName,Params,EventName)==false then
self:T("*** FSM ***    Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** onbefore"..EventName)
return false
else
if self:_call_handler("OnBefore",EventName,Params,EventName)==false then
self:T("*** FSM ***    Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** OnBefore"..EventName)
return false
else
if self:_call_handler("onleave",From,Params,EventName)==false then
self:T("*** FSM ***    Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** onleave"..From)
return false
else
if self:_call_handler("OnLeave",From,Params,EventName)==false then
self:T("*** FSM ***    Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** OnLeave"..From)
return false
end
end
end
end
else
local ClassName=self:GetClassName()
if ClassName=="FSM"then
self:T("*** FSM ***    Transit *** "..self.current.." --> "..EventName.." --> "..To)
end
if ClassName=="FSM_TASK"then
self:T("*** FSM ***    Transit *** "..self.current.." --> "..EventName.." --> "..To.." *** Task: "..self.TaskName)
end
if ClassName=="FSM_CONTROLLABLE"then
self:T("*** FSM ***    Transit *** "..self.current.." --> "..EventName.." --> "..To.." *** TaskUnit: "..self.Controllable.ControllableName.." *** ")
end
if ClassName=="FSM_PROCESS"then
self:T("*** FSM ***    Transit *** "..self.current.." --> "..EventName.." --> "..To.." *** Task: "..self.Task:GetName()..", TaskUnit: "..self.Controllable.ControllableName.." *** ")
end
end
self.current=To
local execute=true
local subtable=self:_gosub(From,EventName)
for _,sub in pairs(subtable)do
self:T("*** FSM ***    Sub *** "..sub.StartEvent)
sub.fsm.fsmparent=self
sub.fsm.ReturnEvents=sub.ReturnEvents
sub.fsm[sub.StartEvent](sub.fsm)
execute=false
end
local fsmparent,Event=self:_isendstate(To)
if fsmparent and Event then
self:T("*** FSM ***    End *** "..Event)
self:_call_handler("onenter",To,Params,EventName)
self:_call_handler("OnEnter",To,Params,EventName)
self:_call_handler("onafter",EventName,Params,EventName)
self:_call_handler("OnAfter",EventName,Params,EventName)
self:_call_handler("onstate","change",Params,EventName)
fsmparent[Event](fsmparent)
execute=false
end
if execute then
self:_call_handler("onafter",EventName,Params,EventName)
self:_call_handler("OnAfter",EventName,Params,EventName)
self:_call_handler("onenter",To,Params,EventName)
self:_call_handler("OnEnter",To,Params,EventName)
self:_call_handler("onstate","change",Params,EventName)
end
else
self:T("*** FSM *** NO Transition *** "..self.current.." --> "..EventName.." -->  ? ")
end
return nil
end
function FSM:_delayed_transition(EventName)
return function(self,DelaySeconds,...)
self:T3("Delayed Event: "..EventName)
local CallID=0
if DelaySeconds~=nil then
if DelaySeconds<0 then
DelaySeconds=math.abs(DelaySeconds)
if not self._EventSchedules[EventName]then
CallID=self.CallScheduler:Schedule(self,self._handler,{EventName,...},DelaySeconds or 1,nil,nil,nil,4,true)
self._EventSchedules[EventName]=CallID
self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec SCHEDULED with CallID=%s",EventName,DelaySeconds,tostring(CallID)))
else
self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec CANCELLED as we already have such an event in the queue.",EventName,DelaySeconds))
end
else
CallID=self.CallScheduler:Schedule(self,self._handler,{EventName,...},DelaySeconds or 1,nil,nil,nil,4,true)
self:T2(string.format("Event %s delayed by %.3f sec SCHEDULED with CallID=%s",EventName,DelaySeconds,tostring(CallID)))
end
else
error("FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this.")
end
end
end
function FSM:_create_transition(EventName)
return function(self,...)
return self._handler(self,EventName,...)
end
end
function FSM:_gosub(ParentFrom,ParentEvent)
local fsmtable={}
if self.subs[ParentFrom]and self.subs[ParentFrom][ParentEvent]then
return self.subs[ParentFrom][ParentEvent]
else
return{}
end
end
function FSM:_isendstate(Current)
local FSMParent=self.fsmparent
if FSMParent and self.endstates[Current]then
FSMParent.current=Current
local ParentFrom=FSMParent.current
local Event=self.ReturnEvents[Current]
if Event then
return FSMParent,Event
else
end
end
return nil
end
function FSM:_add_to_map(Map,Event)
self:F3({Map,Event})
if type(Event.From)=='string'then
Map[Event.From]=Event.To
else
for _,From in ipairs(Event.From)do
Map[From]=Event.To
end
end
end
function FSM:GetState()
return self.current
end
function FSM:GetCurrentState()
return self.current
end
function FSM:Is(State)
return self.current==State
end
function FSM:is(state)
return self.current==state
end
function FSM:can(e)
local Event=self.Events[e]
local To=Event and Event.map[self.current]or Event.map['*']
return To~=nil,To
end
function FSM:cannot(e)
return not self:can(e)
end
end
do
FSM_CONTROLLABLE={
ClassName="FSM_CONTROLLABLE",
}
function FSM_CONTROLLABLE:New(Controllable)
local self=BASE:Inherit(self,FSM:New())
if Controllable then
self:SetControllable(Controllable)
end
self:AddTransition("*","Stop","Stopped")
return self
end
function FSM_CONTROLLABLE:OnAfterStop(Controllable,From,Event,To)
self.CallScheduler:Clear()
end
function FSM_CONTROLLABLE:SetControllable(FSMControllable)
self.Controllable=FSMControllable
end
function FSM_CONTROLLABLE:GetControllable()
return self.Controllable
end
function FSM_CONTROLLABLE:_call_handler(step,trigger,params,EventName)
local handler=step..trigger
local ErrorHandler=function(errmsg)
env.info("Error in SCHEDULER function:"..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
if self[handler]then
self:T("*** FSM ***    "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3].." *** TaskUnit: "..self.Controllable:GetName())
self._EventSchedules[EventName]=nil
local Result,Value=xpcall(function()
return self[handler](self,self.Controllable,unpack(params))
end,ErrorHandler)
return Value
end
end
end
do
FSM_PROCESS={ClassName="FSM_PROCESS"}
function FSM_PROCESS:New(Controllable,Task)
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
self:Assign(Controllable,Task)
return self
end
function FSM_PROCESS:Init(FsmProcess)
self:T("No Initialisation")
end
function FSM_PROCESS:_call_handler(step,trigger,params,EventName)
local handler=step..trigger
local ErrorHandler=function(errmsg)
env.info("Error in FSM_PROCESS call handler:"..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
if self[handler]then
if handler~="onstatechange"then
self:T("*** FSM ***    "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3].." *** Task: "..self.Task:GetName()..", TaskUnit: "..self.Controllable:GetName())
end
self._EventSchedules[EventName]=nil
local Result,Value
if self.Controllable and self.Controllable:IsAlive()==true then
Result,Value=xpcall(function()
return self[handler](self,self.Controllable,self.Task,unpack(params))
end,ErrorHandler)
end
return Value
end
end
function FSM_PROCESS:Copy(Controllable,Task)
local NewFsm=self:New(Controllable,Task)
NewFsm:Assign(Controllable,Task)
NewFsm:Init(self)
NewFsm:SetStartState(self:GetStartState())
for TransitionID,Transition in pairs(self:GetTransitions())do
NewFsm:AddTransition(Transition.From,Transition.Event,Transition.To)
end
for ProcessID,Process in pairs(self:GetProcesses())do
local FsmProcess=NewFsm:AddProcess(Process.From,Process.Event,Process.fsm:Copy(Controllable,Task),Process.ReturnEvents)
end
for EndStateID,EndState in pairs(self:GetEndStates())do
NewFsm:AddEndState(EndState)
end
for ScoreID,Score in pairs(self:GetScores())do
NewFsm:AddScore(ScoreID,Score.ScoreText,Score.Score)
end
return NewFsm
end
function FSM_PROCESS:Remove()
self:F({self:GetClassNameAndID()})
self:F("Clearing Schedules")
self.CallScheduler:Clear()
for ProcessID,Process in pairs(self:GetProcesses())do
if Process.fsm then
Process.fsm:Remove()
Process.fsm=nil
end
end
return self
end
function FSM_PROCESS:SetTask(Task)
self.Task=Task
return self
end
function FSM_PROCESS:GetTask()
return self.Task
end
function FSM_PROCESS:GetMission()
return self.Task.Mission
end
function FSM_PROCESS:GetCommandCenter()
return self:GetTask():GetMission():GetCommandCenter()
end
function FSM_PROCESS:Message(Message)
self:F({Message=Message})
local CC=self:GetCommandCenter()
local TaskGroup=self.Controllable:GetGroup()
local PlayerName=self.Controllable:GetPlayerName()
PlayerName=PlayerName and" ("..PlayerName..")"or""
local Callsign=self.Controllable:GetCallsign()
local Prefix=Callsign and" @ "..Callsign..PlayerName or""
Message=Prefix..": "..Message
CC:MessageToGroup(Message,TaskGroup)
end
function FSM_PROCESS:Assign(ProcessUnit,Task)
self:SetControllable(ProcessUnit)
self:SetTask(Task)
return self
end
function FSM_PROCESS:onenterFailed(ProcessUnit,Task,From,Event,To)
self:T("*** FSM ***    Failed *** "..Task:GetName().."/"..ProcessUnit:GetName().." *** "..From.." --> "..Event.." --> "..To)
self.Task:Fail()
end
function FSM_PROCESS:onstatechange(ProcessUnit,Task,From,Event,To)
if From~=To then
self:T("*** FSM ***    Change *** "..Task:GetName().."/"..ProcessUnit:GetName().." *** "..From.." --> "..Event.." --> "..To)
end
if self._Scores[To]then
local Task=self.Task
local Scoring=Task:GetScoring()
if Scoring then
Scoring:_AddMissionTaskScore(Task.Mission,ProcessUnit,self._Scores[To].ScoreText,self._Scores[To].Score)
end
end
end
end
do
FSM_TASK={
ClassName="FSM_TASK",
}
function FSM_TASK:New(TaskName)
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
self["onstatechange"]=self.OnStateChange
self.TaskName=TaskName
return self
end
function FSM_TASK:_call_handler(step,trigger,params,EventName)
local handler=step..trigger
local ErrorHandler=function(errmsg)
env.info("Error in SCHEDULER function:"..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
if self[handler]then
self:T("*** FSM ***    "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3].." *** Task: "..self.TaskName)
self._EventSchedules[EventName]=nil
local Result,Value=xpcall(function()
return self[handler](self,unpack(params))
end,ErrorHandler)
return Value
end
end
end
do
FSM_SET={
ClassName="FSM_SET",
}
function FSM_SET:New(FSMSet)
self=BASE:Inherit(self,FSM:New())
if FSMSet then
self:Set(FSMSet)
end
return self
end
function FSM_SET:Set(FSMSet)
self:F(FSMSet)
self.Set=FSMSet
end
function FSM_SET:Get()
return self.Set
end
function FSM_SET:_call_handler(step,trigger,params,EventName)
local handler=step..trigger
if self[handler]then
self:T("*** FSM ***    "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3])
self._EventSchedules[EventName]=nil
return self[handler](self,self.Set,unpack(params))
end
end
end
SPAWN={
ClassName="SPAWN",
SpawnTemplatePrefix=nil,
SpawnAliasPrefix=nil,
}
SPAWN.Takeoff={
Air=1,
Runway=2,
Hot=3,
Cold=4,
}
function SPAWN:New(SpawnTemplatePrefix)
local self=BASE:Inherit(self,BASE:New())
self:F({SpawnTemplatePrefix})
local TemplateGroup=GROUP:FindByName(SpawnTemplatePrefix)
if TemplateGroup then
self.SpawnTemplatePrefix=SpawnTemplatePrefix
self.SpawnIndex=0
self.SpawnCount=0
self.AliveUnits=0
self.SpawnIsScheduled=false
self.SpawnTemplate=self._GetTemplate(self,SpawnTemplatePrefix)
self.Repeat=false
self.UnControlled=false
self.SpawnInitLimit=false
self.SpawnMaxUnitsAlive=0
self.SpawnMaxGroups=0
self.SpawnRandomize=false
self.SpawnVisible=false
self.AIOnOff=true
self.SpawnUnControlled=false
self.SpawnInitKeepUnitNames=false
self.DelayOnOff=false
self.SpawnGrouping=nil
self.SpawnInitLivery=nil
self.SpawnInitSkill=nil
self.SpawnInitFreq=nil
self.SpawnInitModu=nil
self.SpawnInitRadio=nil
self.SpawnInitModex=nil
self.SpawnInitAirbase=nil
self.TweakedTemplate=false
self.SpawnGroups={}
else
error("SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'")
end
self:SetEventPriority(5)
self.SpawnHookScheduler=SCHEDULER:New(nil)
return self
end
function SPAWN:NewWithAlias(SpawnTemplatePrefix,SpawnAliasPrefix)
local self=BASE:Inherit(self,BASE:New())
self:F({SpawnTemplatePrefix,SpawnAliasPrefix})
local TemplateGroup=GROUP:FindByName(SpawnTemplatePrefix)
if TemplateGroup then
self.SpawnTemplatePrefix=SpawnTemplatePrefix
self.SpawnAliasPrefix=SpawnAliasPrefix
self.SpawnIndex=0
self.SpawnCount=0
self.AliveUnits=0
self.SpawnIsScheduled=false
self.SpawnTemplate=self._GetTemplate(self,SpawnTemplatePrefix)
self.Repeat=false
self.UnControlled=false
self.SpawnInitLimit=false
self.SpawnMaxUnitsAlive=0
self.SpawnMaxGroups=0
self.SpawnRandomize=false
self.SpawnVisible=false
self.AIOnOff=true
self.SpawnUnControlled=false
self.SpawnInitKeepUnitNames=false
self.DelayOnOff=false
self.SpawnGrouping=nil
self.SpawnInitLivery=nil
self.SpawnInitSkill=nil
self.SpawnInitFreq=nil
self.SpawnInitModu=nil
self.SpawnInitRadio=nil
self.SpawnInitModex=nil
self.SpawnInitAirbase=nil
self.TweakedTemplate=false
self.SpawnGroups={}
else
error("SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'")
end
self:SetEventPriority(5)
self.SpawnHookScheduler=SCHEDULER:New(nil)
return self
end
function SPAWN:NewFromTemplate(SpawnTemplate,SpawnTemplatePrefix,SpawnAliasPrefix)
local self=BASE:Inherit(self,BASE:New())
self:F({SpawnTemplate,SpawnTemplatePrefix,SpawnAliasPrefix})
if SpawnAliasPrefix==nil or SpawnAliasPrefix==""then
BASE:I("ERROR: in function NewFromTemplate, required parameter SpawnAliasPrefix is not set")
return nil
end
if SpawnTemplate then
self.SpawnTemplate=SpawnTemplate
self.SpawnTemplatePrefix=SpawnTemplatePrefix
self.SpawnAliasPrefix=SpawnAliasPrefix
self.SpawnIndex=0
self.SpawnCount=0
self.AliveUnits=0
self.SpawnIsScheduled=false
self.Repeat=false
self.UnControlled=false
self.SpawnInitLimit=false
self.SpawnMaxUnitsAlive=0
self.SpawnMaxGroups=0
self.SpawnRandomize=false
self.SpawnVisible=false
self.AIOnOff=true
self.SpawnUnControlled=false
self.SpawnInitKeepUnitNames=false
self.DelayOnOff=false
self.Grouping=nil
self.SpawnInitLivery=nil
self.SpawnInitSkill=nil
self.SpawnInitFreq=nil
self.SpawnInitModu=nil
self.SpawnInitRadio=nil
self.SpawnInitModex=nil
self.SpawnInitAirbase=nil
self.TweakedTemplate=true
self.SpawnGroups={}
else
error("There is no template provided for SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'")
end
self:SetEventPriority(5)
self.SpawnHookScheduler=SCHEDULER:New(nil)
return self
end
function SPAWN:InitLimit(SpawnMaxUnitsAlive,SpawnMaxGroups)
self:F({self.SpawnTemplatePrefix,SpawnMaxUnitsAlive,SpawnMaxGroups})
self.SpawnInitLimit=true
self.SpawnMaxUnitsAlive=SpawnMaxUnitsAlive
self.SpawnMaxGroups=SpawnMaxGroups
for SpawnGroupID=1,self.SpawnMaxGroups do
self:_InitializeSpawnGroups(SpawnGroupID)
end
return self
end
function SPAWN:InitKeepUnitNames(KeepUnitNames)
self:F()
self.SpawnInitKeepUnitNames=KeepUnitNames or true
return self
end
function SPAWN:InitLateActivated(LateActivated)
self:F()
self.LateActivated=LateActivated or true
return self
end
function SPAWN:InitAirbase(AirbaseName,Takeoff,TerminalType)
self:F()
self.SpawnInitAirbase=AIRBASE:FindByName(AirbaseName)
self.SpawnInitTakeoff=Takeoff or SPAWN.Takeoff.Hot
self.SpawnInitTerminalType=TerminalType
return self
end
function SPAWN:InitHeading(HeadingMin,HeadingMax)
self:F()
self.SpawnInitHeadingMin=HeadingMin
self.SpawnInitHeadingMax=HeadingMax
return self
end
function SPAWN:InitGroupHeading(HeadingMin,HeadingMax,unitVar)
self:F({HeadingMin=HeadingMin,HeadingMax=HeadingMax,unitVar=unitVar})
self.SpawnInitGroupHeadingMin=HeadingMin
self.SpawnInitGroupHeadingMax=HeadingMax
self.SpawnInitGroupUnitVar=unitVar
return self
end
function SPAWN:InitCoalition(Coalition)
self:F({coalition=Coalition})
self.SpawnInitCoalition=Coalition
return self
end
function SPAWN:InitCountry(Country)
self:F()
self.SpawnInitCountry=Country
return self
end
function SPAWN:InitCategory(Category)
self:F()
self.SpawnInitCategory=Category
return self
end
function SPAWN:InitLivery(Livery)
self:F({livery=Livery})
self.SpawnInitLivery=Livery
return self
end
function SPAWN:InitSkill(Skill)
self:F({skill=Skill})
if Skill:lower()=="average"then
self.SpawnInitSkill="Average"
elseif Skill:lower()=="good"then
self.SpawnInitSkill="Good"
elseif Skill:lower()=="excellent"then
self.SpawnInitSkill="Excellent"
elseif Skill:lower()=="random"then
self.SpawnInitSkill="Random"
else
self.SpawnInitSkill="High"
end
return self
end
function SPAWN:InitRadioCommsOnOff(switch)
self:F({switch=switch})
self.SpawnInitRadio=switch or true
return self
end
function SPAWN:InitRadioFrequency(frequency)
self:F({frequency=frequency})
self.SpawnInitFreq=frequency
return self
end
function SPAWN:InitRadioModulation(modulation)
self:F({modulation=modulation})
if modulation and modulation:lower()=="fm"then
self.SpawnInitModu=radio.modulation.FM
else
self.SpawnInitModu=radio.modulation.AM
end
return self
end
function SPAWN:InitModex(modex)
if modex then
self.SpawnInitModex=tonumber(modex)
end
return self
end
function SPAWN:InitRandomizeRoute(SpawnStartPoint,SpawnEndPoint,SpawnRadius,SpawnHeight)
self:F({self.SpawnTemplatePrefix,SpawnStartPoint,SpawnEndPoint,SpawnRadius,SpawnHeight})
self.SpawnRandomizeRoute=true
self.SpawnRandomizeRouteStartPoint=SpawnStartPoint
self.SpawnRandomizeRouteEndPoint=SpawnEndPoint
self.SpawnRandomizeRouteRadius=SpawnRadius
self.SpawnRandomizeRouteHeight=SpawnHeight
for GroupID=1,self.SpawnMaxGroups do
self:_RandomizeRoute(GroupID)
end
return self
end
function SPAWN:InitRandomizePosition(RandomizePosition,OuterRadius,InnerRadius)
self:F({self.SpawnTemplatePrefix,RandomizePosition,OuterRadius,InnerRadius})
self.SpawnRandomizePosition=RandomizePosition or false
self.SpawnRandomizePositionOuterRadius=OuterRadius or 0
self.SpawnRandomizePositionInnerRadius=InnerRadius or 0
for GroupID=1,self.SpawnMaxGroups do
self:_RandomizeRoute(GroupID)
end
return self
end
function SPAWN:InitRandomizeUnits(RandomizeUnits,OuterRadius,InnerRadius)
self:F({self.SpawnTemplatePrefix,RandomizeUnits,OuterRadius,InnerRadius})
self.SpawnRandomizeUnits=RandomizeUnits or false
self.SpawnOuterRadius=OuterRadius or 0
self.SpawnInnerRadius=InnerRadius or 0
for GroupID=1,self.SpawnMaxGroups do
self:_RandomizeRoute(GroupID)
end
return self
end
function SPAWN:InitRandomizeTemplate(SpawnTemplatePrefixTable)
self:F({self.SpawnTemplatePrefix,SpawnTemplatePrefixTable})
local temptable={}
for _,_temp in pairs(SpawnTemplatePrefixTable)do
temptable[#temptable+1]=_temp
end
self.SpawnTemplatePrefixTable=UTILS.ShuffleTable(temptable)
self.SpawnRandomizeTemplate=true
for SpawnGroupID=1,self.SpawnMaxGroups do
self:_RandomizeTemplate(SpawnGroupID)
end
return self
end
function SPAWN:InitRandomizeTemplateSet(SpawnTemplateSet)
self:F({self.SpawnTemplatePrefix})
local setnames=SpawnTemplateSet:GetSetNames()
self:InitRandomizeTemplate(setnames)
return self
end
function SPAWN:InitRandomizeTemplatePrefixes(SpawnTemplatePrefixes)
self:F({self.SpawnTemplatePrefix})
local SpawnTemplateSet=SET_GROUP:New():FilterPrefixes(SpawnTemplatePrefixes):FilterOnce()
self:InitRandomizeTemplateSet(SpawnTemplateSet)
return self
end
function SPAWN:InitGrouping(Grouping)
self:F({self.SpawnTemplatePrefix,Grouping})
self.SpawnGrouping=Grouping
return self
end
function SPAWN:InitRandomizeZones(SpawnZoneTable)
self:F({self.SpawnTemplatePrefix,SpawnZoneTable})
local temptable={}
for _,_temp in pairs(SpawnZoneTable)do
temptable[#temptable+1]=_temp
end
self.SpawnZoneTable=UTILS.ShuffleTable(temptable)
self.SpawnRandomizeZones=true
for SpawnGroupID=1,self.SpawnMaxGroups do
self:_RandomizeZones(SpawnGroupID)
end
return self
end
function SPAWN:InitRepeat()
self:F({self.SpawnTemplatePrefix,self.SpawnIndex})
self.Repeat=true
self.RepeatOnEngineShutDown=false
self.RepeatOnLanding=true
return self
end
function SPAWN:InitRepeatOnLanding()
self:F({self.SpawnTemplatePrefix})
self:InitRepeat()
self.RepeatOnEngineShutDown=false
self.RepeatOnLanding=true
return self
end
function SPAWN:InitRepeatOnEngineShutDown()
self:F({self.SpawnTemplatePrefix})
self:InitRepeat()
self.RepeatOnEngineShutDown=true
self.RepeatOnLanding=false
return self
end
function SPAWN:InitCleanUp(SpawnCleanUpInterval)
self:F({self.SpawnTemplatePrefix,SpawnCleanUpInterval})
self.SpawnCleanUpInterval=SpawnCleanUpInterval
self.SpawnCleanUpTimeStamps={}
local SpawnGroup,SpawnCursor=self:GetFirstAliveGroup()
self:T({"CleanUp Scheduler:",SpawnGroup})
self.CleanUpScheduler=SCHEDULER:New(self,self._SpawnCleanUpScheduler,{},1,SpawnCleanUpInterval,0.2)
return self
end
function SPAWN:InitArray(SpawnAngle,SpawnWidth,SpawnDeltaX,SpawnDeltaY)
self:F({self.SpawnTemplatePrefix,SpawnAngle,SpawnWidth,SpawnDeltaX,SpawnDeltaY})
self.SpawnVisible=true
local SpawnX=0
local SpawnY=0
local SpawnXIndex=0
local SpawnYIndex=0
for SpawnGroupID=1,self.SpawnMaxGroups do
self:T({SpawnX,SpawnY,SpawnXIndex,SpawnYIndex})
self.SpawnGroups[SpawnGroupID].Visible=true
self.SpawnGroups[SpawnGroupID].Spawned=false
SpawnXIndex=SpawnXIndex+1
if SpawnWidth and SpawnWidth~=0 then
if SpawnXIndex>=SpawnWidth then
SpawnXIndex=0
SpawnYIndex=SpawnYIndex+1
end
end
local SpawnRootX=self.SpawnGroups[SpawnGroupID].SpawnTemplate.x
local SpawnRootY=self.SpawnGroups[SpawnGroupID].SpawnTemplate.y
self:_TranslateRotate(SpawnGroupID,SpawnRootX,SpawnRootY,SpawnX,SpawnY,SpawnAngle)
self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation=true
self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible=true
self.SpawnGroups[SpawnGroupID].Visible=true
self:HandleEvent(EVENTS.Birth,self._OnBirth)
self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash)
self:HandleEvent(EVENTS.RemoveUnit,self._OnDeadOrCrash)
if self.Repeat then
self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff)
self:HandleEvent(EVENTS.Land,self._OnLand)
end
if self.RepeatOnEngineShutDown then
self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutDown)
end
self.SpawnGroups[SpawnGroupID].Group=_DATABASE:Spawn(self.SpawnGroups[SpawnGroupID].SpawnTemplate)
SpawnX=SpawnXIndex*SpawnDeltaX
SpawnY=SpawnYIndex*SpawnDeltaY
end
return self
end
do
function SPAWN:InitAIOnOff(AIOnOff)
self.AIOnOff=AIOnOff
return self
end
function SPAWN:InitAIOn()
return self:InitAIOnOff(true)
end
function SPAWN:InitAIOff()
return self:InitAIOnOff(false)
end
end
do
function SPAWN:InitDelayOnOff(DelayOnOff)
self.DelayOnOff=DelayOnOff
return self
end
function SPAWN:InitDelayOn()
return self:InitDelayOnOff(true)
end
function SPAWN:InitDelayOff()
return self:InitDelayOnOff(false)
end
end
function SPAWN:Spawn()
self:F({self.SpawnTemplatePrefix,self.SpawnIndex,self.AliveUnits})
if self.SpawnInitAirbase then
return self:SpawnAtAirbase(self.SpawnInitAirbase,self.SpawnInitTakeoff,nil,self.SpawnInitTerminalType)
else
return self:SpawnWithIndex(self.SpawnIndex+1)
end
end
function SPAWN:ReSpawn(SpawnIndex)
self:F({self.SpawnTemplatePrefix,SpawnIndex})
if not SpawnIndex then
SpawnIndex=1
end
local SpawnGroup=self:GetGroupFromIndex(SpawnIndex)
local WayPoints=SpawnGroup and SpawnGroup.WayPoints or nil
if SpawnGroup then
local SpawnDCSGroup=SpawnGroup:GetDCSObject()
if SpawnDCSGroup then
SpawnGroup:Destroy()
end
end
local SpawnGroup=self:SpawnWithIndex(SpawnIndex)
if SpawnGroup and WayPoints then
SpawnGroup:WayPointInitialize(WayPoints)
SpawnGroup:WayPointExecute(1,5)
end
if SpawnGroup.ReSpawnFunction then
SpawnGroup:ReSpawnFunction()
end
SpawnGroup:ResetEvents()
return SpawnGroup
end
function SPAWN:SetSpawnIndex(SpawnIndex)
self.SpawnIndex=SpawnIndex or 0
end
function SPAWN:SpawnWithIndex(SpawnIndex,NoBirth)
self:F2({SpawnTemplatePrefix=self.SpawnTemplatePrefix,SpawnIndex=SpawnIndex,AliveUnits=self.AliveUnits,SpawnMaxGroups=self.SpawnMaxGroups})
if self:_GetSpawnIndex(SpawnIndex)then
if self.SpawnGroups[self.SpawnIndex].Visible then
self.SpawnGroups[self.SpawnIndex].Group:Activate()
else
local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
self:T(SpawnTemplate.name)
if SpawnTemplate then
local PointVec3=POINT_VEC3:New(SpawnTemplate.route.points[1].x,SpawnTemplate.route.points[1].alt,SpawnTemplate.route.points[1].y)
self:T({"Current point of ",self.SpawnTemplatePrefix,PointVec3})
if self.SpawnRandomizePosition then
local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnRandomizePositionOuterRadius,self.SpawnRandomizePositionInnerRadius)
local CurrentX=SpawnTemplate.units[1].x
local CurrentY=SpawnTemplate.units[1].y
SpawnTemplate.x=RandomVec2.x
SpawnTemplate.y=RandomVec2.y
for UnitID=1,#SpawnTemplate.units do
SpawnTemplate.units[UnitID].x=SpawnTemplate.units[UnitID].x+(RandomVec2.x-CurrentX)
SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-CurrentY)
self:T('SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
end
end
if self.SpawnRandomizeUnits then
for UnitID=1,#SpawnTemplate.units do
local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius)
SpawnTemplate.units[UnitID].x=RandomVec2.x
SpawnTemplate.units[UnitID].y=RandomVec2.y
self:T('SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
end
end
local function _Heading(courseDeg)
local h
if courseDeg<=180 then
h=math.rad(courseDeg)
else
h=-math.rad(360-courseDeg)
end
return h
end
local Rad180=math.rad(180)
local function _HeadingRad(courseRad)
if courseRad<=Rad180 then
return courseRad
else
return-((2*Rad180)-courseRad)
end
end
local function _RandomInRange(min,max)
if min and max then
return min+(math.random()*(max-min))
else
return min
end
end
if self.SpawnInitGroupHeadingMin and#SpawnTemplate.units>0 then
local pivotX=SpawnTemplate.units[1].x
local pivotY=SpawnTemplate.units[1].y
local headingRad=math.rad(_RandomInRange(self.SpawnInitGroupHeadingMin or 0,self.SpawnInitGroupHeadingMax))
local cosHeading=math.cos(headingRad)
local sinHeading=math.sin(headingRad)
local unitVarRad=math.rad(self.SpawnInitGroupUnitVar or 0)
for UnitID=1,#SpawnTemplate.units do
if UnitID>1 then
local unitXOff=SpawnTemplate.units[UnitID].x-pivotX
local unitYOff=SpawnTemplate.units[UnitID].y-pivotY
SpawnTemplate.units[UnitID].x=pivotX+(unitXOff*cosHeading)-(unitYOff*sinHeading)
SpawnTemplate.units[UnitID].y=pivotY+(unitYOff*cosHeading)+(unitXOff*sinHeading)
end
local unitHeading=SpawnTemplate.units[UnitID].heading+headingRad
SpawnTemplate.units[UnitID].heading=_HeadingRad(_RandomInRange(unitHeading-unitVarRad,unitHeading+unitVarRad))
SpawnTemplate.units[UnitID].psi=-SpawnTemplate.units[UnitID].heading
end
end
if self.SpawnInitHeadingMin then
for UnitID=1,#SpawnTemplate.units do
SpawnTemplate.units[UnitID].heading=_Heading(_RandomInRange(self.SpawnInitHeadingMin,self.SpawnInitHeadingMax))
SpawnTemplate.units[UnitID].psi=-SpawnTemplate.units[UnitID].heading
end
end
if self.SpawnInitLivery then
for UnitID=1,#SpawnTemplate.units do
SpawnTemplate.units[UnitID].livery_id=self.SpawnInitLivery
end
end
if self.SpawnInitSkill then
for UnitID=1,#SpawnTemplate.units do
SpawnTemplate.units[UnitID].skill=self.SpawnInitSkill
end
end
if self.SpawnInitModex then
for UnitID=1,#SpawnTemplate.units do
SpawnTemplate.units[UnitID].onboard_num=string.format("%03d",self.SpawnInitModex+(UnitID-1))
end
end
if self.SpawnInitRadio then
SpawnTemplate.communication=self.SpawnInitRadio
end
if self.SpawnInitFreq then
SpawnTemplate.frequency=self.SpawnInitFreq
end
if self.SpawnInitModu then
SpawnTemplate.modulation=self.SpawnInitModu
end
SpawnTemplate.CategoryID=self.SpawnInitCategory or SpawnTemplate.CategoryID
SpawnTemplate.CountryID=self.SpawnInitCountry or SpawnTemplate.CountryID
SpawnTemplate.CoalitionID=self.SpawnInitCoalition or SpawnTemplate.CoalitionID
end
if not NoBirth then
self:HandleEvent(EVENTS.Birth,self._OnBirth)
end
self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash)
self:HandleEvent(EVENTS.RemoveUnit,self._OnDeadOrCrash)
if self.Repeat then
self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff)
self:HandleEvent(EVENTS.Land,self._OnLand)
end
if self.RepeatOnEngineShutDown then
self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutDown)
end
self.SpawnGroups[self.SpawnIndex].Group=_DATABASE:Spawn(SpawnTemplate)
local SpawnGroup=self.SpawnGroups[self.SpawnIndex].Group
if SpawnGroup then
SpawnGroup:SetAIOnOff(self.AIOnOff)
end
self:T3(SpawnTemplate.name)
if self.SpawnFunctionHook then
self.SpawnHookScheduler:Schedule(nil,self.SpawnFunctionHook,{self.SpawnGroups[self.SpawnIndex].Group,unpack(self.SpawnFunctionArguments)},0.1)
end
end
self.SpawnGroups[self.SpawnIndex].Spawned=true
return self.SpawnGroups[self.SpawnIndex].Group
else
end
return nil
end
function SPAWN:SpawnScheduled(SpawnTime,SpawnTimeVariation)
self:F({SpawnTime,SpawnTimeVariation})
if SpawnTime~=nil and SpawnTimeVariation~=nil then
local InitialDelay=0
if self.DelayOnOff==true then
InitialDelay=math.random(SpawnTime-SpawnTime*SpawnTimeVariation,SpawnTime+SpawnTime*SpawnTimeVariation)
end
self.SpawnScheduler=SCHEDULER:New(self,self._Scheduler,{},InitialDelay,SpawnTime,SpawnTimeVariation)
end
return self
end
function SPAWN:SpawnScheduleStart()
self:F({self.SpawnTemplatePrefix})
self.SpawnScheduler:Start()
return self
end
function SPAWN:SpawnScheduleStop()
self:F({self.SpawnTemplatePrefix})
self.SpawnScheduler:Stop()
return self
end
function SPAWN:OnSpawnGroup(SpawnCallBackFunction,...)
self:F("OnSpawnGroup")
self.SpawnFunctionHook=SpawnCallBackFunction
self.SpawnFunctionArguments={}
if arg then
self.SpawnFunctionArguments=arg
end
return self
end
function SPAWN:SpawnAtAirbase(SpawnAirbase,Takeoff,TakeoffAltitude,TerminalType,EmergencyAirSpawn,Parkingdata)
self:F({self.SpawnTemplatePrefix,SpawnAirbase,Takeoff,TakeoffAltitude,TerminalType})
local PointVec3=SpawnAirbase:GetCoordinate()
self:T2(PointVec3)
Takeoff=Takeoff or SPAWN.Takeoff.Hot
if EmergencyAirSpawn==nil then
EmergencyAirSpawn=true
end
self:F({SpawnIndex=self.SpawnIndex})
if self:_GetSpawnIndex(self.SpawnIndex+1)then
local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
self:F({SpawnTemplate=SpawnTemplate})
if SpawnTemplate then
local GroupAlive=self:GetGroupFromIndex(self.SpawnIndex)
self:F({GroupAlive=GroupAlive})
self:T({"Current point of ",self.SpawnTemplatePrefix,SpawnAirbase})
local TemplateGroup=GROUP:FindByName(self.SpawnTemplatePrefix)
local TemplateUnit=TemplateGroup:GetUnit(1)
local group=TemplateGroup
local istransport=group:HasAttribute("Transports")and group:HasAttribute("Planes")
local isawacs=group:HasAttribute("AWACS")
local isfighter=group:HasAttribute("Fighters")or group:HasAttribute("Interceptors")or group:HasAttribute("Multirole fighters")or(group:HasAttribute("Bombers")and not group:HasAttribute("Strategic bombers"))
local isbomber=group:HasAttribute("Strategic bombers")
local istanker=group:HasAttribute("Tankers")
local ishelo=TemplateUnit:HasAttribute("Helicopters")
local nunits=#SpawnTemplate.units
local SpawnPoint=SpawnTemplate.route.points[1]
SpawnPoint.linkUnit=nil
SpawnPoint.helipadId=nil
SpawnPoint.airdromeId=nil
local AirbaseID=SpawnAirbase:GetID()
local AirbaseCategory=SpawnAirbase:GetAirbaseCategory()
self:F({AirbaseCategory=AirbaseCategory})
if AirbaseCategory==Airbase.Category.SHIP then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.HELIPAD then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.AIRDROME then
SpawnPoint.airdromeId=AirbaseID
end
SpawnPoint.alt=0
SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1]
SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2]
local spawnonground=not(Takeoff==SPAWN.Takeoff.Air)
self:T({spawnonground=spawnonground,TOtype=Takeoff,TOair=Takeoff==SPAWN.Takeoff.Air})
local spawnonship=false
local spawnonfarp=false
local spawnonrunway=false
local spawnonairport=false
if spawnonground then
if AirbaseCategory==Airbase.Category.SHIP then
spawnonship=true
elseif AirbaseCategory==Airbase.Category.HELIPAD then
spawnonfarp=true
elseif AirbaseCategory==Airbase.Category.AIRDROME then
spawnonairport=true
end
spawnonrunway=Takeoff==SPAWN.Takeoff.Runway
end
local parkingspots={}
local parkingindex={}
local spots
if spawnonground and not SpawnTemplate.parked then
local nfree=0
local termtype=TerminalType
if spawnonrunway then
if spawnonship then
if ishelo then
termtype=AIRBASE.TerminalType.HelicopterUsable
else
termtype=AIRBASE.TerminalType.OpenMedOrBig
end
else
termtype=AIRBASE.TerminalType.Runway
end
end
local scanradius=50
local scanunits=true
local scanstatics=true
local scanscenery=false
local verysafe=false
if spawnonship or spawnonfarp or spawnonrunway then
self:T(string.format("Group %s is spawned on farp/ship/runway %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype,true)
spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype,true)
else
if ishelo then
if termtype==nil then
self:T(string.format("Helo group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.HelicopterOnly))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
if nfree<nunits then
self:T(string.format("Helo group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.HelicopterUsable))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterUsable,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
self:T(string.format("Helo group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),termtype))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,termtype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
if termtype==nil then
if isbomber or istransport or istanker or isawacs then
self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.OpenBig))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.OpenBig,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
if nfree<nunits then
self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.OpenMedOrBig))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.OpenMedOrBig,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
self:T(string.format("Fighter group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.FighterAircraft))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.FighterAircraft,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
self:T(string.format("Plane group %s is at %s using terminal type %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),tostring(termtype)))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,termtype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
end
end
local _notenough=false
if spawnonship or spawnonfarp or spawnonrunway then
if nfree>=1 then
for i=1,nunits do
table.insert(parkingspots,spots[1].Coordinate)
table.insert(parkingindex,spots[1].TerminalID)
end
PointVec3=spots[1].Coordinate
else
_notenough=true
end
elseif spawnonairport then
if nfree>=nunits then
for i=1,nunits do
table.insert(parkingspots,spots[i].Coordinate)
table.insert(parkingindex,spots[i].TerminalID)
end
else
_notenough=true
end
end
if _notenough then
if EmergencyAirSpawn and not self.SpawnUnControlled then
self:E(string.format("WARNING: Group %s has no parking spots at %s ==> air start!",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
spawnonground=false
spawnonship=false
spawnonfarp=false
spawnonrunway=false
SpawnPoint.type=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1]
SpawnPoint.action=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2]
PointVec3.x=PointVec3.x+math.random(-500,500)
PointVec3.z=PointVec3.z+math.random(-500,500)
if ishelo then
PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000)
else
PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500)
end
Takeoff=GROUP.Takeoff.Air
else
self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
return nil
end
end
else
if TakeoffAltitude then
PointVec3.y=TakeoffAltitude
else
if ishelo then
PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000)
else
PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500)
end
end
end
if not SpawnTemplate.parked then
SpawnTemplate.parked=true
for UnitID=1,nunits do
self:T2('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
local UnitTemplate=SpawnTemplate.units[UnitID]
local SX=UnitTemplate.x
local SY=UnitTemplate.y
local BX=SpawnTemplate.route.points[1].x
local BY=SpawnTemplate.route.points[1].y
local TX=PointVec3.x+(SX-BX)
local TY=PointVec3.z+(SY-BY)
if spawnonground then
if spawnonship or spawnonfarp or spawnonrunway then
self:T(string.format("Group %s spawning at farp, ship or runway %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
SpawnTemplate.units[UnitID].x=PointVec3.x
SpawnTemplate.units[UnitID].y=PointVec3.z
SpawnTemplate.units[UnitID].alt=PointVec3.y
else
self:T(string.format("Group %s spawning at airbase %s on parking spot id %d",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),parkingindex[UnitID]))
SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x
SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z
SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y
end
else
self:T(string.format("Group %s spawning in air at %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
SpawnTemplate.units[UnitID].x=TX
SpawnTemplate.units[UnitID].y=TY
SpawnTemplate.units[UnitID].alt=PointVec3.y
end
UnitTemplate.parking=nil
UnitTemplate.parking_id=nil
if parkingindex[UnitID]then
UnitTemplate.parking=parkingindex[UnitID]
end
self:T(string.format("Group %s unit number %d: Parking    = %s",self.SpawnTemplatePrefix,UnitID,tostring(UnitTemplate.parking)))
self:T(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix,UnitID,tostring(UnitTemplate.parking_id)))
self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
end
end
SpawnPoint.x=PointVec3.x
SpawnPoint.y=PointVec3.z
SpawnPoint.alt=PointVec3.y
SpawnTemplate.x=PointVec3.x
SpawnTemplate.y=PointVec3.z
SpawnTemplate.uncontrolled=self.SpawnUnControlled
local GroupSpawned=self:SpawnWithIndex(self.SpawnIndex)
if Takeoff==GROUP.Takeoff.Air then
for UnitID,UnitSpawned in pairs(GroupSpawned:GetUnits())do
SCHEDULER:New(nil,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()},5)
end
end
if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then
SCHEDULER:New(nil,AIRBASE.CheckOnRunWay,{SpawnAirbase,GroupSpawned,75,true},1.0)
end
return GroupSpawned
end
end
return nil
end
function SPAWN:SpawnAtParkingSpot(Airbase,Spots,Takeoff)
self:F({Airbase=Airbase,Spots=Spots,Takeoff=Takeoff})
if type(Spots)~="table"then
Spots={Spots}
end
local group=GROUP:FindByName(self.SpawnTemplatePrefix)
local nunits=self.SpawnGrouping or#group:GetUnits()
if nunits then
if#Spots<nunits then
self:E("ERROR: Number of provided parking spots is less than number of units in group!")
return nil
end
local Parkingdata={}
for _,TerminalID in pairs(Spots)do
local spot=Airbase:GetParkingSpotData(TerminalID)
self:T2({spot=spot})
if spot and spot.Free then
self:T(string.format("Adding parking spot ID=%d TermType=%d",spot.TerminalID,spot.TerminalType))
table.insert(Parkingdata,spot)
end
end
if#Parkingdata>=nunits then
return self:SpawnAtAirbase(Airbase,Takeoff,nil,nil,nil,Parkingdata)
else
self:E("ERROR: Could not find enough free parking spots!")
end
else
self:E("ERROR: Could not get number of units in group!")
end
return nil
end
function SPAWN:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,SpawnIndex)
self:F({SpawnIndex=SpawnIndex,SpawnMaxGroups=self.SpawnMaxGroups})
local PointVec3=SpawnAirbase:GetCoordinate()
self:T2(PointVec3)
local Takeoff=SPAWN.Takeoff.Cold
local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate
if SpawnTemplate then
local GroupAlive=self:GetGroupFromIndex(SpawnIndex)
self:T({"Current point of ",self.SpawnTemplatePrefix,SpawnAirbase})
local TemplateGroup=GROUP:FindByName(self.SpawnTemplatePrefix)
local TemplateUnit=TemplateGroup:GetUnit(1)
local ishelo=TemplateUnit:HasAttribute("Helicopters")
local isbomber=TemplateUnit:HasAttribute("Bombers")
local istransport=TemplateUnit:HasAttribute("Transports")
local isfighter=TemplateUnit:HasAttribute("Battleplanes")
local nunits=#SpawnTemplate.units
local SpawnPoint=SpawnTemplate.route.points[1]
SpawnPoint.linkUnit=nil
SpawnPoint.helipadId=nil
SpawnPoint.airdromeId=nil
local AirbaseID=SpawnAirbase:GetID()
local AirbaseCategory=SpawnAirbase:GetAirbaseCategory()
self:F({AirbaseCategory=AirbaseCategory})
if AirbaseCategory==Airbase.Category.SHIP then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.HELIPAD then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.AIRDROME then
SpawnPoint.airdromeId=AirbaseID
end
SpawnPoint.alt=0
SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1]
SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2]
local spawnonground=not(Takeoff==SPAWN.Takeoff.Air)
self:T({spawnonground=spawnonground,TOtype=Takeoff,TOair=Takeoff==SPAWN.Takeoff.Air})
local spawnonship=false
local spawnonfarp=false
local spawnonrunway=false
local spawnonairport=false
if spawnonground then
if AirbaseCategory==Airbase.Category.SHIP then
spawnonship=true
elseif AirbaseCategory==Airbase.Category.HELIPAD then
spawnonfarp=true
elseif AirbaseCategory==Airbase.Category.AIRDROME then
spawnonairport=true
end
spawnonrunway=Takeoff==SPAWN.Takeoff.Runway
end
local parkingspots={}
local parkingindex={}
local spots
if spawnonground and not SpawnTemplate.parked then
local nfree=0
local termtype=TerminalType
local scanradius=50
local scanunits=true
local scanstatics=true
local scanscenery=false
local verysafe=false
if spawnonship or spawnonfarp or spawnonrunway then
self:T(string.format("Group %s is spawned on farp/ship/runway %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype,true)
spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype,true)
else
if ishelo then
if termtype==nil then
self:T(string.format("Helo group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.HelicopterOnly))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
if nfree<nunits then
self:T(string.format("Helo group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.HelicopterUsable))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterUsable,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
self:T(string.format("Helo group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),termtype))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,termtype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
if termtype==nil then
if isbomber or istransport then
self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.OpenBig))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.OpenBig,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
if nfree<nunits then
self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.OpenMedOrBig))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.OpenMedOrBig,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
self:T(string.format("Fighter group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.FighterAircraft))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.FighterAircraft,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
self:T(string.format("Plane group %s is at %s using terminal type %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),tostring(termtype)))
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,termtype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
end
end
local _notenough=false
if spawnonship or spawnonfarp or spawnonrunway then
if nfree>=1 then
for i=1,nunits do
table.insert(parkingspots,spots[1].Coordinate)
table.insert(parkingindex,spots[1].TerminalID)
end
PointVec3=spots[1].Coordinate
else
_notenough=true
end
elseif spawnonairport then
if nfree>=nunits then
for i=1,nunits do
table.insert(parkingspots,spots[i].Coordinate)
table.insert(parkingindex,spots[i].TerminalID)
end
else
_notenough=true
end
end
if _notenough then
if not self.SpawnUnControlled then
else
self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
return nil
end
end
else
end
if not SpawnTemplate.parked then
SpawnTemplate.parked=true
for UnitID=1,nunits do
self:F('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
local UnitTemplate=SpawnTemplate.units[UnitID]
local SX=UnitTemplate.x
local SY=UnitTemplate.y
local BX=SpawnTemplate.route.points[1].x
local BY=SpawnTemplate.route.points[1].y
local TX=PointVec3.x+(SX-BX)
local TY=PointVec3.z+(SY-BY)
if spawnonground then
if spawnonship or spawnonfarp or spawnonrunway then
self:T(string.format("Group %s spawning at farp, ship or runway %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
SpawnTemplate.units[UnitID].x=PointVec3.x
SpawnTemplate.units[UnitID].y=PointVec3.z
SpawnTemplate.units[UnitID].alt=PointVec3.y
else
self:T(string.format("Group %s spawning at airbase %s on parking spot id %d",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),parkingindex[UnitID]))
SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x
SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z
SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y
end
else
self:T(string.format("Group %s spawning in air at %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
SpawnTemplate.units[UnitID].x=TX
SpawnTemplate.units[UnitID].y=TY
SpawnTemplate.units[UnitID].alt=PointVec3.y
end
UnitTemplate.parking=nil
UnitTemplate.parking_id=nil
if parkingindex[UnitID]then
UnitTemplate.parking=parkingindex[UnitID]
end
self:T2(string.format("Group %s unit number %d: Parking    = %s",self.SpawnTemplatePrefix,UnitID,tostring(UnitTemplate.parking)))
self:T2(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix,UnitID,tostring(UnitTemplate.parking_id)))
self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
end
end
SpawnPoint.x=PointVec3.x
SpawnPoint.y=PointVec3.z
SpawnPoint.alt=PointVec3.y
SpawnTemplate.x=PointVec3.x
SpawnTemplate.y=PointVec3.z
SpawnTemplate.uncontrolled=true
local GroupSpawned=self:SpawnWithIndex(SpawnIndex,true)
if Takeoff==GROUP.Takeoff.Air then
for UnitID,UnitSpawned in pairs(GroupSpawned:GetUnits())do
SCHEDULER:New(nil,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()},5)
end
end
if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then
SCHEDULER:New(nil,AIRBASE.CheckOnRunWay,{SpawnAirbase,GroupSpawned,75,true},1.0)
end
end
end
function SPAWN:ParkAtAirbase(SpawnAirbase,TerminalType,Parkingdata)
self:F({self.SpawnTemplatePrefix,SpawnAirbase,TerminalType})
self:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,1)
for SpawnIndex=2,self.SpawnMaxGroups do
self:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,SpawnIndex)
end
self:SetSpawnIndex(0)
return nil
end
function SPAWN:SpawnFromVec3(Vec3,SpawnIndex)
self:F({self.SpawnTemplatePrefix,Vec3,SpawnIndex})
local PointVec3=POINT_VEC3:NewFromVec3(Vec3)
self:T2(PointVec3)
if SpawnIndex then
else
SpawnIndex=self.SpawnIndex+1
end
if self:_GetSpawnIndex(SpawnIndex)then
local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
if SpawnTemplate then
self:T({"Current point of ",self.SpawnTemplatePrefix,Vec3})
local TemplateHeight=SpawnTemplate.route and SpawnTemplate.route.points[1].alt or nil
SpawnTemplate.route=SpawnTemplate.route or{}
SpawnTemplate.route.points=SpawnTemplate.route.points or{}
SpawnTemplate.route.points[1]=SpawnTemplate.route.points[1]or{}
SpawnTemplate.route.points[1].x=SpawnTemplate.route.points[1].x or 0
SpawnTemplate.route.points[1].y=SpawnTemplate.route.points[1].y or 0
for UnitID=1,#SpawnTemplate.units do
local UnitTemplate=SpawnTemplate.units[UnitID]
local SX=UnitTemplate.x or 0
local SY=UnitTemplate.y or 0
local BX=SpawnTemplate.route.points[1].x
local BY=SpawnTemplate.route.points[1].y
local TX=Vec3.x+(SX-BX)
local TY=Vec3.z+(SY-BY)
SpawnTemplate.units[UnitID].x=TX
SpawnTemplate.units[UnitID].y=TY
if SpawnTemplate.CategoryID~=Group.Category.SHIP then
SpawnTemplate.units[UnitID].alt=Vec3.y or TemplateHeight
end
self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
end
SpawnTemplate.route.points[1].x=Vec3.x
SpawnTemplate.route.points[1].y=Vec3.z
if SpawnTemplate.CategoryID~=Group.Category.SHIP then
SpawnTemplate.route.points[1].alt=Vec3.y or TemplateHeight
end
SpawnTemplate.x=Vec3.x
SpawnTemplate.y=Vec3.z
SpawnTemplate.alt=Vec3.y or TemplateHeight
return self:SpawnWithIndex(self.SpawnIndex)
end
end
return nil
end
function SPAWN:SpawnFromCoordinate(Coordinate,SpawnIndex)
self:F({self.SpawnTemplatePrefix,SpawnIndex})
return self:SpawnFromVec3(Coordinate:GetVec3(),SpawnIndex)
end
function SPAWN:SpawnFromPointVec3(PointVec3,SpawnIndex)
self:F({self.SpawnTemplatePrefix,SpawnIndex})
return self:SpawnFromVec3(PointVec3:GetVec3(),SpawnIndex)
end
function SPAWN:SpawnFromVec2(Vec2,MinHeight,MaxHeight,SpawnIndex)
self:F({self.SpawnTemplatePrefix,self.SpawnIndex,Vec2,MinHeight,MaxHeight,SpawnIndex})
local Height=nil
if MinHeight and MaxHeight then
Height=math.random(MinHeight,MaxHeight)
end
return self:SpawnFromVec3({x=Vec2.x,y=Height,z=Vec2.y},SpawnIndex)
end
function SPAWN:SpawnFromPointVec2(PointVec2,MinHeight,MaxHeight,SpawnIndex)
self:F({self.SpawnTemplatePrefix,self.SpawnIndex})
return self:SpawnFromVec2(PointVec2:GetVec2(),MinHeight,MaxHeight,SpawnIndex)
end
function SPAWN:SpawnFromUnit(HostUnit,MinHeight,MaxHeight,SpawnIndex)
self:F({self.SpawnTemplatePrefix,HostUnit,MinHeight,MaxHeight,SpawnIndex})
if HostUnit and HostUnit:IsAlive()~=nil then
return self:SpawnFromVec2(HostUnit:GetVec2(),MinHeight,MaxHeight,SpawnIndex)
end
return nil
end
function SPAWN:SpawnFromStatic(HostStatic,MinHeight,MaxHeight,SpawnIndex)
self:F({self.SpawnTemplatePrefix,HostStatic,MinHeight,MaxHeight,SpawnIndex})
if HostStatic and HostStatic:IsAlive()then
return self:SpawnFromVec2(HostStatic:GetVec2(),MinHeight,MaxHeight,SpawnIndex)
end
return nil
end
function SPAWN:SpawnInZone(Zone,RandomizeGroup,MinHeight,MaxHeight,SpawnIndex)
self:F({self.SpawnTemplatePrefix,Zone,RandomizeGroup,MinHeight,MaxHeight,SpawnIndex})
if Zone then
if RandomizeGroup then
return self:SpawnFromVec2(Zone:GetRandomVec2(),MinHeight,MaxHeight,SpawnIndex)
else
return self:SpawnFromVec2(Zone:GetVec2(),MinHeight,MaxHeight,SpawnIndex)
end
end
return nil
end
function SPAWN:InitUnControlled(UnControlled)
self:F2({self.SpawnTemplatePrefix,UnControlled})
self.SpawnUnControlled=(UnControlled==true)and true or nil
for SpawnGroupID=1,self.SpawnMaxGroups do
self.SpawnGroups[SpawnGroupID].UnControlled=self.SpawnUnControlled
end
return self
end
function SPAWN:GetCoordinate()
local LateGroup=GROUP:FindByName(self.SpawnTemplatePrefix)
if LateGroup then
return LateGroup:GetCoordinate()
end
return nil
end
function SPAWN:SpawnGroupName(SpawnIndex)
self:F({self.SpawnTemplatePrefix,SpawnIndex})
local SpawnPrefix=self.SpawnTemplatePrefix
if self.SpawnAliasPrefix then
SpawnPrefix=self.SpawnAliasPrefix
end
if SpawnIndex then
local SpawnName=string.format('%s#%03d',SpawnPrefix,SpawnIndex)
self:T(SpawnName)
return SpawnName
else
self:T(SpawnPrefix)
return SpawnPrefix
end
end
function SPAWN:GetFirstAliveGroup()
self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix})
for SpawnIndex=1,self.SpawnCount do
local SpawnGroup=self:GetGroupFromIndex(SpawnIndex)
if SpawnGroup and SpawnGroup:IsAlive()then
return SpawnGroup,SpawnIndex
end
end
return nil,nil
end
function SPAWN:GetNextAliveGroup(SpawnIndexStart)
self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnIndexStart})
SpawnIndexStart=SpawnIndexStart+1
for SpawnIndex=SpawnIndexStart,self.SpawnCount do
local SpawnGroup=self:GetGroupFromIndex(SpawnIndex)
if SpawnGroup and SpawnGroup:IsAlive()then
return SpawnGroup,SpawnIndex
end
end
return nil,nil
end
function SPAWN:GetLastAliveGroup()
self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix})
for SpawnIndex=self.SpawnCount,1,-1 do
local SpawnGroup=self:GetGroupFromIndex(SpawnIndex)
if SpawnGroup and SpawnGroup:IsAlive()then
self.SpawnIndex=SpawnIndex
return SpawnGroup
end
end
self.SpawnIndex=nil
return nil
end
function SPAWN:GetGroupFromIndex(SpawnIndex)
self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnIndex})
if not SpawnIndex then
SpawnIndex=1
end
if self.SpawnGroups and self.SpawnGroups[SpawnIndex]then
local SpawnGroup=self.SpawnGroups[SpawnIndex].Group
return SpawnGroup
else
return nil
end
end
function SPAWN:_GetPrefixFromGroup(SpawnGroup)
local GroupName=SpawnGroup:GetName()
if GroupName then
local SpawnPrefix=self:_GetPrefixFromGroupName(GroupName)
return SpawnPrefix
end
return nil
end
function SPAWN:_GetPrefixFromGroupName(SpawnGroupName)
if SpawnGroupName then
local SpawnPrefix=string.match(SpawnGroupName,".*#")
if SpawnPrefix then
SpawnPrefix=SpawnPrefix:sub(1,-2)
end
return SpawnPrefix
end
return nil
end
function SPAWN:GetSpawnIndexFromGroup(SpawnGroup)
self:F2({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnGroup})
local IndexString=string.match(SpawnGroup:GetName(),"#(%d*)$"):sub(2)
local Index=tonumber(IndexString)
self:T3(IndexString,Index)
return Index
end
function SPAWN:_GetLastIndex()
self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix})
return self.SpawnMaxGroups
end
function SPAWN:_InitializeSpawnGroups(SpawnIndex)
self:F3({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnIndex})
if not self.SpawnGroups[SpawnIndex]then
self.SpawnGroups[SpawnIndex]={}
self.SpawnGroups[SpawnIndex].Visible=false
self.SpawnGroups[SpawnIndex].Spawned=false
self.SpawnGroups[SpawnIndex].UnControlled=false
self.SpawnGroups[SpawnIndex].SpawnTime=0
self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix=self.SpawnTemplatePrefix
self.SpawnGroups[SpawnIndex].SpawnTemplate=self:_Prepare(self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix,SpawnIndex)
end
self:_RandomizeTemplate(SpawnIndex)
self:_RandomizeRoute(SpawnIndex)
return self.SpawnGroups[SpawnIndex]
end
function SPAWN:_GetGroupCategoryID(SpawnPrefix)
local TemplateGroup=Group.getByName(SpawnPrefix)
if TemplateGroup then
return TemplateGroup:getCategory()
else
return nil
end
end
function SPAWN:_GetGroupCoalitionID(SpawnPrefix)
local TemplateGroup=Group.getByName(SpawnPrefix)
if TemplateGroup then
return TemplateGroup:getCoalition()
else
return nil
end
end
function SPAWN:_GetGroupCountryID(SpawnPrefix)
self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnPrefix})
local TemplateGroup=Group.getByName(SpawnPrefix)
if TemplateGroup then
local TemplateUnits=TemplateGroup:getUnits()
return TemplateUnits[1]:getCountry()
else
return nil
end
end
function SPAWN:_GetTemplate(SpawnTemplatePrefix)
self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnTemplatePrefix})
local SpawnTemplate=nil
local Template=_DATABASE.Templates.Groups[SpawnTemplatePrefix].Template
self:F({Template=Template})
SpawnTemplate=UTILS.DeepCopy(_DATABASE.Templates.Groups[SpawnTemplatePrefix].Template)
if SpawnTemplate==nil then
error('No Template returned for SpawnTemplatePrefix = '..SpawnTemplatePrefix)
end
self:T3({SpawnTemplate})
return SpawnTemplate
end
function SPAWN:_Prepare(SpawnTemplatePrefix,SpawnIndex)
self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix})
local SpawnTemplate
if self.TweakedTemplate~=nil and self.TweakedTemplate==true then
BASE:I("WARNING: You are using a tweaked template.")
SpawnTemplate=self.SpawnTemplate
else
SpawnTemplate=self:_GetTemplate(SpawnTemplatePrefix)
SpawnTemplate.name=self:SpawnGroupName(SpawnIndex)
end
SpawnTemplate.groupId=nil
SpawnTemplate.lateActivation=self.LateActivated or false
if SpawnTemplate.CategoryID==Group.Category.GROUND then
self:T3("For ground units, visible needs to be false...")
SpawnTemplate.visible=false
end
if self.SpawnGrouping then
local UnitAmount=#SpawnTemplate.units
self:F({UnitAmount=UnitAmount,SpawnGrouping=self.SpawnGrouping})
if UnitAmount>self.SpawnGrouping then
for UnitID=self.SpawnGrouping+1,UnitAmount do
SpawnTemplate.units[UnitID]=nil
end
else
if UnitAmount<self.SpawnGrouping then
for UnitID=UnitAmount+1,self.SpawnGrouping do
SpawnTemplate.units[UnitID]=UTILS.DeepCopy(SpawnTemplate.units[1])
SpawnTemplate.units[UnitID].unitId=nil
end
end
end
end
if self.SpawnInitKeepUnitNames==false then
for UnitID=1,#SpawnTemplate.units do
SpawnTemplate.units[UnitID].name=string.format(SpawnTemplate.name..'-%02d',UnitID)
SpawnTemplate.units[UnitID].unitId=nil
end
else
for UnitID=1,#SpawnTemplate.units do
local UnitPrefix,Rest=string.match(SpawnTemplate.units[UnitID].name,"^([^#]+)#?"):gsub("^%s*(.-)%s*$","%1")
self:T({UnitPrefix,Rest})
SpawnTemplate.units[UnitID].name=string.format('%s#%03d-%02d',UnitPrefix,SpawnIndex,UnitID)
SpawnTemplate.units[UnitID].unitId=nil
end
end
for UnitID=1,#SpawnTemplate.units do
local Callsign=SpawnTemplate.units[UnitID].callsign
if Callsign then
if type(Callsign)~="number"then
Callsign[2]=((SpawnIndex-1)%10)+1
local CallsignName=SpawnTemplate.units[UnitID].callsign["name"]
CallsignName=string.match(CallsignName,"^(%a+)")
local CallsignLen=CallsignName:len()
SpawnTemplate.units[UnitID].callsign["name"]=CallsignName:sub(1,CallsignLen)..SpawnTemplate.units[UnitID].callsign[2]..SpawnTemplate.units[UnitID].callsign[3]
else
SpawnTemplate.units[UnitID].callsign=Callsign+SpawnIndex
end
end
end
self:T3({"Template:",SpawnTemplate})
return SpawnTemplate
end
function SPAWN:_RandomizeRoute(SpawnIndex)
self:F({self.SpawnTemplatePrefix,SpawnIndex,self.SpawnRandomizeRoute,self.SpawnRandomizeRouteStartPoint,self.SpawnRandomizeRouteEndPoint,self.SpawnRandomizeRouteRadius})
if self.SpawnRandomizeRoute then
local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate
local RouteCount=#SpawnTemplate.route.points
for t=self.SpawnRandomizeRouteStartPoint+1,(RouteCount-self.SpawnRandomizeRouteEndPoint)do
SpawnTemplate.route.points[t].x=SpawnTemplate.route.points[t].x+math.random(self.SpawnRandomizeRouteRadius*-1,self.SpawnRandomizeRouteRadius)
SpawnTemplate.route.points[t].y=SpawnTemplate.route.points[t].y+math.random(self.SpawnRandomizeRouteRadius*-1,self.SpawnRandomizeRouteRadius)
if SpawnTemplate.CategoryID==Group.Category.AIRPLANE or SpawnTemplate.CategoryID==Group.Category.HELICOPTER then
if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then
SpawnTemplate.route.points[t].alt=SpawnTemplate.route.points[t].alt+math.random(1,self.SpawnRandomizeRouteHeight)
end
else
SpawnTemplate.route.points[t].alt=nil
end
self:T('SpawnTemplate.route.points['..t..'].x = '..SpawnTemplate.route.points[t].x..', SpawnTemplate.route.points['..t..'].y = '..SpawnTemplate.route.points[t].y)
end
end
self:_RandomizeZones(SpawnIndex)
return self
end
function SPAWN:_RandomizeTemplate(SpawnIndex)
self:F({self.SpawnTemplatePrefix,SpawnIndex,self.SpawnRandomizeTemplate})
if self.SpawnRandomizeTemplate then
self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix=self.SpawnTemplatePrefixTable[math.random(1,#self.SpawnTemplatePrefixTable)]
self.SpawnGroups[SpawnIndex].SpawnTemplate=self:_Prepare(self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix,SpawnIndex)
self.SpawnGroups[SpawnIndex].SpawnTemplate.route=UTILS.DeepCopy(self.SpawnTemplate.route)
self.SpawnGroups[SpawnIndex].SpawnTemplate.x=self.SpawnTemplate.x
self.SpawnGroups[SpawnIndex].SpawnTemplate.y=self.SpawnTemplate.y
self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time=self.SpawnTemplate.start_time
local OldX=self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].x
local OldY=self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y
for UnitID=1,#self.SpawnGroups[SpawnIndex].SpawnTemplate.units do
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading=self.SpawnTemplate.units[1].heading
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x=self.SpawnTemplate.units[1].x+(self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x-OldX)
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y=self.SpawnTemplate.units[1].y+(self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y-OldY)
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt=self.SpawnTemplate.units[1].alt
end
end
self:_RandomizeRoute(SpawnIndex)
return self
end
function SPAWN:_RandomizeZones(SpawnIndex)
self:F({self.SpawnTemplatePrefix,SpawnIndex,self.SpawnRandomizeZones})
if self.SpawnRandomizeZones then
local SpawnZone=nil
while not SpawnZone do
self:T({SpawnZoneTableCount=#self.SpawnZoneTable,self.SpawnZoneTable})
local ZoneID=math.random(#self.SpawnZoneTable)
self:T(ZoneID)
SpawnZone=self.SpawnZoneTable[ZoneID]:GetZoneMaybe()
end
self:T("Preparing Spawn in Zone",SpawnZone:GetName())
local SpawnVec2=SpawnZone:GetRandomVec2()
self:T({SpawnVec2=SpawnVec2})
local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate
self:T({Route=SpawnTemplate.route})
for UnitID=1,#SpawnTemplate.units do
local UnitTemplate=SpawnTemplate.units[UnitID]
self:T('Before Translation SpawnTemplate.units['..UnitID..'].x = '..UnitTemplate.x..', SpawnTemplate.units['..UnitID..'].y = '..UnitTemplate.y)
local SX=UnitTemplate.x
local SY=UnitTemplate.y
local BX=SpawnTemplate.route.points[1].x
local BY=SpawnTemplate.route.points[1].y
local TX=SpawnVec2.x+(SX-BX)
local TY=SpawnVec2.y+(SY-BY)
UnitTemplate.x=TX
UnitTemplate.y=TY
self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..UnitTemplate.x..', SpawnTemplate.units['..UnitID..'].y = '..UnitTemplate.y)
end
SpawnTemplate.x=SpawnVec2.x
SpawnTemplate.y=SpawnVec2.y
SpawnTemplate.route.points[1].x=SpawnVec2.x
SpawnTemplate.route.points[1].y=SpawnVec2.y
end
return self
end
function SPAWN:_TranslateRotate(SpawnIndex,SpawnRootX,SpawnRootY,SpawnX,SpawnY,SpawnAngle)
self:F({self.SpawnTemplatePrefix,SpawnIndex,SpawnRootX,SpawnRootY,SpawnX,SpawnY,SpawnAngle})
local TranslatedX=SpawnX
local TranslatedY=SpawnY
local RotatedX=-TranslatedX*math.cos(math.rad(SpawnAngle))+TranslatedY*math.sin(math.rad(SpawnAngle))
local RotatedY=TranslatedX*math.sin(math.rad(SpawnAngle))+TranslatedY*math.cos(math.rad(SpawnAngle))
self.SpawnGroups[SpawnIndex].SpawnTemplate.x=SpawnRootX-RotatedX
self.SpawnGroups[SpawnIndex].SpawnTemplate.y=SpawnRootY+RotatedY
local SpawnUnitCount=table.getn(self.SpawnGroups[SpawnIndex].SpawnTemplate.units)
for u=1,SpawnUnitCount do
local TranslatedX=SpawnX
local TranslatedY=SpawnY-10*(u-1)
local RotatedX=-TranslatedX*math.cos(math.rad(SpawnAngle))+TranslatedY*math.sin(math.rad(SpawnAngle))
local RotatedY=TranslatedX*math.sin(math.rad(SpawnAngle))+TranslatedY*math.cos(math.rad(SpawnAngle))
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x=SpawnRootX-RotatedX
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y=SpawnRootY+RotatedY
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading=self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading+math.rad(SpawnAngle)
end
return self
end
function SPAWN:_GetSpawnIndex(SpawnIndex)
self:F2({self.SpawnTemplatePrefix,SpawnIndex,self.SpawnMaxGroups,self.SpawnMaxUnitsAlive,self.AliveUnits,#self.SpawnTemplate.units})
if(self.SpawnMaxGroups==0)or(SpawnIndex<=self.SpawnMaxGroups)then
if(self.SpawnMaxUnitsAlive==0)or(self.AliveUnits+#self.SpawnTemplate.units<=self.SpawnMaxUnitsAlive)or self.UnControlled==true then
self:F({SpawnCount=self.SpawnCount,SpawnIndex=SpawnIndex})
if SpawnIndex and SpawnIndex>=self.SpawnCount+1 then
self.SpawnCount=self.SpawnCount+1
SpawnIndex=self.SpawnCount
end
self.SpawnIndex=SpawnIndex
if not self.SpawnGroups[self.SpawnIndex]then
self:_InitializeSpawnGroups(self.SpawnIndex)
end
else
return nil
end
else
return nil
end
return self.SpawnIndex
end
function SPAWN:_OnBirth(EventData)
self:F(self.SpawnTemplatePrefix)
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
self:T({"Birth Event:",EventPrefix,self.SpawnTemplatePrefix})
if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
self.AliveUnits=self.AliveUnits+1
self:T("Alive Units: "..self.AliveUnits)
end
end
end
end
function SPAWN:_OnDeadOrCrash(EventData)
self:F(self.SpawnTemplatePrefix)
local unit=UNIT:FindByName(EventData.IniUnitName)
if unit then
local EventPrefix=self:_GetPrefixFromGroupName(unit.GroupName)
if EventPrefix then
self:T({"Dead event: "..EventPrefix})
if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
self.AliveUnits=self.AliveUnits-1
self:T("Alive Units: "..self.AliveUnits)
end
end
end
end
function SPAWN:_OnTakeOff(EventData)
self:F(self.SpawnTemplatePrefix)
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
self:T({"TakeOff event: "..EventPrefix})
if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
self:T("self.Landed = false")
SpawnGroup:SetState(SpawnGroup,"Spawn_Landed",false)
end
end
end
end
function SPAWN:_OnLand(EventData)
self:F(self.SpawnTemplatePrefix)
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
self:T({"Land event: "..EventPrefix})
if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
SpawnGroup:SetState(SpawnGroup,"Spawn_Landed",true)
if self.RepeatOnLanding then
local SpawnGroupIndex=self:GetSpawnIndexFromGroup(SpawnGroup)
self:T({"Landed:","ReSpawn:",SpawnGroup:GetName(),SpawnGroupIndex})
SCHEDULER:New(nil,self.ReSpawn,{self,SpawnGroupIndex},3)
end
end
end
end
end
function SPAWN:_OnEngineShutDown(EventData)
self:F(self.SpawnTemplatePrefix)
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
self:T({"EngineShutdown event: "..EventPrefix})
if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
local Landed=SpawnGroup:GetState(SpawnGroup,"Spawn_Landed")
if Landed and self.RepeatOnEngineShutDown then
local SpawnGroupIndex=self:GetSpawnIndexFromGroup(SpawnGroup)
self:T({"EngineShutDown: ","ReSpawn:",SpawnGroup:GetName(),SpawnGroupIndex})
SCHEDULER:New(nil,self.ReSpawn,{self,SpawnGroupIndex},3)
end
end
end
end
end
function SPAWN:_Scheduler()
self:F2({"_Scheduler",self.SpawnTemplatePrefix,self.SpawnAliasPrefix,self.SpawnIndex,self.SpawnMaxGroups,self.SpawnMaxUnitsAlive})
self:Spawn()
return true
end
function SPAWN:_SpawnCleanUpScheduler()
self:F({"CleanUp Scheduler:",self.SpawnTemplatePrefix})
local SpawnGroup,SpawnCursor=self:GetFirstAliveGroup()
self:T({"CleanUp Scheduler:",SpawnGroup,SpawnCursor})
local IsHelo=false
while SpawnGroup do
IsHelo=SpawnGroup:IsHelicopter()
local SpawnUnits=SpawnGroup:GetUnits()
for UnitID,UnitData in pairs(SpawnUnits)do
local SpawnUnit=UnitData
local SpawnUnitName=SpawnUnit:GetName()
self.SpawnCleanUpTimeStamps[SpawnUnitName]=self.SpawnCleanUpTimeStamps[SpawnUnitName]or{}
local Stamp=self.SpawnCleanUpTimeStamps[SpawnUnitName]
self:T({SpawnUnitName,Stamp})
if Stamp.Vec2 then
if(SpawnUnit:InAir()==false and SpawnUnit:GetVelocityKMH()<1)or IsHelo then
local NewVec2=SpawnUnit:GetVec2()or{x=0,y=0}
if(Stamp.Vec2.x==NewVec2.x and Stamp.Vec2.y==NewVec2.y)or(SpawnUnit:GetLife()<=1)then
if Stamp.Time+self.SpawnCleanUpInterval<timer.getTime()then
self:T({"CleanUp Scheduler:","ReSpawning:",SpawnGroup:GetName()})
self:ReSpawn(SpawnCursor)
Stamp.Vec2=nil
Stamp.Time=nil
end
else
Stamp.Time=timer.getTime()
Stamp.Vec2=SpawnUnit:GetVec2()
end
else
Stamp.Vec2=nil
Stamp.Time=nil
end
else
if SpawnUnit:InAir()==false or(IsHelo and SpawnUnit:GetLife()<=1)then
Stamp.Vec2=SpawnUnit:GetVec2()or{x=0,y=0}
if(SpawnUnit:GetVelocityKMH()<1)then
Stamp.Time=timer.getTime()
end
else
Stamp.Time=nil
Stamp.Vec2=nil
end
end
end
SpawnGroup,SpawnCursor=self:GetNextAliveGroup(SpawnCursor)
self:T({"CleanUp Scheduler:",SpawnGroup,SpawnCursor})
end
return true
end
SPAWNSTATIC={
ClassName="SPAWNSTATIC",
SpawnIndex=0,
}
function SPAWNSTATIC:NewFromStatic(SpawnTemplateName,SpawnCountryID)
local self=BASE:Inherit(self,BASE:New())
local TemplateStatic,CoalitionID,CategoryID,CountryID=_DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
if TemplateStatic then
self.SpawnTemplatePrefix=SpawnTemplateName
self.TemplateStaticUnit=UTILS.DeepCopy(TemplateStatic.units[1])
self.CountryID=SpawnCountryID or CountryID
self.CategoryID=CategoryID
self.CoalitionID=CoalitionID
self.SpawnIndex=0
else
error("SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '"..tostring(SpawnTemplateName).."'")
end
self:SetEventPriority(5)
return self
end
function SPAWNSTATIC:NewFromTemplate(SpawnTemplate,CountryID)
local self=BASE:Inherit(self,BASE:New())
self.TemplateStaticUnit=UTILS.DeepCopy(SpawnTemplate)
self.SpawnTemplatePrefix=SpawnTemplate.name
self.CountryID=CountryID or country.id.USA
return self
end
function SPAWNSTATIC:NewFromType(StaticType,StaticCategory,CountryID)
local self=BASE:Inherit(self,BASE:New())
self.InitStaticType=StaticType
self.InitStaticCategory=StaticCategory
self.CountryID=CountryID or country.id.USA
self.SpawnTemplatePrefix=self.InitStaticType
self.InitStaticCoordinate=COORDINATE:New(0,0,0)
self.InitStaticHeading=0
return self
end
function SPAWNSTATIC:InitCoordinate(Coordinate)
self.InitStaticCoordinate=Coordinate
return self
end
function SPAWNSTATIC:InitHeading(Heading)
self.InitStaticHeading=Heading
return self
end
function SPAWNSTATIC:InitLivery(LiveryName)
self.InitStaticLivery=LiveryName
return self
end
function SPAWNSTATIC:InitType(StaticType)
self.InitStaticType=StaticType
return self
end
function SPAWNSTATIC:InitShape(StaticShape)
self.InitStaticShape=StaticShape
return self
end
function SPAWNSTATIC:InitFARP(CallsignID,Frequency,Modulation)
self.InitFarp=true
self.InitFarpCallsignID=CallsignID or 1
self.InitFarpFreq=Frequency or 127.5
self.InitFarpModu=Modulation or 0
return self
end
function SPAWNSTATIC:InitCargoMass(Mass)
self.InitStaticCargoMass=Mass
return self
end
function SPAWNSTATIC:InitCargo(IsCargo)
self.InitStaticCargo=IsCargo
return self
end
function SPAWNSTATIC:InitDead(IsDead)
self.InitStaticDead=IsDead
return self
end
function SPAWNSTATIC:InitCountry(CountryID)
self.CountryID=CountryID
return self
end
function SPAWNSTATIC:InitNamePrefix(NamePrefix)
self.SpawnTemplatePrefix=NamePrefix
return self
end
function SPAWNSTATIC:InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle)
self.InitLinkUnit=Unit
self.InitOffsetX=OffsetX or 0
self.InitOffsetY=OffsetY or 0
self.InitOffsetAngle=OffsetAngle or 0
return self
end
function SPAWNSTATIC:Spawn(Heading,NewName)
if Heading then
self.InitStaticHeading=Heading
end
if NewName then
self.InitStaticName=NewName
end
return self:_SpawnStatic(self.TemplateStaticUnit,self.CountryID)
end
function SPAWNSTATIC:SpawnFromPointVec2(PointVec2,Heading,NewName)
local vec2={x=PointVec2:GetX(),y=PointVec2:GetY()}
local Coordinate=COORDINATE:NewFromVec2(vec2)
return self:SpawnFromCoordinate(Coordinate,Heading,NewName)
end
function SPAWNSTATIC:SpawnFromCoordinate(Coordinate,Heading,NewName)
self.InitStaticCoordinate=Coordinate
if Heading then
self.InitStaticHeading=Heading
end
if NewName then
self.InitStaticName=NewName
end
return self:_SpawnStatic(self.TemplateStaticUnit,self.CountryID)
end
function SPAWNSTATIC:SpawnFromZone(Zone,Heading,NewName)
local Static=self:SpawnFromPointVec2(Zone:GetPointVec2(),Heading,NewName)
return Static
end
function SPAWNSTATIC:_SpawnStatic(Template,CountryID)
Template=Template or{}
local CountryID=CountryID or self.CountryID
if self.InitStaticType then
Template.type=self.InitStaticType
end
if self.InitStaticCategory then
Template.category=self.InitStaticCategory
end
if self.InitStaticCoordinate then
Template.x=self.InitStaticCoordinate.x
Template.y=self.InitStaticCoordinate.z
Template.alt=self.InitStaticCoordinate.y
end
if self.InitStaticHeading then
Template.heading=math.rad(self.InitStaticHeading)
end
if self.InitStaticShape then
Template.shape_name=self.InitStaticShape
end
if self.InitStaticLivery then
Template.livery_id=self.InitStaticLivery
end
if self.InitStaticDead~=nil then
Template.dead=self.InitStaticDead
end
if self.InitStaticCargo~=nil then
Template.canCargo=self.InitStaticCargo
end
if self.InitStaticCargoMass~=nil then
Template.mass=self.InitStaticCargoMass
end
if self.InitLinkUnit then
Template.linkUnit=self.InitLinkUnit:GetID()
Template.linkOffset=true
Template.offsets={}
Template.offsets.y=self.InitOffsetY
Template.offsets.x=self.InitOffsetX
Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle)or 0
end
if self.InitFarp then
Template.heliport_callsign_id=self.InitFarpCallsignID
Template.heliport_frequency=self.InitFarpFreq
Template.heliport_modulation=self.InitFarpModu
Template.unitId=nil
end
self.SpawnIndex=self.SpawnIndex+1
Template.name=self.InitStaticName or string.format("%s#%05d",self.SpawnTemplatePrefix,self.SpawnIndex)
local mystatic=_DATABASE:AddStatic(Template.name)
self:T(Template)
local Static=nil
if self.InitFarp then
local TemplateGroup={}
TemplateGroup.units={}
TemplateGroup.units[1]=Template
TemplateGroup.visible=true
TemplateGroup.hidden=false
TemplateGroup.x=Template.x
TemplateGroup.y=Template.y
TemplateGroup.name=Template.name
self:T("Spawning FARP")
self:T({Template=Template})
self:T({TemplateGroup=TemplateGroup})
Static=coalition.addGroup(CountryID,-1,TemplateGroup)
local Event={
id=EVENTS.Birth,
time=timer.getTime(),
initiator=Static
}
world.onEvent(Event)
else
self:T("Spawning Static")
self:T2({Template=Template})
Static=coalition.addStaticObject(CountryID,Template)
end
return mystatic
end
TIMER={
ClassName="TIMER",
lid=nil,
}
_TIMERID=0
TIMER.version="0.1.2"
function TIMER:New(Function,...)
local self=BASE:Inherit(self,BASE:New())
self.func=Function
self.para=arg or{}
self.ncalls=0
self.isrunning=false
_TIMERID=_TIMERID+1
self.uid=_TIMERID
self.lid=string.format("TIMER UID=%d | ",self.uid)
return self
end
function TIMER:Start(Tstart,dT,Duration)
local Tnow=timer.getTime()
self.Tstart=Tstart and Tnow+math.max(Tstart,0.001)or Tnow+0.001
self.dT=dT
if Duration then
self.Tstop=self.Tstart+Duration
end
self.tid=timer.scheduleFunction(self._Function,self,self.Tstart)
self.lid=string.format("TIMER UID=%d/%d | ",self.uid,self.tid)
self.isrunning=true
self:T(self.lid..string.format("Starting Timer in %.3f sec, dT=%s, Tstop=%s",self.Tstart-Tnow,tostring(self.dT),tostring(self.Tstop)))
return self
end
function TIMER:StartIf(Condition,Tstart,dT,Duration)
if Condition then
self:Start(Tstart,dT,Duration)
end
return self
end
function TIMER:Stop(Delay)
if Delay and Delay>0 then
self.Tstop=timer.getTime()+Delay
else
if self.tid then
self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!",self.ncalls))
timer.removeFunction(self.tid)
self.isrunning=false
end
end
return self
end
function TIMER:SetMaxFunctionCalls(Nmax)
self.ncallsMax=Nmax
return self
end
function TIMER:SetTimeInterval(dT)
self.dT=dT
return self
end
function TIMER:IsRunning()
return self.isrunning
end
function TIMER:_Function(time)
self.func(unpack(self.para))
self.ncalls=self.ncalls+1
local Tnext=self.dT and time+self.dT or nil
local stopme=false
if Tnext==nil then
self:T(self.lid..string.format("No next time as dT=nil ==> Stopping timer after %d function calls",self.ncalls))
stopme=true
elseif self.Tstop and Tnext>self.Tstop then
self:T(self.lid..string.format("Stop time passed %.3f > %.3f ==> Stopping timer after %d function calls",Tnext,self.Tstop,self.ncalls))
stopme=true
elseif self.ncallsMax and self.ncalls>=self.ncallsMax then
self:T(self.lid..string.format("Max function calls Nmax=%d reached ==> Stopping timer after %d function calls",self.ncallsMax,self.ncalls))
stopme=true
end
if stopme then
self:Stop()
return nil
else
return Tnext
end
end
do
GOAL={
ClassName="GOAL",
}
GOAL.Players={}
GOAL.TotalContributions=0
function GOAL:New()
local self=BASE:Inherit(self,FSM:New())
self:F({})
self:SetStartState("Pending")
self:AddTransition("*","Achieved","Achieved")
self:SetEventPriority(5)
return self
end
function GOAL:AddPlayerContribution(PlayerName)
self:F({PlayerName})
self.Players[PlayerName]=self.Players[PlayerName]or 0
self.Players[PlayerName]=self.Players[PlayerName]+1
self.TotalContributions=self.TotalContributions+1
end
function GOAL:GetPlayerContribution(PlayerName)
return self.Players[PlayerName]or 0
end
function GOAL:GetPlayerContributions()
return self.Players or{}
end
function GOAL:GetTotalContributions()
return self.TotalContributions or 0
end
function GOAL:IsAchieved()
return self:Is("Achieved")
end
end
do
SPOT={
ClassName="SPOT",
}
function SPOT:New(Recce)
local self=BASE:Inherit(self,FSM:New())
self:F({})
self:SetStartState("Off")
self:AddTransition("Off","LaseOn","On")
self:AddTransition("Off","LaseOnCoordinate","On")
self:AddTransition("On","Lasing","On")
self:AddTransition({"On","Destroyed"},"LaseOff","Off")
self:AddTransition("*","Destroyed","Destroyed")
self.Recce=Recce
self.LaseScheduler=SCHEDULER:New(self)
self:SetEventPriority(5)
self.Lasing=false
return self
end
function SPOT:onafterLaseOn(From,Event,To,Target,LaserCode,Duration)
self:F({"LaseOn",Target,LaserCode,Duration})
local function StopLase(self)
self:LaseOff()
end
self.Target=Target
self.LaserCode=LaserCode
self.Lasing=true
local RecceDcsUnit=self.Recce:GetDCSObject()
local relativespot=self.relstartpos or{x=0,y=2,z=0}
self.SpotIR=Spot.createInfraRed(RecceDcsUnit,relativespot,Target:GetPointVec3():AddY(1):GetVec3())
self.SpotLaser=Spot.createLaser(RecceDcsUnit,relativespot,Target:GetPointVec3():AddY(1):GetVec3(),LaserCode)
if Duration then
self.ScheduleID=self.LaseScheduler:Schedule(self,StopLase,{self},Duration)
end
self:HandleEvent(EVENTS.Dead)
self:__Lasing(-1)
end
function SPOT:onafterLaseOnCoordinate(From,Event,To,Coordinate,LaserCode,Duration)
self:F({"LaseOnCoordinate",Coordinate,LaserCode,Duration})
local function StopLase(self)
self:LaseOff()
end
self.Target=nil
self.TargetCoord=Coordinate
self.LaserCode=LaserCode
self.Lasing=true
local RecceDcsUnit=self.Recce:GetDCSObject()
self.SpotIR=Spot.createInfraRed(RecceDcsUnit,{x=0,y=1,z=0},Coordinate:GetVec3())
self.SpotLaser=Spot.createLaser(RecceDcsUnit,{x=0,y=1,z=0},Coordinate:GetVec3(),LaserCode)
if Duration then
self.ScheduleID=self.LaseScheduler:Schedule(self,StopLase,{self},Duration)
end
self:__Lasing(-1)
end
function SPOT:OnEventDead(EventData)
self:F({Dead=EventData.IniDCSUnitName,Target=self.Target})
if self.Target then
if EventData.IniDCSUnitName==self.Target:GetName()then
self:F({"Target dead ",self.Target:GetName()})
self:Destroyed()
self:LaseOff()
end
end
end
function SPOT:onafterLasing(From,Event,To)
if self.Target and self.Target:IsAlive()then
self.SpotIR:setPoint(self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/100):AddX(math.random(-100,100)/100):GetVec3())
self.SpotLaser:setPoint(self.Target:GetPointVec3():AddY(1):GetVec3())
self:__Lasing(-0.2)
elseif self.TargetCoord then
local irvec3={x=self.TargetCoord.x+math.random(-100,100)/100,y=self.TargetCoord.y+math.random(-100,100)/100,z=self.TargetCoord.z}
local lsvec3={x=self.TargetCoord.x,y=self.TargetCoord.y,z=self.TargetCoord.z}
self.SpotIR:setPoint(irvec3)
self.SpotLaser:setPoint(lsvec3)
self:__Lasing(-0.25)
else
self:F({"Target is not alive",self.Target:IsAlive()})
end
end
function SPOT:onafterLaseOff(From,Event,To)
self:F({"Stopped lasing for ",self.Target and self.Target:GetName()or"coord",SpotIR=self.SportIR,SpotLaser=self.SpotLaser})
self.Lasing=false
self.SpotIR:destroy()
self.SpotLaser:destroy()
self.SpotIR=nil
self.SpotLaser=nil
if self.ScheduleID then
self.LaseScheduler:Stop(self.ScheduleID)
end
self.ScheduleID=nil
self.Target=nil
return self
end
function SPOT:IsLasing()
return self.Lasing
end
function SPOT:SetRelativeStartPosition(position)
self.relstartpos=position or{x=0,y=2,z=0}
return self
end
end
MARKEROPS_BASE={
ClassName="MARKEROPS",
Tag="mytag",
Keywords={},
version="0.1.0",
debug=false,
Casesensitive=true,
}
function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
local self=BASE:Inherit(self,FSM:New())
self.lid=string.format("MARKEROPS_BASE %s | ",tostring(self.version))
self.Tag=Tagname or"mytag"
self.Keywords=Keywords or{}
self.debug=false
self.Casesensitive=true
if Casesensitive and Casesensitive==false then
self.Casesensitive=false
end
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","MarkAdded","*")
self:AddTransition("*","MarkChanged","*")
self:AddTransition("*","MarkDeleted","*")
self:AddTransition("Running","Stop","Stopped")
self:HandleEvent(EVENTS.MarkAdded,self.OnEventMark)
self:HandleEvent(EVENTS.MarkChange,self.OnEventMark)
self:HandleEvent(EVENTS.MarkRemoved,self.OnEventMark)
self:I(self.lid..string.format("started for %s",self.Tag))
self:__Start(1)
return self
end
function MARKEROPS_BASE:OnEventMark(Event)
self:T({Event})
if Event==nil or Event.idx==nil then
self:E("Skipping onEvent. Event or Event.idx unknown.")
return true
end
local vec3={y=Event.pos.y,x=Event.pos.x,z=Event.pos.z}
local coord=COORDINATE:NewFromVec3(vec3)
if self.debug then
local coordtext=coord:ToStringLLDDM()
local text=tostring(Event.text)
local m=MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
end
if Event.id==world.event.S_EVENT_MARK_ADDED then
self:T({event="S_EVENT_MARK_ADDED",carrier=self.groupname,vec3=Event.pos})
local Eventtext=tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext)then
local matchtable=self:_MatchKeywords(Eventtext)
self:MarkAdded(Eventtext,matchtable,coord)
end
end
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
self:T({event="S_EVENT_MARK_CHANGE",carrier=self.groupname,vec3=Event.pos})
local Eventtext=tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext)then
local matchtable=self:_MatchKeywords(Eventtext)
self:MarkChanged(Eventtext,matchtable,coord)
end
end
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
self:T({event="S_EVENT_MARK_REMOVED",carrier=self.groupname,vec3=Event.pos})
local Eventtext=tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext)then
self:MarkDeleted()
end
end
end
end
function MARKEROPS_BASE:_MatchTag(Eventtext)
local matches=false
if not self.Casesensitive then
local type=string.lower(self.Tag)
if string.find(string.lower(Eventtext),type)then
matches=true
end
else
local type=self.Tag
if string.find(Eventtext,type)then
matches=true
end
end
return matches
end
function MARKEROPS_BASE:_MatchKeywords(Eventtext)
local matchtable={}
local keytable=self.Keywords
for _index,_word in pairs(keytable)do
if string.find(string.lower(Eventtext),string.lower(_word))then
table.insert(matchtable,_word)
end
end
return matchtable
end
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord)
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
end
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord)
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
end
function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To)
self:T({self.lid,From,Event,To})
end
function MARKEROPS_BASE:onenterStopped(From,Event,To)
self:T({self.lid,From,Event,To})
self:UnHandleEvent(EVENTS.MarkAdded)
self:UnHandleEvent(EVENTS.MarkChange)
self:UnHandleEvent(EVENTS.MarkRemoved)
end
TEXTANDSOUND={
ClassName="TEXTANDSOUND",
version="0.0.1",
lid="",
locale="en",
entries={},
textclass="",
}
function TEXTANDSOUND:New(ClassName,Defaultlocale)
local self=BASE:Inherit(self,BASE:New())
self.lid=string.format("%s (%s) | ",self.ClassName,self.version)
self.locale=Defaultlocale or(_SETTINGS:GetLocale()or"en")
self.textclass=ClassName or"none"
self.entries={}
local initentry={}
initentry.Classname=ClassName
initentry.Data={}
initentry.Locale=self.locale
self.entries[self.locale]=initentry
self:I(self.lid.."Instantiated.")
self:T({self.entries[self.locale]})
return self
end
function TEXTANDSOUND:AddEntry(Locale,ID,Text,Soundfile,Soundlength,Subtitle)
self:T(self.lid.."AddEntry")
local locale=Locale or self.locale
local dataentry={}
dataentry.ID=ID or"1"
dataentry.Text=Text or"none"
dataentry.Soundfile=Soundfile
dataentry.Soundlength=Soundlength or 0
dataentry.Subtitle=Subtitle
if not self.entries[locale]then
local initentry={}
initentry.Classname=self.textclass
initentry.Data={}
initentry.Locale=locale
self.entries[locale]=initentry
end
self.entries[locale].Data[ID]=dataentry
self:T({self.entries[locale].Data})
return self
end
function TEXTANDSOUND:GetEntry(ID,Locale)
self:T(self.lid.."GetEntry")
local locale=Locale or self.locale
if not self.entries[locale]then
locale=self.locale
end
local Text,Soundfile,Soundlength,Subtitle=nil,nil,0,nil
if self.entries[locale]then
if self.entries[locale].Data then
local data=self.entries[locale].Data[ID]
if data then
Text=data.Text
Soundfile=data.Soundfile
Soundlength=data.Soundlength
Subtitle=data.Subtitle
elseif self.entries[self.locale].Data[ID]then
local data=self.entries[self.locale].Data[ID]
Text=data.Text
Soundfile=data.Soundfile
Soundlength=data.Soundlength
Subtitle=data.Subtitle
end
end
else
return nil,nil,0,nil
end
return Text,Soundfile,Soundlength,Subtitle
end
function TEXTANDSOUND:GetDefaultLocale()
self:T(self.lid.."GetDefaultLocale")
return self.locale
end
function TEXTANDSOUND:SetDefaultLocale(locale)
self:T(self.lid.."SetDefaultLocale")
self.locale=locale or"en"
return self
end
function TEXTANDSOUND:HasLocale(Locale)
self:T(self.lid.."HasLocale")
return self.entries[Locale]and true or false
end
function TEXTANDSOUND:FlushToLog()
self:I(self.lid.."Flushing entries:")
local text=string.format("Textclass: %s | Default Locale: %s",self.textclass,self.locale)
for _,_entry in pairs(self.entries)do
local entry=_entry
local text=string.format("Textclassname: %s | Locale: %s",entry.Classname,entry.Locale)
self:I(text)
for _ID,_data in pairs(entry.Data)do
local data=_data
local text=string.format("ID: %s\nText: %s\nSoundfile: %s With length: %d\nSubtitle: %s",tostring(_ID),data.Text or"none",data.Soundfile or"none",data.Soundlength or 0,data.Subtitle or"none")
self:I(text)
end
end
return self
end
OBJECT={
ClassName="OBJECT",
ObjectName="",
}
function OBJECT:New(ObjectName,Test)
local self=BASE:Inherit(self,BASE:New())
self:F2(ObjectName)
self.ObjectName=ObjectName
return self
end
function OBJECT:GetID()
local DCSObject=self:GetDCSObject()
if DCSObject then
local ObjectID=DCSObject:getID()
return ObjectID
end
BASE:E({"Cannot GetID",Name=self.ObjectName,Class=self:GetClassName()})
return nil
end
function OBJECT:Destroy()
local DCSObject=self:GetDCSObject()
if DCSObject then
DCSObject:destroy(false)
return true
end
BASE:E({"Cannot Destroy",Name=self.ObjectName,Class=self:GetClassName()})
return nil
end
IDENTIFIABLE={
ClassName="IDENTIFIABLE",
IdentifiableName="",
}
local _CategoryName={
[Unit.Category.AIRPLANE]="Airplane",
[Unit.Category.HELICOPTER]="Helicopter",
[Unit.Category.GROUND_UNIT]="Ground Identifiable",
[Unit.Category.SHIP]="Ship",
[Unit.Category.STRUCTURE]="Structure",
}
function IDENTIFIABLE:New(IdentifiableName)
local self=BASE:Inherit(self,OBJECT:New(IdentifiableName))
self:F2(IdentifiableName)
self.IdentifiableName=IdentifiableName
return self
end
function IDENTIFIABLE:IsAlive()
self:F3(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableIsAlive=DCSIdentifiable:isExist()
return IdentifiableIsAlive
end
return false
end
function IDENTIFIABLE:GetName()
local IdentifiableName=self.IdentifiableName
return IdentifiableName
end
function IDENTIFIABLE:GetTypeName()
self:F2(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableTypeName=DCSIdentifiable:getTypeName()
self:T3(IdentifiableTypeName)
return IdentifiableTypeName
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:GetCategory()
self:F2(self.ObjectName)
local DCSObject=self:GetDCSObject()
if DCSObject then
local ObjectCategory=DCSObject:getCategory()
self:T3(ObjectCategory)
return ObjectCategory
end
return nil
end
function IDENTIFIABLE:GetCategoryName()
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableCategoryName=_CategoryName[self:GetDesc().category]
return IdentifiableCategoryName
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:GetCoalition()
self:F2(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableCoalition=DCSIdentifiable:getCoalition()
self:T3(IdentifiableCoalition)
return IdentifiableCoalition
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:GetCoalitionName()
self:F2(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableCoalition=DCSIdentifiable:getCoalition()
self:T3(IdentifiableCoalition)
return UTILS.GetCoalitionName(IdentifiableCoalition)
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:GetCountry()
self:F2(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableCountry=DCSIdentifiable:getCountry()
self:T3(IdentifiableCountry)
return IdentifiableCountry
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:GetCountryName()
self:F2(self.IdentifiableName)
local countryid=self:GetCountry()
for name,id in pairs(country.id)do
if countryid==id then
return name
end
end
end
function IDENTIFIABLE:GetDesc()
self:F2(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableDesc=DCSIdentifiable:getDesc()
self:T2(IdentifiableDesc)
return IdentifiableDesc
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:HasAttribute(AttributeName)
self:F2(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableHasAttribute=DCSIdentifiable:hasAttribute(AttributeName)
self:T2(IdentifiableHasAttribute)
return IdentifiableHasAttribute
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:GetCallsign()
return''
end
function IDENTIFIABLE:GetThreatLevel()
return 0,"Scenery"
end
POSITIONABLE={
ClassName="POSITIONABLE",
PositionableName="",
coordinate=nil,
pointvec3=nil,
}
POSITIONABLE.__={}
POSITIONABLE.__.Cargo={}
function POSITIONABLE:New(PositionableName)
local self=BASE:Inherit(self,IDENTIFIABLE:New(PositionableName))
self.PositionableName=PositionableName
return self
end
function POSITIONABLE:Destroy(GenerateEvent)
self:F2(self.ObjectName)
local DCSObject=self:GetDCSObject()
if DCSObject then
local UnitGroup=self:GetGroup()
local UnitGroupName=UnitGroup:GetName()
self:F({UnitGroupName=UnitGroupName})
if GenerateEvent and GenerateEvent==true then
if self:IsAir()then
self:CreateEventCrash(timer.getTime(),DCSObject)
else
self:CreateEventDead(timer.getTime(),DCSObject)
end
elseif GenerateEvent==false then
else
self:CreateEventRemoveUnit(timer.getTime(),DCSObject)
end
USERFLAG:New(UnitGroupName):Set(100)
DCSObject:destroy()
end
return nil
end
function POSITIONABLE:GetDCSObject()
return nil
end
function POSITIONABLE:GetPosition()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionablePosition=DCSPositionable:getPosition()
self:T3(PositionablePosition)
return PositionablePosition
end
BASE:E({"Cannot GetPosition",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetOrientation()
local position=self:GetPosition()
if position then
return position.x,position.y,position.z
else
BASE:E({"Cannot GetOrientation",Positionable=self,Alive=self:IsAlive()})
return nil,nil,nil
end
end
function POSITIONABLE:GetOrientationX()
local position=self:GetPosition()
if position then
return position.x
else
BASE:E({"Cannot GetOrientationX",Positionable=self,Alive=self:IsAlive()})
return nil
end
end
function POSITIONABLE:GetOrientationY()
local position=self:GetPosition()
if position then
return position.y
else
BASE:E({"Cannot GetOrientationY",Positionable=self,Alive=self:IsAlive()})
return nil
end
end
function POSITIONABLE:GetOrientationZ()
local position=self:GetPosition()
if position then
return position.z
else
BASE:E({"Cannot GetOrientationZ",Positionable=self,Alive=self:IsAlive()})
return nil
end
end
function POSITIONABLE:GetPositionVec3()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionablePosition=DCSPositionable:getPosition().p
self:T3(PositionablePosition)
return PositionablePosition
end
BASE:E({"Cannot GetPositionVec3",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetVec3()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local vec3=DCSPositionable:getPoint()
if vec3 then
return vec3
else
self:E("ERROR: Cannot get vec3!")
end
end
self:E({"Cannot GetVec3",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetVec2()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local Vec3=DCSPositionable:getPoint()
return{x=Vec3.x,y=Vec3.z}
end
self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetPointVec2()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableVec3=DCSPositionable:getPosition().p
local PositionablePointVec2=POINT_VEC2:NewFromVec3(PositionableVec3)
return PositionablePointVec2
end
self:E({"Cannot GetPointVec2",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetPointVec3()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableVec3=self:GetPositionVec3()
if false and self.pointvec3 then
self.pointvec3.x=PositionableVec3.x
self.pointvec3.y=PositionableVec3.y
self.pointvec3.z=PositionableVec3.z
else
self.pointvec3=POINT_VEC3:NewFromVec3(PositionableVec3)
end
return self.pointvec3
end
BASE:E({"Cannot GetPointVec3",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetCoord()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableVec3=self:GetVec3()
if self.coordinate then
self.coordinate:UpdateFromVec3(PositionableVec3)
else
self.coordinate=COORDINATE:NewFromVec3(PositionableVec3)
end
return self.coordinate
end
BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetCoordinate()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableVec3=self:GetVec3()
local coord=COORDINATE:NewFromVec3(PositionableVec3)
local heading=self:GetHeading()
coord.Heading=heading
return coord
end
self:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetOffsetCoordinate(x,y,z)
x=x or 0
y=y or 0
z=z or 0
local X=self:GetOrientationX()
local Y=self:GetOrientationY()
local Z=self:GetOrientationZ()
local A={x=x,y=y,z=z}
local x={x=X.x*A.x,y=X.y*A.x,z=X.z*A.x}
local y={x=Y.x*A.y,y=Y.y*A.y,z=Y.z*A.y}
local z={x=Z.x*A.z,y=Z.y*A.z,z=Z.z*A.z}
local a={x=x.x+y.x+z.x,y=x.y+y.y+z.y,z=x.z+y.z+z.z}
local u=self:GetVec3()
local v={x=a.x+u.x,y=a.y+u.y,z=a.z+u.z}
local coord=COORDINATE:NewFromVec3(v)
return coord
end
function POSITIONABLE:GetRandomVec3(Radius)
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionablePointVec3=DCSPositionable:getPosition().p
if Radius then
local PositionableRandomVec3={}
local angle=math.random()*math.pi*2;
PositionableRandomVec3.x=PositionablePointVec3.x+math.cos(angle)*math.random()*Radius;
PositionableRandomVec3.y=PositionablePointVec3.y
PositionableRandomVec3.z=PositionablePointVec3.z+math.sin(angle)*math.random()*Radius;
self:T3(PositionableRandomVec3)
return PositionableRandomVec3
else
self:F("Radius is nil, returning the PointVec3 of the POSITIONABLE",PositionablePointVec3)
return PositionablePointVec3
end
end
BASE:E({"Cannot GetRandomVec3",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetBoundingBox()
self:F2()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableDesc=DCSPositionable:getDesc()
if PositionableDesc then
local PositionableBox=PositionableDesc.box
return PositionableBox
end
end
BASE:E({"Cannot GetBoundingBox",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetObjectSize()
local box=self:GetBoundingBox()
if box then
local x=box.max.x+math.abs(box.min.x)
local y=box.max.y+math.abs(box.min.y)
local z=box.max.z+math.abs(box.min.z)
return math.max(x,z),x,y,z
end
return 0,0,0,0
end
function POSITIONABLE:GetBoundingRadius(MinDist)
self:F2()
local Box=self:GetBoundingBox()
local boxmin=MinDist or 0
if Box then
local X=Box.max.x-Box.min.x
local Z=Box.max.z-Box.min.z
local CX=X/2
local CZ=Z/2
return math.max(math.max(CX,CZ),boxmin)
end
BASE:E({"Cannot GetBoundingRadius",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetAltitude()
self:F2()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionablePointVec3=DCSPositionable:getPoint()
return PositionablePointVec3.y
end
BASE:E({"Cannot GetAltitude",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:IsAboveRunway()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local Vec2=self:GetVec2()
local SurfaceType=land.getSurfaceType(Vec2)
local IsAboveRunway=SurfaceType==land.SurfaceType.RUNWAY
self:T2(IsAboveRunway)
return IsAboveRunway
end
BASE:E({"Cannot IsAboveRunway",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetSize()
local DCSObject=self:GetDCSObject()
if DCSObject then
return 1
else
return 0
end
end
function POSITIONABLE:GetHeading()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionablePosition=DCSPositionable:getPosition()
if PositionablePosition then
local PositionableHeading=math.atan2(PositionablePosition.x.z,PositionablePosition.x.x)
if PositionableHeading<0 then
PositionableHeading=PositionableHeading+2*math.pi
end
PositionableHeading=PositionableHeading*180/math.pi
return PositionableHeading
end
end
self:E({"Cannot GetHeading",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:IsAir()
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitDescriptor=DCSUnit:getDesc()
self:T3({UnitDescriptor.category,Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
local IsAirResult=(UnitDescriptor.category==Unit.Category.AIRPLANE)or(UnitDescriptor.category==Unit.Category.HELICOPTER)
self:T3(IsAirResult)
return IsAirResult
end
self:E({"Cannot check IsAir",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:IsGround()
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitDescriptor=DCSUnit:getDesc()
self:T3({UnitDescriptor.category,Unit.Category.GROUND_UNIT})
local IsGroundResult=(UnitDescriptor.category==Unit.Category.GROUND_UNIT)
self:T3(IsGroundResult)
return IsGroundResult
end
self:E({"Cannot check IsGround",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:IsShip()
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitDescriptor=DCSUnit:getDesc()
self:T3({UnitDescriptor.category,Unit.Category.SHIP})
local IsShipResult=(UnitDescriptor.category==Unit.Category.SHIP)
self:T3(IsShipResult)
return IsShipResult
end
self:E({"Cannot check IsShip",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:IsSubmarine()
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitDescriptor=DCSUnit:getDesc()
if UnitDescriptor.attributes["Submarines"]==true then
return true
else
return false
end
end
self:E({"Cannot check IsSubmarine",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:InAir()
self:F2(self.PositionableName)
return nil
end
function POSITIONABLE:GetVelocity()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local Velocity=VELOCITY:New(self)
return Velocity
end
BASE:E({"Cannot GetVelocity",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetVelocityVec3()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable and DCSPositionable:isExist()then
local PositionableVelocityVec3=DCSPositionable:getVelocity()
self:T3(PositionableVelocityVec3)
return PositionableVelocityVec3
end
BASE:E({"Cannot GetVelocityVec3",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetRelativeVelocity(Positionable)
self:F2(self.PositionableName)
local v1=self:GetVelocityVec3()
local v2=Positionable:GetVelocityVec3()
local vtot=UTILS.VecAdd(v1,v2)
return UTILS.VecNorm(vtot)
end
function POSITIONABLE:GetHeight()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable and DCSPositionable:isExist()then
local PositionablePosition=DCSPositionable:getPosition()
if PositionablePosition then
local PositionableHeight=PositionablePosition.p.y
self:T2(PositionableHeight)
return PositionableHeight
end
end
return nil
end
function POSITIONABLE:GetVelocityKMH()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable and DCSPositionable:isExist()then
local VelocityVec3=self:GetVelocityVec3()
local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5
local Velocity=Velocity*3.6
self:T3(Velocity)
return Velocity
end
return 0
end
function POSITIONABLE:GetVelocityMPS()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable and DCSPositionable:isExist()then
local VelocityVec3=self:GetVelocityVec3()
local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5
self:T3(Velocity)
return Velocity
end
return 0
end
function POSITIONABLE:GetVelocityKNOTS()
self:F2(self.PositionableName)
return UTILS.MpsToKnots(self:GetVelocityMPS())
end
function POSITIONABLE:GetAoA()
local unitpos=self:GetPosition()
if unitpos then
local unitvel=self:GetVelocityVec3()
if unitvel and UTILS.VecNorm(unitvel)~=0 then
local wind=self:GetCoordinate():GetWindWithTurbulenceVec3()
unitvel.x=unitvel.x-wind.x
unitvel.y=unitvel.y-wind.y
unitvel.z=unitvel.z-wind.z
local AxialVel={}
AxialVel.x=UTILS.VecDot(unitpos.x,unitvel)
AxialVel.y=UTILS.VecDot(unitpos.y,unitvel)
AxialVel.z=UTILS.VecDot(unitpos.z,unitvel)
local AoA=math.acos(UTILS.VecDot({x=1,y=0,z=0},{x=AxialVel.x,y=AxialVel.y,z=0})/UTILS.VecNorm({x=AxialVel.x,y=AxialVel.y,z=0}))
if AxialVel.y>0 then
AoA=-AoA
end
return math.deg(AoA)
end
end
return nil
end
function POSITIONABLE:GetClimbAngle()
local unitpos=self:GetPosition()
if unitpos then
local unitvel=self:GetVelocityVec3()
if unitvel and UTILS.VecNorm(unitvel)~=0 then
local angle=math.asin(unitvel.y/UTILS.VecNorm(unitvel))
return math.deg(angle)
else
return 0
end
end
return nil
end
function POSITIONABLE:GetPitch()
local unitpos=self:GetPosition()
if unitpos then
return math.deg(math.asin(unitpos.x.y))
end
return nil
end
function POSITIONABLE:GetRoll()
local unitpos=self:GetPosition()
if unitpos then
local cp=UTILS.VecCross(unitpos.x,{x=0,y=1,z=0})
local dp=UTILS.VecDot(cp,unitpos.z)
local Roll=math.acos(dp/(UTILS.VecNorm(cp)*UTILS.VecNorm(unitpos.z)))
if unitpos.z.y>0 then
Roll=-Roll
end
return math.deg(Roll)
end
return nil
end
function POSITIONABLE:GetYaw()
local unitpos=self:GetPosition()
if unitpos then
local unitvel=self:GetVelocityVec3()
if unitvel and UTILS.VecNorm(unitvel)~=0 then
local AxialVel={}
AxialVel.x=UTILS.VecDot(unitpos.x,unitvel)
AxialVel.y=UTILS.VecDot(unitpos.y,unitvel)
AxialVel.z=UTILS.VecDot(unitpos.z,unitvel)
local Yaw=math.acos(UTILS.VecDot({x=1,y=0,z=0},{x=AxialVel.x,y=0,z=AxialVel.z})/UTILS.VecNorm({x=AxialVel.x,y=0,z=AxialVel.z}))
if AxialVel.z>0 then
Yaw=-Yaw
end
return Yaw
end
end
return nil
end
function POSITIONABLE:GetMessageText(Message,Name)
local DCSObject=self:GetDCSObject()
if DCSObject then
local Callsign=string.format("%s",((Name~=""and Name)or self:GetCallsign()~=""and self:GetCallsign())or self:GetName())
local MessageText=string.format("%s - %s",Callsign,Message)
return MessageText
end
return nil
end
function POSITIONABLE:GetMessage(Message,Duration,Name)
local DCSObject=self:GetDCSObject()
if DCSObject then
local MessageText=self:GetMessageText(Message,Name)
return MESSAGE:New(MessageText,Duration)
end
return nil
end
function POSITIONABLE:GetMessageType(Message,MessageType,Name)
local DCSObject=self:GetDCSObject()
if DCSObject then
local MessageText=self:GetMessageText(Message,Name)
return MESSAGE:NewType(MessageText,MessageType)
end
return nil
end
function POSITIONABLE:MessageToAll(Message,Duration,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessage(Message,Duration,Name):ToAll()
end
return nil
end
function POSITIONABLE:MessageToCoalition(Message,Duration,MessageCoalition,Name)
self:F2({Message,Duration})
local Name=Name or""
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessage(Message,Duration,Name):ToCoalition(MessageCoalition)
end
return nil
end
function POSITIONABLE:MessageTypeToCoalition(Message,MessageType,MessageCoalition,Name)
self:F2({Message,MessageType})
local Name=Name or""
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessageType(Message,MessageType,Name):ToCoalition(MessageCoalition)
end
return nil
end
function POSITIONABLE:MessageToRed(Message,Duration,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessage(Message,Duration,Name):ToRed()
end
return nil
end
function POSITIONABLE:MessageToBlue(Message,Duration,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessage(Message,Duration,Name):ToBlue()
end
return nil
end
function POSITIONABLE:MessageToClient(Message,Duration,Client,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessage(Message,Duration,Name):ToClient(Client)
end
return nil
end
function POSITIONABLE:MessageToUnit(Message,Duration,MessageUnit,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
if MessageUnit:IsAlive()then
self:GetMessage(Message,Duration,Name):ToUnit(MessageUnit)
else
BASE:E({"Message not sent to Unit; Unit is not alive...",Message=Message,MessageUnit=MessageUnit})
end
else
BASE:E({"Message not sent to Unit; Positionable is not alive ...",Message=Message,Positionable=self,MessageUnit=MessageUnit})
end
end
end
function POSITIONABLE:MessageToGroup(Message,Duration,MessageGroup,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
if MessageGroup:IsAlive()then
self:GetMessage(Message,Duration,Name):ToGroup(MessageGroup)
else
BASE:E({"Message not sent to Group; Group is not alive...",Message=Message,MessageGroup=MessageGroup})
end
else
BASE:E({
"Message not sent to Group; Positionable is not alive ...",
Message=Message,
Positionable=self,
MessageGroup=MessageGroup
})
end
end
return nil
end
function POSITIONABLE:MessageToUnit(Message,Duration,MessageUnit,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
if MessageUnit:IsAlive()then
self:GetMessage(Message,Duration,Name):ToUnit(MessageUnit)
else
BASE:E({"Message not sent to Unit; Unit is not alive...",Message=Message,MessageUnit=MessageUnit})
end
else
BASE:E({"Message not sent to Unit; Positionable is not alive ...",Message=Message,Positionable=self,MessageUnit=MessageUnit})
end
end
return nil
end
function POSITIONABLE:MessageTypeToGroup(Message,MessageType,MessageGroup,Name)
self:F2({Message,MessageType})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
self:GetMessageType(Message,MessageType,Name):ToGroup(MessageGroup)
end
end
return nil
end
function POSITIONABLE:MessageToSetGroup(Message,Duration,MessageSetGroup,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
MessageSetGroup:ForEachGroupAlive(function(MessageGroup)
self:GetMessage(Message,Duration,Name):ToGroup(MessageGroup)
end)
end
end
return nil
end
function POSITIONABLE:MessageToSetUnit(Message,Duration,MessageSetUnit,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
MessageSetUnit:ForEachUnit(
function(MessageGroup)
self:GetMessage(Message,Duration,Name):ToUnit(MessageGroup)
end
)
end
end
return nil
end
function POSITIONABLE:MessageToSetUnit(Message,Duration,MessageSetUnit,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
MessageSetUnit:ForEachUnit(
function(MessageGroup)
self:GetMessage(Message,Duration,Name):ToUnit(MessageGroup)
end
)
end
end
return nil
end
function POSITIONABLE:Message(Message,Duration,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessage(Message,Duration,Name):ToGroup(self)
end
return nil
end
function POSITIONABLE:GetRadio()
self:F2(self)
return RADIO:New(self)
end
function POSITIONABLE:GetBeacon()
self:F2(self)
return BEACON:New(self)
end
function POSITIONABLE:LaseUnit(Target,LaserCode,Duration)
self:F2()
LaserCode=LaserCode or math.random(1000,9999)
local RecceDcsUnit=self:GetDCSObject()
local TargetVec3=Target:GetVec3()
self:F("building spot")
self.Spot=SPOT:New(self)
self.Spot:LaseOn(Target,LaserCode,Duration)
self.LaserCode=LaserCode
return self.Spot
end
function POSITIONABLE:LaseCoordinate(Coordinate,LaserCode,Duration)
self:F2()
LaserCode=LaserCode or math.random(1000,9999)
self.Spot=SPOT:New(self)
self.Spot:LaseOnCoordinate(Coordinate,LaserCode,Duration)
self.LaserCode=LaserCode
return self.Spot
end
function POSITIONABLE:LaseOff()
self:F2()
if self.Spot then
self.Spot:LaseOff()
self.Spot=nil
end
return self
end
function POSITIONABLE:IsLasing()
self:F2()
local Lasing=false
if self.Spot then
Lasing=self.Spot:IsLasing()
end
return Lasing
end
function POSITIONABLE:GetSpot()
return self.Spot
end
function POSITIONABLE:GetLaserCode()
return self.LaserCode
end
do
function POSITIONABLE:AddCargo(Cargo)
self.__.Cargo[Cargo]=Cargo
return self
end
function POSITIONABLE:GetCargo()
return self.__.Cargo
end
function POSITIONABLE:RemoveCargo(Cargo)
self.__.Cargo[Cargo]=nil
return self
end
function POSITIONABLE:HasCargo(Cargo)
return self.__.Cargo[Cargo]
end
function POSITIONABLE:ClearCargo()
self.__.Cargo={}
end
function POSITIONABLE:IsCargoEmpty()
local IsEmpty=true
for _,Cargo in pairs(self.__.Cargo)do
IsEmpty=false
break
end
return IsEmpty
end
function POSITIONABLE:CargoItemCount()
local ItemCount=0
for CargoName,Cargo in pairs(self.__.Cargo)do
ItemCount=ItemCount+Cargo:GetCount()
end
return ItemCount
end
function POSITIONABLE:GetTroopCapacity()
local DCSunit=self:GetDCSObject()
local capacity=DCSunit:getDescentCapacity()
return capacity
end
function POSITIONABLE:GetCargoBayFreeWeight()
if not self.__.CargoBayWeightLimit then
self:SetCargoBayWeightLimit()
end
local CargoWeight=0
for CargoName,Cargo in pairs(self.__.Cargo)do
CargoWeight=CargoWeight+Cargo:GetWeight()
end
return self.__.CargoBayWeightLimit-CargoWeight
end
function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit)
if WeightLimit then
self.__.CargoBayWeightLimit=WeightLimit
elseif self.__.CargoBayWeightLimit~=nil then
else
local Desc=self:GetDesc()
self:F({Desc=Desc})
local TypeName=Desc.typeName or"Unknown Type"
if self:IsAir()then
local Weights={
["C-130"]=70000,
}
local massMax=Desc.massMax or 0
local maxTakeoff=Weights[TypeName]
if maxTakeoff then
massMax=maxTakeoff
end
local massEmpty=Desc.massEmpty or 0
local massFuelMax=Desc.fuelMassMax or 0
local relFuel=math.min(self:GetFuel()or 1.0,1.0)
local massFuel=massFuelMax*relFuel
local CargoWeight=massMax-(massEmpty+massFuel)
self:T(string.format("Setting Cargo bay weight limit [%s]=%d kg (Mass max=%d, empty=%d, fuelMax=%d kg (rel=%.3f), fuel=%d kg",TypeName,CargoWeight,massMax,massEmpty,massFuelMax,relFuel,massFuel))
self.__.CargoBayWeightLimit=CargoWeight
elseif self:IsShip()then
local Weights={
["Type_071"]=245000,
["LHA_Tarawa"]=500000,
["Ropucha-class"]=150000,
["Dry-cargo ship-1"]=70000,
["Dry-cargo ship-2"]=70000,
["Higgins_boat"]=3700,
["USS_Samuel_Chase"]=25000,
["LST_Mk2"]=2100000,
["speedboat"]=500,
["Seawise_Giant"]=261000000,
}
self.__.CargoBayWeightLimit=(Weights[TypeName]or 50000)
else
local Weights={
["AAV7"]=25,
["Bedford_MWD"]=8,
["Blitz_36-6700A"]=10,
["BMD-1"]=9,
["BMP-1"]=8,
["BMP-2"]=7,
["BMP-3"]=8,
["Boman"]=25,
["BTR-80"]=9,
["BTR-82A"]=9,
["BTR_D"]=12,
["Cobra"]=8,
["Land_Rover_101_FC"]=11,
["Land_Rover_109_S3"]=7,
["LAV-25"]=6,
["M-2 Bradley"]=6,
["M1043 HMMWV Armament"]=4,
["M1045 HMMWV TOW"]=4,
["M1126 Stryker ICV"]=9,
["M1134 Stryker ATGM"]=9,
["M2A1_halftrack"]=9,
["M-113"]=9,
["Marder"]=6,
["MCV-80"]=9,
["MLRS FDDM"]=4,
["MTLB"]=25,
["GAZ-66"]=8,
["GAZ-3307"]=12,
["GAZ-3308"]=14,
["Grad_FDDM"]=6,
["KAMAZ Truck"]=12,
["KrAZ6322"]=12,
["M 818"]=12,
["Tigr_233036"]=6,
["TPZ"]=10,
["UAZ-469"]=4,
["Ural-375"]=12,
["Ural-4320-31"]=14,
["Ural-4320 APA-5D"]=10,
["Ural-4320T"]=14,
["ZBD04A"]=7,
["VAB_Mephisto"]=8,
["tt_KORD"]=6,
["tt_DSHK"]=6,
["HL_KORD"]=6,
["HL_DSHK"]=6,
}
local CargoBayWeightLimit=(Weights[TypeName]or 0)*95
self.__.CargoBayWeightLimit=CargoBayWeightLimit
end
end
self:F({CargoBayWeightLimit=self.__.CargoBayWeightLimit})
end
function POSITIONABLE:GetCargoBayWeightLimit()
if self.__.CargoBayWeightLimit==nil then
self:SetCargoBayWeightLimit()
end
return self.__.CargoBayWeightLimit
end
end
function POSITIONABLE:Flare(FlareColor)
self:F2()
trigger.action.signalFlare(self:GetVec3(),FlareColor,0)
end
function POSITIONABLE:FlareWhite()
self:F2()
trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.White,0)
end
function POSITIONABLE:FlareYellow()
self:F2()
trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.Yellow,0)
end
function POSITIONABLE:FlareGreen()
self:F2()
trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.Green,0)
end
function POSITIONABLE:FlareRed()
self:F2()
local Vec3=self:GetVec3()
if Vec3 then
trigger.action.signalFlare(Vec3,trigger.flareColor.Red,0)
end
end
function POSITIONABLE:Smoke(SmokeColor,Range,AddHeight)
self:F2()
if Range then
local Vec3=self:GetRandomVec3(Range)
Vec3.y=Vec3.y+AddHeight or 0
trigger.action.smoke(Vec3,SmokeColor)
else
local Vec3=self:GetVec3()
Vec3.y=Vec3.y+AddHeight or 0
trigger.action.smoke(self:GetVec3(),SmokeColor)
end
end
function POSITIONABLE:SmokeGreen()
self:F2()
trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Green)
end
function POSITIONABLE:SmokeRed()
self:F2()
trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Red)
end
function POSITIONABLE:SmokeWhite()
self:F2()
trigger.action.smoke(self:GetVec3(),trigger.smokeColor.White)
end
function POSITIONABLE:SmokeOrange()
self:F2()
trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Orange)
end
function POSITIONABLE:SmokeBlue()
self:F2()
trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Blue)
end
function POSITIONABLE:IsInZone(Zone)
self:F2({self.PositionableName,Zone})
if self:IsAlive()then
local IsInZone=Zone:IsVec3InZone(self:GetVec3())
return IsInZone
end
return false
end
function POSITIONABLE:IsNotInZone(Zone)
self:F2({self.PositionableName,Zone})
if self:IsAlive()then
local IsNotInZone=not Zone:IsVec3InZone(self:GetVec3())
return IsNotInZone
else
return false
end
end
CONTROLLABLE={
ClassName="CONTROLLABLE",
ControllableName="",
WayPointFunctions={},
}
function CONTROLLABLE:New(ControllableName)
local self=BASE:Inherit(self,POSITIONABLE:New(ControllableName))
self.ControllableName=ControllableName
self.TaskScheduler=SCHEDULER:New(self)
return self
end
function CONTROLLABLE:_GetController()
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local ControllableController=DCSControllable:getController()
return ControllableController
end
return nil
end
function CONTROLLABLE:GetLife()
self:F2(self.ControllableName)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local UnitLife=0
local Units=self:GetUnits()
if#Units==1 then
local Unit=Units[1]
UnitLife=Unit:GetLife()
else
local UnitLifeTotal=0
for UnitID,Unit in pairs(Units)do
local Unit=Unit
UnitLifeTotal=UnitLifeTotal+Unit:GetLife()
end
UnitLife=UnitLifeTotal/#Units
end
return UnitLife
end
return nil
end
function CONTROLLABLE:GetLife0()
self:F2(self.ControllableName)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local UnitLife=0
local Units=self:GetUnits()
if#Units==1 then
local Unit=Units[1]
UnitLife=Unit:GetLife0()
else
local UnitLifeTotal=0
for UnitID,Unit in pairs(Units)do
local Unit=Unit
UnitLifeTotal=UnitLifeTotal+Unit:GetLife0()
end
UnitLife=UnitLifeTotal/#Units
end
return UnitLife
end
return nil
end
function CONTROLLABLE:GetFuelMin()
self:F(self.ControllableName)
return nil
end
function CONTROLLABLE:GetFuelAve()
self:F(self.ControllableName)
return nil
end
function CONTROLLABLE:GetFuel()
self:F(self.ControllableName)
return nil
end
function CONTROLLABLE:ClearTasks()
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
Controller:resetTask()
return self
end
return nil
end
function CONTROLLABLE:PopCurrentTask()
self:F2()
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
Controller:popTask()
return self
end
return nil
end
function CONTROLLABLE:PushTask(DCSTask,WaitTime)
self:F2()
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local DCSControllableName=self:GetName()
local function PushTask(Controller,DCSTask)
if self and self:IsAlive()then
local Controller=self:_GetController()
Controller:pushTask(DCSTask)
else
BASE:E({DCSControllableName.." is not alive anymore.",DCSTask=DCSTask})
end
end
if not WaitTime or WaitTime==0 then
PushTask(self,DCSTask)
else
self.TaskScheduler:Schedule(self,PushTask,{DCSTask},WaitTime)
end
return self
end
return nil
end
function CONTROLLABLE:SetTask(DCSTask,WaitTime)
self:F({"SetTask",WaitTime,DCSTask=DCSTask})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local DCSControllableName=self:GetName()
self:T2("Controllable Name = "..DCSControllableName)
local function SetTask(Controller,DCSTask)
if self and self:IsAlive()then
local Controller=self:_GetController()
Controller:setTask(DCSTask)
self:T({ControllableName=self:GetName(),DCSTask=DCSTask})
else
BASE:E({DCSControllableName.." is not alive anymore.",DCSTask=DCSTask})
end
end
if not WaitTime or WaitTime==0 then
SetTask(self,DCSTask)
self:T({ControllableName=self:GetName(),DCSTask=DCSTask})
else
self.TaskScheduler:Schedule(self,SetTask,{DCSTask},WaitTime)
end
return self
end
return nil
end
function CONTROLLABLE:HasTask()
local HasTaskResult=false
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
HasTaskResult=Controller:hasTask()
end
return HasTaskResult
end
function CONTROLLABLE:TaskCondition(time,userFlag,userFlagValue,condition,duration,lastWayPoint)
local DCSStopCondition={}
DCSStopCondition.time=time
DCSStopCondition.userFlag=userFlag
DCSStopCondition.userFlagValue=userFlagValue
DCSStopCondition.condition=condition
DCSStopCondition.duration=duration
DCSStopCondition.lastWayPoint=lastWayPoint
return DCSStopCondition
end
function CONTROLLABLE:TaskControlled(DCSTask,DCSStopCondition)
local DCSTaskControlled={
id='ControlledTask',
params={
task=DCSTask,
stopCondition=DCSStopCondition,
},
}
return DCSTaskControlled
end
function CONTROLLABLE:TaskCombo(DCSTasks)
local DCSTaskCombo={
id='ComboTask',
params={
tasks=DCSTasks,
},
}
return DCSTaskCombo
end
function CONTROLLABLE:TaskWrappedAction(DCSCommand,Index)
local DCSTaskWrappedAction={
id="WrappedAction",
enabled=true,
number=Index or 1,
auto=false,
params={
action=DCSCommand,
},
}
return DCSTaskWrappedAction
end
function CONTROLLABLE:SetTaskWaypoint(Waypoint,Task)
Waypoint.task=self:TaskCombo({Task})
self:F({Waypoint.task})
return Waypoint.task
end
function CONTROLLABLE:SetCommand(DCSCommand)
self:F2(DCSCommand)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
Controller:setCommand(DCSCommand)
return self
end
return nil
end
function CONTROLLABLE:CommandSwitchWayPoint(FromWayPoint,ToWayPoint)
self:F2({FromWayPoint,ToWayPoint})
local CommandSwitchWayPoint={
id='SwitchWaypoint',
params={
fromWaypointIndex=FromWayPoint,
goToWaypointIndex=ToWayPoint,
},
}
self:T3({CommandSwitchWayPoint})
return CommandSwitchWayPoint
end
function CONTROLLABLE:CommandStopRoute(StopRoute)
self:F2({StopRoute})
local CommandStopRoute={
id='StopRoute',
params={
value=StopRoute,
},
}
self:T3({CommandStopRoute})
return CommandStopRoute
end
function CONTROLLABLE:StartUncontrolled(delay)
if delay and delay>0 then
SCHEDULER:New(nil,CONTROLLABLE.StartUncontrolled,{self},delay)
else
self:SetCommand({id='Start',params={}})
end
return self
end
function CONTROLLABLE:CommandActivateBeacon(Type,System,Frequency,UnitID,Channel,ModeChannel,AA,Callsign,Bearing,Delay)
AA=AA or self:IsAir()
UnitID=UnitID or self:GetID()
local CommandActivateBeacon={
id="ActivateBeacon",
params={
["type"]=Type,
["system"]=System,
["frequency"]=Frequency,
["unitId"]=UnitID,
["channel"]=Channel,
["modeChannel"]=ModeChannel,
["AA"]=AA,
["callsign"]=Callsign,
["bearing"]=Bearing,
},
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandActivateBeacon,{self,Type,System,Frequency,UnitID,Channel,ModeChannel,AA,Callsign,Bearing},Delay)
else
self:SetCommand(CommandActivateBeacon)
end
return self
end
function CONTROLLABLE:CommandActivateACLS(UnitID,Name,Delay)
local CommandActivateACLS={
id='ActivateACLS',
params={
unitId=UnitID or self:GetID(),
name=Name or"ACL",
}
}
self:T({CommandActivateACLS})
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandActivateACLS,{self,UnitID,Name},Delay)
else
self:SetCommand(CommandActivateACLS)
end
return self
end
function CONTROLLABLE:CommandDeactivateACLS(Delay)
local CommandDeactivateACLS={
id='DeactivateACLS',
params={}
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandDeactivateACLS,{self},Delay)
else
self:SetCommand(CommandDeactivateACLS)
end
return self
end
function CONTROLLABLE:CommandActivateICLS(Channel,UnitID,Callsign,Delay)
local CommandActivateICLS={
id="ActivateICLS",
params={
["type"]=BEACON.Type.ICLS,
["channel"]=Channel,
["unitId"]=UnitID or self:GetID(),
["callsign"]=Callsign,
},
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandActivateICLS,{self,Channel,UnitID,Callsign},Delay)
else
self:SetCommand(CommandActivateICLS)
end
return self
end
function CONTROLLABLE:CommandActivateLink4(Frequency,UnitID,Callsign,Delay)
local freq=Frequency or 336
local CommandActivateLink4={
id="ActivateLink4",
params={
["frequency "]=freq*1000000,
["unitId"]=UnitID or self:GetID(),
["name"]=Callsign or"LNK",
}
}
self:T({CommandActivateLink4})
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandActivateLink4,{self,Frequency,UnitID,Callsign},Delay)
else
self:SetCommand(CommandActivateLink4)
end
return self
end
function CONTROLLABLE:CommandDeactivateBeacon(Delay)
local CommandDeactivateBeacon={id='DeactivateBeacon',params={}}
local CommandDeactivateBeacon={id='DeactivateBeacon',params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandDeactivateBeacon,{self},Delay)
else
self:SetCommand(CommandDeactivateBeacon)
end
return self
end
function CONTROLLABLE:CommandDeactivateLink4(Delay)
local CommandDeactivateLink4={id='DeactivateLink4',params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandDeactivateLink4,{self},Delay)
else
self:SetCommand(CommandDeactivateLink4)
end
return self
end
function CONTROLLABLE:CommandDeactivateICLS(Delay)
local CommandDeactivateICLS={id='DeactivateICLS',params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandDeactivateICLS,{self},Delay)
else
self:SetCommand(CommandDeactivateICLS)
end
return self
end
function CONTROLLABLE:CommandSetCallsign(CallName,CallNumber,Delay)
local CommandSetCallsign={id='SetCallsign',params={callname=CallName,number=CallNumber or 1}}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandSetCallsign,{self,CallName,CallNumber},Delay)
else
self:SetCommand(CommandSetCallsign)
end
return self
end
function CONTROLLABLE:CommandEPLRS(SwitchOnOff,Delay)
if SwitchOnOff==nil then
SwitchOnOff=true
end
local CommandEPLRS={
id='EPLRS',
params={
value=SwitchOnOff,
groupId=self:GetID(),
},
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandEPLRS,{self,SwitchOnOff},Delay)
else
self:T(string.format("EPLRS=%s for controllable %s (id=%s)",tostring(SwitchOnOff),tostring(self:GetName()),tostring(self:GetID())))
self:SetCommand(CommandEPLRS)
end
return self
end
function CONTROLLABLE:CommandSetFrequency(Frequency,Modulation,Delay)
local CommandSetFrequency={
id='SetFrequency',
params={
frequency=Frequency*1000000,
modulation=Modulation or radio.modulation.AM,
},
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandSetFrequency,{self,Frequency,Modulation},Delay)
else
self:SetCommand(CommandSetFrequency)
end
return self
end
function CONTROLLABLE:TaskEPLRS(SwitchOnOff,idx)
if SwitchOnOff==nil then
SwitchOnOff=true
end
local CommandEPLRS={
id='EPLRS',
params={
value=SwitchOnOff,
groupId=self:GetID(),
},
}
return self:TaskWrappedAction(CommandEPLRS,idx or 1)
end
function CONTROLLABLE:TaskAttackGroup(AttackGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit,GroupAttack)
local DCSTask={id='AttackGroup',
params={
groupId=AttackGroup:GetID(),
weaponType=WeaponType or 1073741822,
expend=WeaponExpend or"Auto",
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty or 1,
directionEnabled=Direction and true or false,
direction=Direction and math.rad(Direction)or 0,
altitudeEnabled=Altitude and true or false,
altitude=Altitude,
groupAttack=GroupAttack and true or false,
},
}
return DCSTask
end
function CONTROLLABLE:TaskAttackUnit(AttackUnit,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType)
local DCSTask={
id='AttackUnit',
params={
unitId=AttackUnit:GetID(),
groupAttack=GroupAttack and GroupAttack or false,
expend=WeaponExpend or"Auto",
directionEnabled=Direction and true or false,
direction=Direction and math.rad(Direction)or 0,
altitudeEnabled=Altitude and true or false,
altitude=Altitude,
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty,
weaponType=WeaponType or 1073741822,
},
}
return DCSTask
end
function CONTROLLABLE:TaskBombing(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType,Divebomb)
local DCSTask={
id='Bombing',
params={
point=Vec2,
x=Vec2.x,
y=Vec2.y,
groupAttack=GroupAttack and GroupAttack or false,
expend=WeaponExpend or"Auto",
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty or 1,
directionEnabled=Direction and true or false,
direction=Direction and math.rad(Direction)or 0,
altitudeEnabled=Altitude and true or false,
altitude=Altitude or 2000,
weaponType=WeaponType or 1073741822,
attackType=Divebomb and"Dive"or nil,
},
}
return DCSTask
end
function CONTROLLABLE:TaskAttackMapObject(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType)
local DCSTask={
id='AttackMapObject',
params={
point=Vec2,
x=Vec2.x,
y=Vec2.y,
groupAttack=GroupAttack or false,
expend=WeaponExpend or"Auto",
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty,
directionEnabled=Direction and true or false,
direction=Direction and math.rad(Direction)or 0,
altitudeEnabled=Altitude and true or false,
altitude=Altitude,
weaponType=WeaponType or 1073741822,
},
}
return DCSTask
end
function CONTROLLABLE:TaskCarpetBombing(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType,CarpetLength)
local DCSTask={
id='CarpetBombing',
params={
attackType="Carpet",
x=Vec2.x,
y=Vec2.y,
groupAttack=GroupAttack and GroupAttack or false,
carpetLength=CarpetLength or 500,
weaponType=WeaponType or ENUMS.WeaponFlag.AnyBomb,
expend=WeaponExpend or"All",
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty or 1,
directionEnabled=Direction and true or false,
direction=Direction and math.rad(Direction)or 0,
altitudeEnabled=Altitude and true or false,
altitude=Altitude,
},
}
return DCSTask
end
function CONTROLLABLE:TaskFollowBigFormation(FollowControllable,Vec3,LastWaypointIndex)
local DCSTask={
id='FollowBigFormation',
params={
groupId=FollowControllable:GetID(),
pos=Vec3,
lastWptIndexFlag=LastWaypointIndex and true or false,
lastWptIndex=LastWaypointIndex,
},
}
return DCSTask
end
function CONTROLLABLE:TaskEmbarking(Coordinate,GroupSetForEmbarking,Duration,Distribution)
local g4e={}
if GroupSetForEmbarking then
for _,_group in pairs(GroupSetForEmbarking:GetSet())do
local group=_group
table.insert(g4e,group:GetID())
end
else
self:E("ERROR: No groups for embarking specified!")
return nil
end
local groupID=self and self:GetID()
local DCSTask={
id='Embarking',
params={
selectedTransport=groupID,
x=Coordinate.x,
y=Coordinate.z,
groupsForEmbarking=g4e,
durationFlag=Duration and true or false,
duration=Duration,
distributionFlag=Distribution and true or false,
distribution=Distribution,
},
}
return DCSTask
end
function CONTROLLABLE:TaskEmbarkToTransport(Coordinate,Radius,UnitType)
local EmbarkToTransport={
id="EmbarkToTransport",
params={
x=Coordinate.x,
y=Coordinate.z,
zoneRadius=Radius or 200,
selectedType=UnitType,
},
}
return EmbarkToTransport
end
function CONTROLLABLE:TaskDisembarking(Coordinate,GroupSetToDisembark)
local g4e={}
if GroupSetToDisembark then
for _,_group in pairs(GroupSetToDisembark:GetSet())do
local group=_group
table.insert(g4e,group:GetID())
end
else
self:E("ERROR: No groups for disembarking specified!")
return nil
end
local Disembarking={
id="Disembarking",
params={
x=Coordinate.x,
y=Coordinate.z,
groupsForEmbarking=g4e,
},
}
return Disembarking
end
function CONTROLLABLE:TaskOrbitCircleAtVec2(Point,Altitude,Speed)
self:F2({self.ControllableName,Point,Altitude,Speed})
local DCSTask={
id='Orbit',
params={
pattern=AI.Task.OrbitPattern.CIRCLE,
point=Point,
speed=Speed,
altitude=Altitude+land.getHeight(Point),
},
}
return DCSTask
end
function CONTROLLABLE:TaskOrbit(Coord,Altitude,Speed,CoordRaceTrack)
local Pattern=AI.Task.OrbitPattern.CIRCLE
local P1=Coord:GetVec2()
local P2=nil
if CoordRaceTrack then
Pattern=AI.Task.OrbitPattern.RACE_TRACK
P2=CoordRaceTrack:GetVec2()
end
local Task={
id='Orbit',
params={
pattern=Pattern,
point=P1,
point2=P2,
speed=Speed or UTILS.KnotsToMps(250),
altitude=Altitude or Coord.y,
},
}
return Task
end
function CONTROLLABLE:TaskOrbitCircle(Altitude,Speed,Coordinate)
self:F2({self.ControllableName,Altitude,Speed})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local OrbitVec2=Coordinate and Coordinate:GetVec2()or self:GetVec2()
return self:TaskOrbitCircleAtVec2(OrbitVec2,Altitude,Speed)
end
return nil
end
function CONTROLLABLE:TaskHoldPosition()
self:F2({self.ControllableName})
return self:TaskOrbitCircle(30,10)
end
function CONTROLLABLE:TaskBombingRunway(Airbase,WeaponType,WeaponExpend,AttackQty,Direction,GroupAttack)
local DCSTask={
id='BombingRunway',
params={
runwayId=Airbase:GetID(),
weaponType=WeaponType or ENUMS.WeaponFlag.AnyBomb,
expend=WeaponExpend or AI.Task.WeaponExpend.ALL,
attackQty=AttackQty or 1,
direction=Direction and math.rad(Direction)or 0,
groupAttack=GroupAttack and true or false,
},
}
return DCSTask
end
function CONTROLLABLE:TaskRefueling()
local DCSTask={
id='Refueling',
params={},
}
return DCSTask
end
function CONTROLLABLE:TaskRecoveryTanker(CarrierGroup,Speed,Altitude,LastWptNumber)
local LastWptFlag=type(LastWptNumber)=="number"and true or false
local DCSTask={
id="RecoveryTanker",
params={
groupId=CarrierGroup:GetID(),
speed=Speed,
altitude=Altitude,
lastWptIndexFlag=LastWptFlag,
lastWptIndex=LastWptNumber
}
}
return DCSTask
end
function CONTROLLABLE:TaskLandAtVec2(Vec2,Duration)
local DCSTask={
id='Land',
params={
point=Vec2,
durationFlag=Duration and true or false,
duration=Duration,
},
}
return DCSTask
end
function CONTROLLABLE:TaskLandAtZone(Zone,Duration,RandomPoint)
local Point=RandomPoint and Zone:GetRandomVec2()or Zone:GetVec2()
local DCSTask=CONTROLLABLE.TaskLandAtVec2(self,Point,Duration)
return DCSTask
end
function CONTROLLABLE:TaskFollow(FollowControllable,Vec3,LastWaypointIndex)
self:F2({self.ControllableName,FollowControllable,Vec3,LastWaypointIndex})
local LastWaypointIndexFlag=false
local lastWptIndexFlagChangedManually=false
if LastWaypointIndex then
LastWaypointIndexFlag=true
lastWptIndexFlagChangedManually=true
end
local DCSTask={
id='Follow',
params={
groupId=FollowControllable:GetID(),
pos=Vec3,
lastWptIndexFlag=LastWaypointIndexFlag,
lastWptIndex=LastWaypointIndex,
lastWptIndexFlagChangedManually=lastWptIndexFlagChangedManually,
},
}
self:T3({DCSTask})
return DCSTask
end
function CONTROLLABLE:TaskEscort(FollowControllable,Vec3,LastWaypointIndex,EngagementDistance,TargetTypes)
local DCSTask
DCSTask={
id='Escort',
params={
groupId=FollowControllable and FollowControllable:GetID()or nil,
pos=Vec3,
lastWptIndexFlag=LastWaypointIndex and true or false,
lastWptIndex=LastWaypointIndex,
engagementDistMax=EngagementDistance,
targetTypes=TargetTypes or{"Air"},
},
}
return DCSTask
end
function CONTROLLABLE:TaskFireAtPoint(Vec2,Radius,AmmoCount,WeaponType,Altitude,ASL)
local DCSTask={
id='FireAtPoint',
params={
point=Vec2,
x=Vec2.x,
y=Vec2.y,
zoneRadius=Radius,
radius=Radius,
expendQty=1,
expendQtyEnabled=false,
alt_type=ASL and 0 or 1,
},
}
if AmmoCount then
DCSTask.params.expendQty=AmmoCount
DCSTask.params.expendQtyEnabled=true
end
if Altitude then
DCSTask.params.altitude=Altitude
end
if WeaponType then
DCSTask.params.weaponType=WeaponType
end
return DCSTask
end
function CONTROLLABLE:TaskHold()
local DCSTask={id='Hold',params={}}
return DCSTask
end
function CONTROLLABLE:TaskFAC_AttackGroup(AttackGroup,WeaponType,Designation,Datalink,Frequency,Modulation,CallsignName,CallsignNumber)
local DCSTask={
id='FAC_AttackGroup',
params={
groupId=AttackGroup:GetID(),
weaponType=WeaponType or ENUMS.WeaponFlag.AutoDCS,
designation=Designation or"Auto",
datalink=Datalink and Datalink or true,
frequency=(Frequency or 133)*1000000,
modulation=Modulation or radio.modulation.AM,
callname=CallsignName,
number=CallsignNumber,
},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskEngageTargets(Distance,TargetTypes,Priority)
local DCSTask={
id='EngageTargets',
params={
maxDistEnabled=Distance and true or false,
maxDist=Distance,
targetTypes=TargetTypes or{"Air"},
priority=Priority or 0,
},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskEngageTargetsInZone(Vec2,Radius,TargetTypes,Priority)
local DCSTask={
id='EngageTargetsInZone',
params={
point=Vec2,
zoneRadius=Radius,
targetTypes=TargetTypes or{"Air"},
priority=Priority or 0
},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskAntiShip(TargetTypes,Priority)
local DCSTask={
id='EngageTargets',
key="AntiShip",
params={
targetTypes=TargetTypes or{"Ships"},
priority=Priority or 0
}
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskSEAD(TargetTypes,Priority)
local DCSTask={
id='EngageTargets',
key="SEAD",
params={
targetTypes=TargetTypes or{"Air Defence"},
priority=Priority or 0
}
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskEngageGroup(AttackGroup,Priority,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit)
local DCSTask={
id='EngageControllable',
params={
groupId=AttackGroup:GetID(),
weaponType=WeaponType,
expend=WeaponExpend or"Auto",
directionEnabled=Direction and true or false,
direction=Direction,
altitudeEnabled=Altitude and true or false,
altitude=Altitude,
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty,
priority=Priority or 1,
},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskEngageUnit(EngageUnit,Priority,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,Visible,ControllableAttack)
local DCSTask={
id='EngageUnit',
params={
unitId=EngageUnit:GetID(),
priority=Priority or 1,
groupAttack=GroupAttack and GroupAttack or false,
visible=Visible and Visible or false,
expend=WeaponExpend or"Auto",
directionEnabled=Direction and true or false,
direction=Direction and math.rad(Direction)or nil,
altitudeEnabled=Altitude and true or false,
altitude=Altitude,
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty,
controllableAttack=ControllableAttack,
},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskAWACS()
local DCSTask={
id='AWACS',
params={},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskTanker()
local DCSTask={
id='Tanker',
params={},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskEWR()
local DCSTask={
id='EWR',
params={},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskFAC_EngageGroup(AttackGroup,Priority,WeaponType,Designation,Datalink)
local DCSTask={
id='FAC_EngageControllable',
params={
groupId=AttackGroup:GetID(),
weaponType=WeaponType or"Auto",
designation=Designation,
datalink=Datalink and Datalink or false,
priority=Priority or 0,
},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskFAC(Radius,Priority)
local DCSTask={
id='FAC',
params={
radius=Radius,
priority=Priority
}
}
return DCSTask
end
function CONTROLLABLE:TaskFunction(FunctionString,...)
local DCSScript={}
DCSScript[#DCSScript+1]="local MissionControllable = GROUP:Find( ... ) "
if arg and arg.n>0 then
local ArgumentKey='_'..tostring(arg):match("table: (.*)")
self:SetState(self,ArgumentKey,arg)
DCSScript[#DCSScript+1]="local Arguments = MissionControllable:GetState( MissionControllable, '"..ArgumentKey.."' ) "
DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable, unpack( Arguments ) )"
else
DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable )"
end
local DCSTask=self:TaskWrappedAction(self:CommandDoScript(table.concat(DCSScript)))
return DCSTask
end
function CONTROLLABLE:TaskMission(TaskMission)
local DCSTask={
id='Mission',
params={
TaskMission,
},
}
return DCSTask
end
do
function CONTROLLABLE:PatrolRoute()
local PatrolGroup=self
if not self:IsInstanceOf("GROUP")then
PatrolGroup=self:GetGroup()
end
self:F({PatrolGroup=PatrolGroup:GetName()})
if PatrolGroup:IsGround()or PatrolGroup:IsShip()then
local Waypoints=PatrolGroup:GetTemplateRoutePoints()
local FromCoord=PatrolGroup:GetCoordinate()
local depth=0
local IsSub=false
if PatrolGroup:IsShip()then
local navalvec3=FromCoord:GetVec3()
if navalvec3.y<0 then
depth=navalvec3.y
IsSub=true
end
end
local Waypoint=Waypoints[1]
local Speed=Waypoint.speed or(20/3.6)
local From=FromCoord:WaypointGround(Speed)
if IsSub then
From=FromCoord:WaypointNaval(Speed,Waypoint.alt)
end
table.insert(Waypoints,1,From)
local TaskRoute=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRoute")
self:F({Waypoints=Waypoints})
local Waypoint=Waypoints[#Waypoints]
PatrolGroup:SetTaskWaypoint(Waypoint,TaskRoute)
PatrolGroup:Route(Waypoints)
end
end
function CONTROLLABLE:PatrolRouteRandom(Speed,Formation,ToWaypoint)
local PatrolGroup=self
if not self:IsInstanceOf("GROUP")then
PatrolGroup=self:GetGroup()
end
self:F({PatrolGroup=PatrolGroup:GetName()})
if PatrolGroup:IsGround()or PatrolGroup:IsShip()then
local Waypoints=PatrolGroup:GetTemplateRoutePoints()
local FromCoord=PatrolGroup:GetCoordinate()
local FromWaypoint=1
if ToWaypoint then
FromWaypoint=ToWaypoint
end
local depth=0
local IsSub=false
if PatrolGroup:IsShip()then
local navalvec3=FromCoord:GetVec3()
if navalvec3.y<0 then
depth=navalvec3.y
IsSub=true
end
end
local ToWaypoint
repeat
ToWaypoint=math.random(1,#Waypoints)
until(ToWaypoint~=FromWaypoint)
self:F({FromWaypoint=FromWaypoint,ToWaypoint=ToWaypoint})
local Waypoint=Waypoints[ToWaypoint]
local ToCoord=COORDINATE:NewFromVec2({x=Waypoint.x,y=Waypoint.y})
local Route={}
if IsSub then
Route[#Route+1]=FromCoord:WaypointNaval(Speed,depth)
Route[#Route+1]=ToCoord:WaypointNaval(Speed,Waypoint.alt)
else
Route[#Route+1]=FromCoord:WaypointGround(Speed,Formation)
Route[#Route+1]=ToCoord:WaypointGround(Speed,Formation)
end
local TaskRouteToZone=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRouteRandom",Speed,Formation,ToWaypoint)
PatrolGroup:SetTaskWaypoint(Route[#Route],TaskRouteToZone)
PatrolGroup:Route(Route,1)
end
end
function CONTROLLABLE:PatrolZones(ZoneList,Speed,Formation,DelayMin,DelayMax)
if not type(ZoneList)=="table"then
ZoneList={ZoneList}
end
local PatrolGroup=self
if not self:IsInstanceOf("GROUP")then
PatrolGroup=self:GetGroup()
end
DelayMin=DelayMin or 1
if not DelayMax or DelayMax<DelayMin then
DelayMax=DelayMin
end
local Delay=math.random(DelayMin,DelayMax)
self:F({PatrolGroup=PatrolGroup:GetName()})
if PatrolGroup:IsGround()or PatrolGroup:IsShip()then
local FromCoord=PatrolGroup:GetCoordinate()
local depth=0
local IsSub=false
if PatrolGroup:IsShip()then
local navalvec3=FromCoord:GetVec3()
if navalvec3.y<0 then
depth=navalvec3.y
IsSub=true
end
end
local RandomZone=ZoneList[math.random(1,#ZoneList)]
local ToCoord=RandomZone:GetRandomCoordinate(10)
local Route={}
if IsSub then
Route[#Route+1]=FromCoord:WaypointNaval(Speed,depth)
Route[#Route+1]=ToCoord:WaypointNaval(Speed,depth)
else
Route[#Route+1]=FromCoord:WaypointGround(Speed,Formation)
Route[#Route+1]=ToCoord:WaypointGround(Speed,Formation)
end
local TaskRouteToZone=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolZones",ZoneList,Speed,Formation,DelayMin,DelayMax)
PatrolGroup:SetTaskWaypoint(Route[#Route],TaskRouteToZone)
PatrolGroup:Route(Route,Delay)
end
end
end
function CONTROLLABLE:TaskRoute(Points)
local DCSTask={
id='Mission',
params={
airborne=self:IsAir(),
route={points=Points},
},
}
return DCSTask
end
do
function CONTROLLABLE:RouteToVec2(Point,Speed)
self:F2({Point,Speed})
local ControllablePoint=self:GetUnit(1):GetVec2()
local PointFrom={}
PointFrom.x=ControllablePoint.x
PointFrom.y=ControllablePoint.y
PointFrom.type="Turning Point"
PointFrom.action="Turning Point"
PointFrom.speed=Speed
PointFrom.speed_locked=true
PointFrom.properties={
["vnav"]=1,
["scale"]=0,
["angle"]=0,
["vangle"]=0,
["steer"]=2,
}
local PointTo={}
PointTo.x=Point.x
PointTo.y=Point.y
PointTo.type="Turning Point"
PointTo.action="Fly Over Point"
PointTo.speed=Speed
PointTo.speed_locked=true
PointTo.properties={
["vnav"]=1,
["scale"]=0,
["angle"]=0,
["vangle"]=0,
["steer"]=2,
}
local Points={PointFrom,PointTo}
self:T3(Points)
self:Route(Points)
return self
end
function CONTROLLABLE:RouteToVec3(Point,Speed)
self:F2({Point,Speed})
local ControllableVec3=self:GetUnit(1):GetVec3()
local PointFrom={}
PointFrom.x=ControllableVec3.x
PointFrom.y=ControllableVec3.z
PointFrom.alt=ControllableVec3.y
PointFrom.alt_type="BARO"
PointFrom.type="Turning Point"
PointFrom.action="Turning Point"
PointFrom.speed=Speed
PointFrom.speed_locked=true
PointFrom.properties={
["vnav"]=1,
["scale"]=0,
["angle"]=0,
["vangle"]=0,
["steer"]=2,
}
local PointTo={}
PointTo.x=Point.x
PointTo.y=Point.z
PointTo.alt=Point.y
PointTo.alt_type="BARO"
PointTo.type="Turning Point"
PointTo.action="Fly Over Point"
PointTo.speed=Speed
PointTo.speed_locked=true
PointTo.properties={
["vnav"]=1,
["scale"]=0,
["angle"]=0,
["vangle"]=0,
["steer"]=2,
}
local Points={PointFrom,PointTo}
self:T3(Points)
self:Route(Points)
return self
end
function CONTROLLABLE:Route(Route,DelaySeconds)
self:F2(Route)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local RouteTask=self:TaskRoute(Route)
self:SetTask(RouteTask,DelaySeconds or 1)
return self
end
return nil
end
function CONTROLLABLE:RoutePush(Route,DelaySeconds)
self:F2(Route)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local RouteTask=self:TaskRoute(Route)
self:PushTask(RouteTask,DelaySeconds or 1)
return self
end
return nil
end
function CONTROLLABLE:RouteStop()
self:F(self:GetName().." RouteStop")
local CommandStop=self:CommandStopRoute(true)
self:SetCommand(CommandStop)
end
function CONTROLLABLE:RouteResume()
self:F(self:GetName().." RouteResume")
local CommandResume=self:CommandStopRoute(false)
self:SetCommand(CommandResume)
end
function CONTROLLABLE:RouteGroundTo(ToCoordinate,Speed,Formation,DelaySeconds,WaypointFunction,WaypointFunctionArguments)
local FromCoordinate=self:GetCoordinate()
local FromWP=FromCoordinate:WaypointGround(Speed,Formation)
local ToWP=ToCoordinate:WaypointGround(Speed,Formation)
local route={FromWP,ToWP}
if WaypointFunction then
local N=#route
for n,waypoint in pairs(route)do
waypoint.task={}
waypoint.task.id="ComboTask"
waypoint.task.params={}
waypoint.task.params.tasks={self:TaskFunction("CONTROLLABLE.___PassingWaypoint",n,N,WaypointFunction,unpack(WaypointFunctionArguments or{}))}
end
end
self:Route(route,DelaySeconds)
return self
end
function CONTROLLABLE:RouteGroundOnRoad(ToCoordinate,Speed,DelaySeconds,OffRoadFormation,WaypointFunction,WaypointFunctionArguments)
Speed=Speed or 20
DelaySeconds=DelaySeconds or 1
OffRoadFormation=OffRoadFormation or"Off Road"
local route=self:TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,nil,nil,WaypointFunction,WaypointFunctionArguments)
self:Route(route,DelaySeconds)
return self
end
function CONTROLLABLE:RouteGroundOnRailRoads(ToCoordinate,Speed,DelaySeconds,WaypointFunction,WaypointFunctionArguments)
Speed=Speed or 20
DelaySeconds=DelaySeconds or 1
local route=self:TaskGroundOnRailRoads(ToCoordinate,Speed,WaypointFunction,WaypointFunctionArguments)
self:Route(route,DelaySeconds)
return self
end
function CONTROLLABLE:TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,Shortcut,FromCoordinate,WaypointFunction,WaypointFunctionArguments)
self:T({ToCoordinate=ToCoordinate,Speed=Speed,OffRoadFormation=OffRoadFormation,WaypointFunction=WaypointFunction,Args=WaypointFunctionArguments})
Speed=Speed or 20
OffRoadFormation=OffRoadFormation or"Off Road"
FromCoordinate=FromCoordinate or self:GetCoordinate()
local PathOnRoad,LengthOnRoad,GotPath=FromCoordinate:GetPathOnRoad(ToCoordinate,true)
local _,LengthRoad=FromCoordinate:GetPathOnRoad(ToCoordinate,false)
local LengthOffRoad
local LongRoad
local LengthDirect=FromCoordinate:Get2DDistance(ToCoordinate)
if GotPath and LengthRoad then
LengthOffRoad=LengthOnRoad-LengthRoad
LongRoad=LengthOnRoad and((LengthOnRoad>LengthDirect*10)or(LengthRoad/LengthOnRoad*100<5))
self:T(string.format("Length on road   = %.3f km",LengthOnRoad/1000))
self:T(string.format("Length directly  = %.3f km",LengthDirect/1000))
self:T(string.format("Length fraction  = %.3f km",LengthOnRoad/LengthDirect))
self:T(string.format("Length only road = %.3f km",LengthRoad/1000))
self:T(string.format("Length off road  = %.3f km",LengthOffRoad/1000))
self:T(string.format("Percent on road  = %.1f",LengthRoad/LengthOnRoad*100))
end
local route={}
local canroad=false
if GotPath and LengthRoad and LengthDirect>2000 then
if LongRoad and Shortcut then
table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation))
table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation))
else
table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation))
table.insert(route,PathOnRoad[2]:WaypointGround(Speed,"On Road"))
table.insert(route,PathOnRoad[#PathOnRoad-1]:WaypointGround(Speed,"On Road"))
local dist=ToCoordinate:Get2DDistance(PathOnRoad[#PathOnRoad-1])
if dist>10 then
table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation))
table.insert(route,ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5,OffRoadFormation))
table.insert(route,ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5,OffRoadFormation))
end
end
canroad=true
else
table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation))
table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation))
end
if WaypointFunction then
local N=#route
for n,waypoint in pairs(route)do
waypoint.task={}
waypoint.task.id="ComboTask"
waypoint.task.params={}
waypoint.task.params.tasks={self:TaskFunction("CONTROLLABLE.___PassingWaypoint",n,N,WaypointFunction,unpack(WaypointFunctionArguments or{}))}
end
end
return route,canroad
end
function CONTROLLABLE:TaskGroundOnRailRoads(ToCoordinate,Speed,WaypointFunction,WaypointFunctionArguments)
self:F2({ToCoordinate=ToCoordinate,Speed=Speed})
Speed=Speed or 20
local FromCoordinate=self:GetCoordinate()
local PathOnRail,LengthOnRail=FromCoordinate:GetPathOnRoad(ToCoordinate,false,true)
self:T(string.format("Length on railroad = %.3f km",LengthOnRail/1000))
local route={}
if PathOnRail then
table.insert(route,PathOnRail[1]:WaypointGround(Speed,"On Railroad"))
table.insert(route,PathOnRail[2]:WaypointGround(Speed,"On Railroad"))
end
if WaypointFunction then
local N=#route
for n,waypoint in pairs(route)do
waypoint.task={}
waypoint.task.id="ComboTask"
waypoint.task.params={}
waypoint.task.params.tasks={self:TaskFunction("CONTROLLABLE.___PassingWaypoint",n,N,WaypointFunction,unpack(WaypointFunctionArguments or{}))}
end
end
return route
end
function CONTROLLABLE.___PassingWaypoint(controllable,n,N,waypointfunction,...)
waypointfunction(controllable,n,N,...)
end
function CONTROLLABLE:RouteAirTo(ToCoordinate,AltType,Type,Action,Speed,DelaySeconds)
local FromCoordinate=self:GetCoordinate()
local FromWP=FromCoordinate:WaypointAir()
local ToWP=ToCoordinate:WaypointAir(AltType,Type,Action,Speed)
self:Route({FromWP,ToWP},DelaySeconds)
return self
end
function CONTROLLABLE:TaskRouteToZone(Zone,Randomize,Speed,Formation)
self:F2(Zone)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local ControllablePoint=self:GetVec2()
local PointFrom={}
PointFrom.x=ControllablePoint.x
PointFrom.y=ControllablePoint.y
PointFrom.type="Turning Point"
PointFrom.action=Formation or"Cone"
PointFrom.speed=20/3.6
local PointTo={}
local ZonePoint
if Randomize then
ZonePoint=Zone:GetRandomVec2()
else
ZonePoint=Zone:GetVec2()
end
PointTo.x=ZonePoint.x
PointTo.y=ZonePoint.y
PointTo.type="Turning Point"
if Formation then
PointTo.action=Formation
else
PointTo.action="Cone"
end
if Speed then
PointTo.speed=Speed
else
PointTo.speed=20/3.6
end
local Points={PointFrom,PointTo}
self:T3(Points)
self:Route(Points)
return self
end
return nil
end
function CONTROLLABLE:TaskRouteToVec2(Vec2,Speed,Formation)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local ControllablePoint=self:GetVec2()
local PointFrom={}
PointFrom.x=ControllablePoint.x
PointFrom.y=ControllablePoint.y
PointFrom.type="Turning Point"
PointFrom.action=Formation or"Cone"
PointFrom.speed=20/3.6
local PointTo={}
PointTo.x=Vec2.x
PointTo.y=Vec2.y
PointTo.type="Turning Point"
if Formation then
PointTo.action=Formation
else
PointTo.action="Cone"
end
if Speed then
PointTo.speed=Speed
else
PointTo.speed=20/3.6
end
local Points={PointFrom,PointTo}
self:T3(Points)
self:Route(Points)
return self
end
return nil
end
end
function CONTROLLABLE:CommandDoScript(DoScript)
local DCSDoScript={
id="Script",
params={
command=DoScript,
},
}
self:T3(DCSDoScript)
return DCSDoScript
end
function CONTROLLABLE:GetTaskMission()
self:F2(self.ControllableName)
return routines.utils.deepCopy(_DATABASE.Templates.Controllables[self.ControllableName].Template)
end
function CONTROLLABLE:GetTaskRoute()
self:F2(self.ControllableName)
return routines.utils.deepCopy(_DATABASE.Templates.Controllables[self.ControllableName].Template.route.points)
end
function CONTROLLABLE:CopyRoute(Begin,End,Randomize,Radius)
self:F2({Begin,End})
local Points={}
local ControllableName=string.match(self:GetName(),".*#")
if ControllableName then
ControllableName=ControllableName:sub(1,-2)
else
ControllableName=self:GetName()
end
self:T3({ControllableName})
local Template=_DATABASE.Templates.Controllables[ControllableName].Template
if Template then
if not Begin then
Begin=0
end
if not End then
End=0
end
for TPointID=Begin+1,#Template.route.points-End do
if Template.route.points[TPointID]then
Points[#Points+1]=routines.utils.deepCopy(Template.route.points[TPointID])
if Randomize then
if not Radius then
Radius=500
end
Points[#Points].x=Points[#Points].x+math.random(Radius*-1,Radius)
Points[#Points].y=Points[#Points].y+math.random(Radius*-1,Radius)
end
end
end
return Points
else
error("Template not found for Controllable : "..ControllableName)
end
return nil
end
function CONTROLLABLE:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
self:F2(self.ControllableName)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local DetectionVisual=(DetectVisual and DetectVisual==true)and Controller.Detection.VISUAL or nil
local DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.OPTICAL or nil
local DetectionRadar=(DetectRadar and DetectRadar==true)and Controller.Detection.RADAR or nil
local DetectionIRST=(DetectIRST and DetectIRST==true)and Controller.Detection.IRST or nil
local DetectionRWR=(DetectRWR and DetectRWR==true)and Controller.Detection.RWR or nil
local DetectionDLINK=(DetectDLINK and DetectDLINK==true)and Controller.Detection.DLINK or nil
local Params={}
if DetectionVisual then
Params[#Params+1]=DetectionVisual
end
if DetectionOptical then
Params[#Params+1]=DetectionOptical
end
if DetectionRadar then
Params[#Params+1]=DetectionRadar
end
if DetectionIRST then
Params[#Params+1]=DetectionIRST
end
if DetectionRWR then
Params[#Params+1]=DetectionRWR
end
if DetectionDLINK then
Params[#Params+1]=DetectionDLINK
end
self:T2({DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK})
return self:_GetController():getDetectedTargets(Params[1],Params[2],Params[3],Params[4],Params[5],Params[6])
end
return nil
end
function CONTROLLABLE:IsTargetDetected(DCSObject,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
self:F2(self.ControllableName)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local DetectionVisual=(DetectVisual and DetectVisual==true)and Controller.Detection.VISUAL or nil
local DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.OPTICAL or nil
local DetectionRadar=(DetectRadar and DetectRadar==true)and Controller.Detection.RADAR or nil
local DetectionIRST=(DetectIRST and DetectIRST==true)and Controller.Detection.IRST or nil
local DetectionRWR=(DetectRWR and DetectRWR==true)and Controller.Detection.RWR or nil
local DetectionDLINK=(DetectDLINK and DetectDLINK==true)and Controller.Detection.DLINK or nil
local Controller=self:_GetController()
local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity
=Controller:isTargetDetected(DCSObject,DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK)
return TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity
end
return nil
end
function CONTROLLABLE:IsUnitDetected(Unit,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
self:F2(self.ControllableName)
if Unit and Unit:IsAlive()then
return self:IsTargetDetected(Unit:GetDCSObject(),DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
end
return nil
end
function CONTROLLABLE:IsGroupDetected(Group,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
self:F2(self.ControllableName)
if Group and Group:IsAlive()then
for _,_unit in pairs(Group:GetUnits())do
local unit=_unit
if unit and unit:IsAlive()then
local isdetected=self:IsUnitDetected(unit,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
if isdetected then
return true
end
end
end
return false
end
return nil
end
function CONTROLLABLE:GetDetectedUnitSet(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
local detectedtargets=self:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
local unitset=SET_UNIT:New()
for DetectionObjectID,Detection in pairs(detectedtargets or{})do
local DetectedObject=Detection.object
if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then
local unit=UNIT:Find(DetectedObject)
if unit and unit:IsAlive()then
if not unitset:FindUnit(unit:GetName())then
unitset:AddUnit(unit)
end
end
end
end
return unitset
end
function CONTROLLABLE:GetDetectedGroupSet(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
local detectedtargets=self:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
local groupset=SET_GROUP:New()
for DetectionObjectID,Detection in pairs(detectedtargets or{})do
local DetectedObject=Detection.object
if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then
local unit=UNIT:Find(DetectedObject)
if unit and unit:IsAlive()then
local group=unit:GetGroup()
if group and not groupset:FindGroup(group:GetName())then
groupset:AddGroup(group)
end
end
end
end
return groupset
end
function CONTROLLABLE:SetOption(OptionID,OptionValue)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
Controller:setOption(OptionID,OptionValue)
return self
end
return nil
end
function CONTROLLABLE:OptionROE(ROEvalue)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ROE,ROEvalue)
elseif self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ROE,ROEvalue)
elseif self:IsShip()then
Controller:setOption(AI.Option.Naval.id.ROE,ROEvalue)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROEHoldFirePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()or self:IsGround()or self:IsShip()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROEHoldFire()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD)
elseif self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.WEAPON_HOLD)
elseif self:IsShip()then
Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.WEAPON_HOLD)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROEReturnFirePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()or self:IsGround()or self:IsShip()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROEReturnFire()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.RETURN_FIRE)
elseif self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.RETURN_FIRE)
elseif self:IsShip()then
Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.RETURN_FIRE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROEOpenFirePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()or self:IsGround()or self:IsShip()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROEOpenFire()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE)
elseif self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.OPEN_FIRE)
elseif self:IsShip()then
Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.OPEN_FIRE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROEOpenFireWeaponFreePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROEOpenFireWeaponFree()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE_WEAPON_FREE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROEWeaponFreePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROEWeaponFree()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_FREE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROTNoReactionPossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROTNoReaction()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROT(ROTvalue)
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,ROTvalue)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROTPassiveDefensePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROTPassiveDefense()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROTEvadeFirePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROTEvadeFire()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROTVerticalPossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROTVertical()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionAlarmStateAuto()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.AUTO)
elseif self:IsShip()then
Controller:setOption(9,0)
end
return self
end
return nil
end
function CONTROLLABLE:OptionAlarmStateGreen()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
elseif self:IsShip()then
Controller:setOption(9,1)
end
return self
end
return nil
end
function CONTROLLABLE:OptionAlarmStateRed()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
elseif self:IsShip()then
Controller:setOption(9,2)
end
return self
end
return nil
end
function CONTROLLABLE:OptionRTBBingoFuel(RTB)
self:F2({self.ControllableName})
if RTB==nil then
RTB=true
end
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.RTB_ON_BINGO,RTB)
end
return self
end
return nil
end
function CONTROLLABLE:OptionRTBAmmo(WeaponsFlag)
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.RTB_ON_OUT_OF_AMMO,WeaponsFlag)
end
return self
end
return nil
end
function CONTROLLABLE:OptionAllowJettisonWeaponsOnThreat()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.PROHIBIT_JETT,false)
end
return self
end
return nil
end
function CONTROLLABLE:OptionKeepWeaponsOnThreat()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.PROHIBIT_JETT,true)
end
return self
end
return nil
end
function CONTROLLABLE:OptionProhibitAfterburner(Prohibit)
self:F2({self.ControllableName})
if Prohibit==nil then
Prohibit=true
end
if self:IsAir()then
self:SetOption(AI.Option.Air.id.PROHIBIT_AB,Prohibit)
end
return self
end
function CONTROLLABLE:OptionECM_Never()
self:F2({self.ControllableName})
if self:IsAir()then
self:SetOption(AI.Option.Air.id.ECM_USING,0)
end
return self
end
function CONTROLLABLE:OptionECM_OnlyLockByRadar()
self:F2({self.ControllableName})
if self:IsAir()then
self:SetOption(AI.Option.Air.id.ECM_USING,1)
end
return self
end
function CONTROLLABLE:OptionECM_DetectedLockByRadar()
self:F2({self.ControllableName})
if self:IsAir()then
self:SetOption(AI.Option.Air.id.ECM_USING,2)
end
return self
end
function CONTROLLABLE:OptionECM_AlwaysOn()
self:F2({self.ControllableName})
if self:IsAir()then
self:SetOption(AI.Option.Air.id.ECM_USING,3)
end
return self
end
function CONTROLLABLE:WayPointInitialize(WayPoints)
self:F({WayPoints})
if WayPoints then
self.WayPoints=WayPoints
else
self.WayPoints=self:GetTaskRoute()
end
return self
end
function CONTROLLABLE:GetWayPoints()
self:F()
if self.WayPoints then
return self.WayPoints
end
return nil
end
function CONTROLLABLE:WayPointFunction(WayPoint,WayPointIndex,WayPointFunction,...)
self:F2({WayPoint,WayPointIndex,WayPointFunction})
table.insert(self.WayPoints[WayPoint].task.params.tasks,WayPointIndex)
self.WayPoints[WayPoint].task.params.tasks[WayPointIndex]=self:TaskFunction(WayPointFunction,arg)
return self
end
function CONTROLLABLE:WayPointExecute(WayPoint,WaitTime)
self:F({WayPoint,WaitTime})
if not WayPoint then
WayPoint=1
end
for TaskPointID=1,WayPoint-1 do
table.remove(self.WayPoints,1)
end
self:T3(self.WayPoints)
self:SetTask(self:TaskRoute(self.WayPoints),WaitTime)
return self
end
function CONTROLLABLE:IsAirPlane()
self:F2()
local DCSObject=self:GetDCSObject()
if DCSObject then
local Category=DCSObject:getDesc().category
return Category==Unit.Category.AIRPLANE
end
return nil
end
function CONTROLLABLE:IsHelicopter()
self:F2()
local DCSObject=self:GetDCSObject()
if DCSObject then
local Category=DCSObject:getDesc().category
return Category==Unit.Category.HELICOPTER
end
return nil
end
function CONTROLLABLE:OptionRestrictBurner(RestrictBurner)
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if Controller then
if RestrictBurner==true then
if self:IsAir()then
Controller:setOption(16,true)
end
else
if self:IsAir()then
Controller:setOption(16,false)
end
end
end
end
end
function CONTROLLABLE:OptionAAAttackRange(range)
self:F2({self.ControllableName})
local range=range or 3
if range<0 or range>4 then
range=3
end
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if Controller then
if self:IsAir()then
self:SetOption(AI.Option.Air.val.MISSILE_ATTACK,range)
end
end
return self
end
return nil
end
function CONTROLLABLE:OptionEngageRange(EngageRange)
self:F2({self.ControllableName})
EngageRange=EngageRange or 100
if EngageRange<0 or EngageRange>100 then
EngageRange=100
end
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if Controller then
if self:IsGround()then
self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,EngageRange)
end
end
return self
end
return nil
end
function CONTROLLABLE:RelocateGroundRandomInRadius(speed,radius,onroad,shortcut,formation)
self:F2({self.ControllableName})
local _coord=self:GetCoordinate()
local _radius=radius or 500
local _speed=speed or 20
local _tocoord=_coord:GetRandomCoordinateInRadius(_radius,100)
local _onroad=onroad or true
local _grptsk={}
local _candoroad=false
local _shortcut=shortcut or false
local _formation=formation or"Off Road"
if onroad then
_grptsk,_candoroad=self:TaskGroundOnRoad(_tocoord,_speed,_formation,_shortcut)
self:Route(_grptsk,5)
else
self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,_formation)
end
return self
end
function CONTROLLABLE:OptionDisperseOnAttack(Seconds)
self:F2({self.ControllableName})
local seconds=Seconds or 0
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if Controller then
if self:IsGround()then
self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK,seconds)
end
end
return self
end
return nil
end
function CONTROLLABLE:IsSubmarine()
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitDescriptor=DCSUnit:getDesc()
if UnitDescriptor.attributes["Submarines"]==true then
return true
else
return false
end
end
return nil
end
function CONTROLLABLE:SetSpeed(Speed,Keep)
self:F2({self.ControllableName})
local speed=Speed or 5
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if Controller then
Controller:setSpeed(speed,Keep)
end
end
return self
end
function CONTROLLABLE:SetAltitude(Altitude,Keep,AltType)
self:F2({self.ControllableName})
local altitude=Altitude or 1000
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if Controller then
if self:IsAir()then
Controller:setAltitude(altitude,Keep,AltType)
end
end
end
return self
end
GROUP={
ClassName="GROUP",
}
GROUP.Takeoff={
Air=1,
Runway=2,
Hot=3,
Cold=4,
}
GROUPTEMPLATE={}
GROUPTEMPLATE.Takeoff={
[GROUP.Takeoff.Air]={"Turning Point","Turning Point"},
[GROUP.Takeoff.Runway]={"TakeOff","From Runway"},
[GROUP.Takeoff.Hot]={"TakeOffParkingHot","From Parking Area Hot"},
[GROUP.Takeoff.Cold]={"TakeOffParking","From Parking Area"}
}
GROUP.Attribute={
AIR_TRANSPORTPLANE="Air_TransportPlane",
AIR_AWACS="Air_AWACS",
AIR_FIGHTER="Air_Fighter",
AIR_BOMBER="Air_Bomber",
AIR_TANKER="Air_Tanker",
AIR_TRANSPORTHELO="Air_TransportHelo",
AIR_ATTACKHELO="Air_AttackHelo",
AIR_UAV="Air_UAV",
AIR_OTHER="Air_OtherAir",
GROUND_APC="Ground_APC",
GROUND_TRUCK="Ground_Truck",
GROUND_INFANTRY="Ground_Infantry",
GROUND_IFV="Ground_IFV",
GROUND_ARTILLERY="Ground_Artillery",
GROUND_TANK="Ground_Tank",
GROUND_TRAIN="Ground_Train",
GROUND_EWR="Ground_EWR",
GROUND_AAA="Ground_AAA",
GROUND_SAM="Ground_SAM",
GROUND_OTHER="Ground_OtherGround",
NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier",
NAVAL_WARSHIP="Naval_WarShip",
NAVAL_ARMEDSHIP="Naval_ArmedShip",
NAVAL_UNARMEDSHIP="Naval_UnarmedShip",
NAVAL_OTHER="Naval_OtherNaval",
OTHER_UNKNOWN="Other_Unknown",
}
function GROUP:NewTemplate(GroupTemplate,CoalitionSide,CategoryID,CountryID)
local GroupName=GroupTemplate.name
_DATABASE:_RegisterGroupTemplate(GroupTemplate,CoalitionSide,CategoryID,CountryID,GroupName)
local self=BASE:Inherit(self,CONTROLLABLE:New(GroupName))
self.GroupName=GroupName
if not _DATABASE.GROUPS[GroupName]then
_DATABASE.GROUPS[GroupName]=self
end
self:SetEventPriority(4)
return self
end
function GROUP:Register(GroupName)
local self=BASE:Inherit(self,CONTROLLABLE:New(GroupName))
self.GroupName=GroupName
self:SetEventPriority(4)
return self
end
function GROUP:Find(DCSGroup)
local GroupName=DCSGroup:getName()
local GroupFound=_DATABASE:FindGroup(GroupName)
return GroupFound
end
function GROUP:FindByName(GroupName)
local GroupFound=_DATABASE:FindGroup(GroupName)
return GroupFound
end
function GROUP:GetDCSObject()
local DCSGroup=Group.getByName(self.GroupName)
if DCSGroup then
return DCSGroup
end
return nil
end
function GROUP:GetPositionVec3()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local unit=DCSPositionable:getUnits()[1]
if unit then
local PositionablePosition=unit:getPosition().p
self:T3(PositionablePosition)
return PositionablePosition
end
end
return nil
end
function GROUP:IsAlive()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
if DCSGroup:isExist()then
local DCSUnit=DCSGroup:getUnit(1)
if DCSUnit then
local GroupIsAlive=DCSUnit:isActive()
self:T3(GroupIsAlive)
return GroupIsAlive
end
end
end
return nil
end
function GROUP:IsActive()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local unit=DCSGroup:getUnit(1)
if unit then
local GroupIsActive=unit:isActive()
return GroupIsActive
end
end
return nil
end
function GROUP:Destroy(GenerateEvent,delay)
self:F2(self.GroupName)
if delay and delay>0 then
self:ScheduleOnce(delay,GROUP.Destroy,self,GenerateEvent)
else
local DCSGroup=self:GetDCSObject()
if DCSGroup then
for Index,UnitData in pairs(DCSGroup:getUnits())do
if GenerateEvent and GenerateEvent==true then
if self:IsAir()then
self:CreateEventCrash(timer.getTime(),UnitData)
else
self:CreateEventDead(timer.getTime(),UnitData)
end
elseif GenerateEvent==false then
else
self:CreateEventRemoveUnit(timer.getTime(),UnitData)
end
end
USERFLAG:New(self:GetName()):Set(100)
DCSGroup:destroy()
DCSGroup=nil
end
end
return nil
end
function GROUP:GetCategory()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCategory=DCSGroup:getCategory()
self:T3(GroupCategory)
return GroupCategory
end
return nil
end
function GROUP:GetCategoryName()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local CategoryNames={
[Group.Category.AIRPLANE]="Airplane",
[Group.Category.HELICOPTER]="Helicopter",
[Group.Category.GROUND]="Ground Unit",
[Group.Category.SHIP]="Ship",
[Group.Category.TRAIN]="Train",
}
local GroupCategory=DCSGroup:getCategory()
self:T3(GroupCategory)
return CategoryNames[GroupCategory]
end
return nil
end
function GROUP:GetCoalition()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCoalition=DCSGroup:getCoalition()
self:T3(GroupCoalition)
return GroupCoalition
end
return nil
end
function GROUP:GetCountry()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCountry=DCSGroup:getUnit(1):getCountry()
self:T3(GroupCountry)
return GroupCountry
end
return nil
end
function GROUP:HasAttribute(attribute,all)
local _units=self:GetUnits()
if _units then
local _allhave=true
local _onehas=false
for _,_unit in pairs(_units)do
local _unit=_unit
if _unit then
local _hastit=_unit:HasAttribute(attribute)
if _hastit==true then
_onehas=true
else
_allhave=false
end
end
end
if all==true then
return _allhave
else
return _onehas
end
end
return nil
end
function GROUP:GetSpeedMax()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local Units=self:GetUnits()
local speedmax=nil
for _,unit in pairs(Units)do
local unit=unit
local speed=unit:GetSpeedMax()
if speedmax==nil or speed<speedmax then
speedmax=speed
end
end
return speedmax
end
return nil
end
function GROUP:GetRange()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local Units=self:GetUnits()
local Rangemin=nil
for _,unit in pairs(Units)do
local unit=unit
local range=unit:GetRange()
if range then
if Rangemin==nil then
Rangemin=range
elseif range<Rangemin then
Rangemin=range
end
end
end
return Rangemin
end
return nil
end
function GROUP:GetUnits()
self:F2({self.GroupName})
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local DCSUnits=DCSGroup:getUnits()or{}
local Units={}
for Index,UnitData in pairs(DCSUnits)do
Units[#Units+1]=UNIT:Find(UnitData)
end
self:T3(Units)
return Units
end
return nil
end
function GROUP:GetPlayerUnits()
self:F2({self.GroupName})
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local DCSUnits=DCSGroup:getUnits()
local Units={}
for Index,UnitData in pairs(DCSUnits)do
local PlayerUnit=UNIT:Find(UnitData)
if PlayerUnit:GetPlayerName()then
Units[#Units+1]=PlayerUnit
end
end
self:T3(Units)
return Units
end
return nil
end
function GROUP:IsPlayer()
return self:GetUnit(1):IsPlayer()
end
function GROUP:GetUnit(UnitNumber)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local UnitFound=nil
local units=DCSGroup:getUnits()or{}
if units[UnitNumber]then
local UnitFound=UNIT:Find(units[UnitNumber])
if UnitFound then
return UnitFound
end
else
for _,_unit in pairs(units)do
local UnitFound=UNIT:Find(_unit)
if UnitFound then
return UnitFound
end
end
end
end
return nil
end
function GROUP:GetDCSUnit(UnitNumber)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
if DCSGroup.getUnit and DCSGroup:getUnit(UnitNumber)then
return DCSGroup:getUnit(UnitNumber)
else
local units=DCSGroup:getUnits()or{}
for _,_unit in pairs(units)do
if _unit and _unit:isExist()then
return _unit
end
end
end
end
return nil
end
function GROUP:GetSize()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupSize=DCSGroup:getSize()
if GroupSize then
return GroupSize
else
return 0
end
end
return nil
end
function GROUP:CountAliveUnits()
self:F3({self.GroupName})
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local units=self:GetUnits()
local n=0
for _,_unit in pairs(units)do
local unit=_unit
if unit and unit:IsAlive()then
n=n+1
end
end
return n
end
return 0
end
function GROUP:GetFirstUnitAlive()
self:F3({self.GroupName})
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local units=self:GetUnits()
for _,_unit in pairs(units)do
local unit=_unit
if unit and unit:IsAlive()then
return unit
end
end
end
return nil
end
function GROUP:GetFirstUnit()
self:F3({self.GroupName})
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local units=self:GetUnits()
return units[1]
end
return nil
end
function GROUP:GetVelocityVec3()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup and DCSGroup:isExist()then
local GroupUnits=DCSGroup:getUnits()
local GroupCount=#GroupUnits
local VelocityVec3={x=0,y=0,z=0}
for _,DCSUnit in pairs(GroupUnits)do
local UnitVelocityVec3=DCSUnit:getVelocity()
VelocityVec3.x=VelocityVec3.x+UnitVelocityVec3.x
VelocityVec3.y=VelocityVec3.y+UnitVelocityVec3.y
VelocityVec3.z=VelocityVec3.z+UnitVelocityVec3.z
end
VelocityVec3.x=VelocityVec3.x/GroupCount
VelocityVec3.y=VelocityVec3.y/GroupCount
VelocityVec3.z=VelocityVec3.z/GroupCount
return VelocityVec3
end
BASE:E({"Cannot GetVelocityVec3",Group=self,Alive=self:IsAlive()})
return nil
end
function GROUP:GetAltitude(FromGround)
self:F2(self.GroupName)
return self:GetHeight(FromGround)
end
function GROUP:GetHeight(FromGround)
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupUnits=DCSGroup:getUnits()
local GroupCount=#GroupUnits
local GroupHeight=0
for _,DCSUnit in pairs(GroupUnits)do
local GroupPosition=DCSUnit:getPosition()
if FromGround==true then
local LandHeight=land.getHeight({x=GroupPosition.p.x,y=GroupPosition.p.z})
GroupHeight=GroupHeight+(GroupPosition.p.y-LandHeight)
else
GroupHeight=GroupHeight+GroupPosition.p.y
end
end
return GroupHeight/GroupCount
end
return nil
end
function GROUP:GetInitialSize()
self:F3({self.GroupName})
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupInitialSize=DCSGroup:getInitialSize()
self:T3(GroupInitialSize)
return GroupInitialSize
end
return nil
end
function GROUP:GetDCSUnits()
self:F2({self.GroupName})
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local DCSUnits=DCSGroup:getUnits()
self:T3(DCSUnits)
return DCSUnits
end
return nil
end
function GROUP:Activate(delay)
self:F2({self.GroupName})
if delay and delay>0 then
self:ScheduleOnce(delay,GROUP.Activate,self)
else
trigger.action.activateGroup(self:GetDCSObject())
end
return self
end
function GROUP:GetTypeName()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupTypeName=DCSGroup:getUnit(1):getTypeName()
self:T3(GroupTypeName)
return(GroupTypeName)
end
return nil
end
function GROUP:GetNatoReportingName()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupTypeName=DCSGroup:getUnit(1):getTypeName()
self:T3(GroupTypeName)
return UTILS.GetReportingName(GroupTypeName)
end
return"Bogey"
end
function GROUP:GetPlayerName()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local PlayerName=DCSGroup:getUnit(1):getPlayerName()
self:T3(PlayerName)
return(PlayerName)
end
return nil
end
function GROUP:GetCallsign()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCallSign=DCSGroup:getUnit(1):getCallsign()
self:T3(GroupCallSign)
return GroupCallSign
end
BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()})
return nil
end
function GROUP:GetVec2()
local Unit=self:GetUnit(1)
if Unit then
local vec2=Unit:GetVec2()
return vec2
end
end
function GROUP:GetVec3()
local unit=self:GetUnit(1)
if unit then
local vec3=unit:GetVec3()
return vec3
end
self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName))
return nil
end
function GROUP:GetAverageVec3()
local units=self:GetUnits()or{}
local x=0;local y=0;local z=0;local n=0
for _,unit in pairs(units)do
local vec3=nil
if unit and unit:IsAlive()then
vec3=unit:GetVec3()
end
if vec3 then
x=x+vec3.x
y=y+vec3.y
z=z+vec3.z
n=n+1
end
end
if n>0 then
local Vec3={x=x/n,y=y/n,z=z/n}
return Vec3
end
return nil
end
function GROUP:GetPointVec2()
self:F2(self.GroupName)
local FirstUnit=self:GetUnit(1)
if FirstUnit then
local FirstUnitPointVec2=FirstUnit:GetPointVec2()
self:T3(FirstUnitPointVec2)
return FirstUnitPointVec2
end
BASE:E({"Cannot GetPointVec2",Group=self,Alive=self:IsAlive()})
return nil
end
function GROUP:GetAverageCoordinate()
local vec3=self:GetAverageVec3()
if vec3 then
local coord=COORDINATE:NewFromVec3(vec3)
local Heading=self:GetHeading()
coord.Heading=Heading
else
BASE:E({"Cannot GetAverageCoordinate",Group=self,Alive=self:IsAlive()})
return nil
end
end
function GROUP:GetCoordinate()
local Units=self:GetUnits()or{}
for _,_unit in pairs(Units)do
local FirstUnit=_unit
if FirstUnit then
local FirstUnitCoordinate=FirstUnit:GetCoordinate()
if FirstUnitCoordinate then
local Heading=self:GetHeading()
FirstUnitCoordinate.Heading=Heading
return FirstUnitCoordinate
end
end
end
BASE:E({"Cannot GetCoordinate",Group=self,Alive=self:IsAlive()})
end
function GROUP:GetRandomVec3(Radius)
self:F2(self.GroupName)
local FirstUnit=self:GetUnit(1)
if FirstUnit then
local FirstUnitRandomPointVec3=FirstUnit:GetRandomVec3(Radius)
self:T3(FirstUnitRandomPointVec3)
return FirstUnitRandomPointVec3
end
BASE:E({"Cannot GetRandomVec3",Group=self,Alive=self:IsAlive()})
return nil
end
function GROUP:GetHeading()
self:F2(self.GroupName)
self:F2(self.GroupName)
local GroupSize=self:GetSize()
local HeadingAccumulator=0
local n=0
local Units=self:GetUnits()
if GroupSize then
for _,unit in pairs(Units)do
if unit and unit:IsAlive()then
HeadingAccumulator=HeadingAccumulator+unit:GetHeading()
n=n+1
end
end
return math.floor(HeadingAccumulator/n)
end
BASE:E({"Cannot GetHeading",Group=self,Alive=self:IsAlive()})
return nil
end
function GROUP:GetFuelMin()
self:F3(self.ControllableName)
if not self:GetDCSObject()then
BASE:E({"Cannot GetFuel",Group=self,Alive=self:IsAlive()})
return 0
end
local min=65535
local unit=nil
local tmp=nil
for UnitID,UnitData in pairs(self:GetUnits())do
if UnitData and UnitData:IsAlive()then
tmp=UnitData:GetFuel()
if tmp<min then
min=tmp
unit=UnitData
end
end
end
return min,unit
end
function GROUP:GetFuelAvg()
self:F(self.ControllableName)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local GroupSize=self:GetSize()
local TotalFuel=0
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
local UnitFuel=Unit:GetFuel()or 0
self:F({Fuel=UnitFuel})
TotalFuel=TotalFuel+UnitFuel
end
local GroupFuel=TotalFuel/GroupSize
return GroupFuel
end
BASE:E({"Cannot GetFuel",Group=self,Alive=self:IsAlive()})
return 0
end
function GROUP:GetFuel()
return self:GetFuelAvg()
end
function GROUP:GetAmmunition()
self:F(self.ControllableName)
local DCSControllable=self:GetDCSObject()
local Ntot=0
local Nshells=0
local Nrockets=0
local Nmissiles=0
local Nbombs=0
local Narti=0
if DCSControllable then
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
local ntot,nshells,nrockets,nbombs,nmissiles,narti=Unit:GetAmmunition()
Ntot=Ntot+ntot
Nshells=Nshells+nshells
Nrockets=Nrockets+nrockets
Nmissiles=Nmissiles+nmissiles
Nbombs=Nbombs+nbombs
Narti=Narti+narti
end
end
return Ntot,Nshells,Nrockets,Nbombs,Nmissiles,Narti
end
do
function GROUP:IsInZone(Zone)
if self:IsAlive()then
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
local vec2=nil
if Unit then
vec2=Unit:GetVec2()
end
if vec2 and Zone:IsVec2InZone(vec2)then
return true
end
end
return false
end
return nil
end
function GROUP:IsCompletelyInZone(Zone)
self:F2({self.GroupName,Zone})
if not self:IsAlive()then return false end
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
if Zone:IsVec3InZone(Unit:GetVec3())then
else
return false
end
end
return true
end
function GROUP:IsPartlyInZone(Zone)
self:F2({self.GroupName,Zone})
local IsOneUnitInZone=false
local IsOneUnitOutsideZone=false
if not self:IsAlive()then return false end
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
if Zone:IsVec3InZone(Unit:GetVec3())then
IsOneUnitInZone=true
else
IsOneUnitOutsideZone=true
end
end
if IsOneUnitInZone and IsOneUnitOutsideZone then
return true
else
return false
end
end
function GROUP:IsPartlyOrCompletelyInZone(Zone)
return self:IsPartlyInZone(Zone)or self:IsCompletelyInZone(Zone)
end
function GROUP:IsNotInZone(Zone)
self:F2({self.GroupName,Zone})
if not self:IsAlive()then return true end
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
if Zone:IsVec3InZone(Unit:GetVec3())then
return false
end
end
return true
end
function GROUP:IsAnyInZone(Zone)
if not self:IsAlive()then return false end
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
if Zone:IsVec3InZone(Unit:GetVec3())then
return true
end
end
return false
end
function GROUP:CountInZone(Zone)
self:F2({self.GroupName,Zone})
local Count=0
if not self:IsAlive()then return Count end
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
if Zone:IsVec3InZone(Unit:GetVec3())then
Count=Count+1
end
end
return Count
end
function GROUP:IsAir()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local IsAirResult=DCSGroup:getCategory()==Group.Category.AIRPLANE or DCSGroup:getCategory()==Group.Category.HELICOPTER
self:T3(IsAirResult)
return IsAirResult
end
return nil
end
function GROUP:IsHelicopter()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCategory=DCSGroup:getCategory()
self:T2(GroupCategory)
return GroupCategory==Group.Category.HELICOPTER
end
return nil
end
function GROUP:IsAirPlane()
self:F2()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCategory=DCSGroup:getCategory()
self:T2(GroupCategory)
return GroupCategory==Group.Category.AIRPLANE
end
return nil
end
function GROUP:IsGround()
self:F2()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCategory=DCSGroup:getCategory()
self:T2(GroupCategory)
return GroupCategory==Group.Category.GROUND
end
return nil
end
function GROUP:IsShip()
self:F2()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCategory=DCSGroup:getCategory()
self:T2(GroupCategory)
return GroupCategory==Group.Category.SHIP
end
return nil
end
function GROUP:AllOnGround()
self:F2()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local AllOnGroundResult=true
for Index,UnitData in pairs(DCSGroup:getUnits())do
if UnitData:inAir()then
AllOnGroundResult=false
end
end
self:T3(AllOnGroundResult)
return AllOnGroundResult
end
return nil
end
end
do
function GROUP:SetAIOnOff(AIOnOff)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local DCSController=DCSGroup:getController()
if DCSController then
DCSController:setOnOff(AIOnOff)
return self
end
end
return nil
end
function GROUP:SetAIOn()
return self:SetAIOnOff(true)
end
function GROUP:SetAIOff()
return self:SetAIOnOff(false)
end
end
function GROUP:GetMaxVelocity()
self:F2()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupVelocityMax=0
for Index,UnitData in pairs(DCSGroup:getUnits())do
local UnitVelocityVec3=UnitData:getVelocity()
local UnitVelocity=math.abs(UnitVelocityVec3.x)+math.abs(UnitVelocityVec3.y)+math.abs(UnitVelocityVec3.z)
if UnitVelocity>GroupVelocityMax then
GroupVelocityMax=UnitVelocity
end
end
return GroupVelocityMax
end
return nil
end
function GROUP:GetMinHeight()
self:F2()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupHeightMin=999999999
for Index,UnitData in pairs(DCSGroup:getUnits())do
local UnitData=UnitData
local UnitHeight=UnitData:getPoint()
if UnitHeight<GroupHeightMin then
GroupHeightMin=UnitHeight
end
end
return GroupHeightMin
end
return nil
end
function GROUP:GetMaxHeight()
self:F2()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupHeightMax=-999999999
for Index,UnitData in pairs(DCSGroup:getUnits())do
local UnitData=UnitData
local UnitHeight=UnitData:getPoint()
if UnitHeight>GroupHeightMax then
GroupHeightMax=UnitHeight
end
end
return GroupHeightMax
end
return nil
end
function GROUP:GetTemplate()
local GroupName=self:GetName()
return UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName))
end
function GROUP:GetTemplateRoutePoints()
local GroupName=self:GetName()
return UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName).route.points)
end
function GROUP:SetTemplateControlled(Template,Controlled)
Template.uncontrolled=not Controlled
return Template
end
function GROUP:SetTemplateCountry(Template,CountryID)
Template.CountryID=CountryID
return Template
end
function GROUP:SetTemplateCoalition(Template,CoalitionID)
Template.CoalitionID=CoalitionID
return Template
end
function GROUP:InitHeading(Heading)
self.InitRespawnHeading=Heading
return self
end
function GROUP:InitHeight(Height)
self.InitRespawnHeight=Height
return self
end
function GROUP:InitZone(Zone)
self.InitRespawnZone=Zone
return self
end
function GROUP:InitRandomizePositionZone(PositionZone)
self.InitRespawnRandomizePositionZone=PositionZone
self.InitRespawnRandomizePositionInner=nil
self.InitRespawnRandomizePositionOuter=nil
return self
end
function GROUP:InitRandomizePositionRadius(OuterRadius,InnerRadius)
self.InitRespawnRandomizePositionZone=nil
self.InitRespawnRandomizePositionOuter=OuterRadius
self.InitRespawnRandomizePositionInner=InnerRadius
return self
end
function GROUP:InitCoordinate(coordinate)
self:F({coordinate=coordinate})
self.InitCoord=coordinate
return self
end
function GROUP:InitRadioCommsOnOff(switch)
self:F({switch=switch})
if switch==true or switch==nil then
self.InitRespawnRadio=true
else
self.InitRespawnRadio=false
end
return self
end
function GROUP:InitRadioFrequency(frequency)
self:F({frequency=frequency})
self.InitRespawnFreq=frequency
return self
end
function GROUP:InitRadioModulation(modulation)
self:F({modulation=modulation})
if modulation and modulation:lower()=="fm"then
self.InitRespawnModu=radio.modulation.FM
else
self.InitRespawnModu=radio.modulation.AM
end
return self
end
function GROUP:InitModex(modex)
self:F({modex=modex})
if modex then
self.InitRespawnModex=tonumber(modex)
end
return self
end
function GROUP:Respawn(Template,Reset)
Template=Template or self:GetTemplate()
local function _Heading(course)
local h
if course<=180 then
h=math.rad(course)
else
h=-math.rad(360-course)
end
return h
end
if self:IsAlive()then
local Zone=self.InitRespawnZone
local Vec3=Zone and Zone:GetVec3()or self:GetVec3()
local From={x=Template.x,y=Template.y}
Template.x=Vec3.x
Template.y=Vec3.z
self:F(#Template.units)
if Reset==true then
for UnitID,UnitData in pairs(self:GetUnits())do
local GroupUnit=UnitData
self:F(GroupUnit:GetName())
if GroupUnit:IsAlive()then
self:I("FF Alive")
local GroupUnitVec3=GroupUnit:GetVec3()
if Zone then
if self.InitRespawnRandomizePositionZone then
GroupUnitVec3=Zone:GetRandomVec3()
else
if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then
GroupUnitVec3=POINT_VEC3:NewFromVec2(From):GetRandomPointVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner)
else
GroupUnitVec3=Zone:GetVec3()
end
end
end
if self.InitCoord then
GroupUnitVec3=self.InitCoord:GetVec3()
end
Template.units[UnitID].alt=self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y
if Zone then
Template.units[UnitID].x=(Template.units[UnitID].x-From.x)+GroupUnitVec3.x
Template.units[UnitID].y=(Template.units[UnitID].y-From.y)+GroupUnitVec3.z
else
Template.units[UnitID].x=GroupUnitVec3.x
Template.units[UnitID].y=GroupUnitVec3.z
end
Template.units[UnitID].heading=_Heading(self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading())
Template.units[UnitID].psi=-Template.units[UnitID].heading
self:F({UnitID,Template.units[UnitID],Template.units[UnitID]})
end
end
elseif Reset==false then
for UnitID,TemplateUnitData in pairs(Template.units)do
self:F("Reset")
local GroupUnitVec3={x=TemplateUnitData.x,y=TemplateUnitData.alt,z=TemplateUnitData.y}
if Zone then
if self.InitRespawnRandomizePositionZone then
GroupUnitVec3=Zone:GetRandomVec3()
else
if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then
GroupUnitVec3=POINT_VEC3:NewFromVec2(From):GetRandomPointVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner)
else
GroupUnitVec3=Zone:GetVec3()
end
end
end
if self.InitCoord then
GroupUnitVec3=self.InitCoord:GetVec3()
end
Template.units[UnitID].alt=self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y
Template.units[UnitID].x=(Template.units[UnitID].x-From.x)+GroupUnitVec3.x
Template.units[UnitID].y=(Template.units[UnitID].y-From.y)+GroupUnitVec3.z
Template.units[UnitID].heading=self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading
self:F({UnitID,Template.units[UnitID],Template.units[UnitID]})
end
else
local units=self:GetUnits()
for UnitID,Unit in pairs(Template.units)do
for _,_unit in pairs(units)do
local unit=_unit
if unit:GetName()==Unit.name then
local coord=unit:GetCoordinate()
local heading=unit:GetHeading()
Unit.x=coord.x
Unit.y=coord.z
Unit.alt=coord.y
Unit.heading=math.rad(heading)
Unit.psi=-Unit.heading
end
end
end
end
end
if self.InitRespawnModex then
for UnitID=1,#Template.units do
Template.units[UnitID].onboard_num=string.format("%03d",self.InitRespawnModex+(UnitID-1))
end
end
if self.InitRespawnRadio then
Template.communication=self.InitRespawnRadio
end
if self.InitRespawnFreq then
Template.frequency=self.InitRespawnFreq
end
if self.InitRespawnModu then
Template.modulation=self.InitRespawnModu
end
self:Destroy(false)
self:T({Template=Template})
_DATABASE:Spawn(Template)
self:ResetEvents()
return self
end
function GROUP:RespawnAtCurrentAirbase(SpawnTemplate,Takeoff,Uncontrolled)
self:F2({SpawnTemplate,Takeoff,Uncontrolled})
if self and self:IsAlive()then
local airbase=self:GetCoordinate():GetClosestAirbase()
if airbase then
self:F2("Closest airbase = "..airbase:GetName())
else
self:E("ERROR: could not find closest airbase!")
return nil
end
Takeoff=Takeoff or SPAWN.Takeoff.Hot
local AirbaseCoord=airbase:GetCoordinate()
SpawnTemplate=SpawnTemplate or self:GetTemplate()
if SpawnTemplate then
local SpawnPoint=SpawnTemplate.route.points[1]
SpawnPoint.linkUnit=nil
SpawnPoint.helipadId=nil
SpawnPoint.airdromeId=nil
local AirbaseID=airbase:GetID()
local AirbaseCategory=airbase:GetAirbaseCategory()
if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.AIRDROME then
SpawnPoint.airdromeId=AirbaseID
end
SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1]
SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2]
local units=self:GetUnits()
local x
local y
for UnitID=1,#units do
local unit=units[UnitID]
local Parkingspot,TermialID,Distance=unit:GetCoordinate():GetClosestParkingSpot(airbase)
self:T2(string.format("Closest parking spot distance = %s, terminal ID=%s",tostring(Distance),tostring(TermialID)))
local uc=unit:GetCoordinate()
SpawnTemplate.units[UnitID].x=uc.x
SpawnTemplate.units[UnitID].y=uc.z
SpawnTemplate.units[UnitID].alt=uc.y
SpawnTemplate.units[UnitID].parking=TermialID
SpawnTemplate.units[UnitID].parking_id=nil
end
SpawnPoint.x=SpawnTemplate.units[1].x
SpawnPoint.y=SpawnTemplate.units[1].y
SpawnPoint.alt=SpawnTemplate.units[1].alt
SpawnTemplate.x=SpawnTemplate.units[1].x
SpawnTemplate.y=SpawnTemplate.units[1].y
SpawnTemplate.uncontrolled=Uncontrolled
if self.InitRespawnRadio then
SpawnTemplate.communication=self.InitRespawnRadio
end
if self.InitRespawnFreq then
SpawnTemplate.frequency=self.InitRespawnFreq
end
if self.InitRespawnModu then
SpawnTemplate.modulation=self.InitRespawnModu
end
self:Destroy(false)
_DATABASE:Spawn(SpawnTemplate)
self:ResetEvents()
return self
end
else
self:E("WARNING: GROUP is not alive!")
end
return nil
end
function GROUP:GetTaskMission()
self:F2(self.GroupName)
return routines.utils.deepCopy(_DATABASE.Templates.Groups[self.GroupName].Template)
end
function GROUP:GetTaskRoute()
self:F2(self.GroupName)
return routines.utils.deepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points)
end
function GROUP:CopyRoute(Begin,End,Randomize,Radius)
self:F2({Begin,End})
local Points={}
local GroupName=string.match(self:GetName(),".*#")
if GroupName then
GroupName=GroupName:sub(1,-2)
else
GroupName=self:GetName()
end
self:T3({GroupName})
local Template=_DATABASE.Templates.Groups[GroupName].Template
if Template then
if not Begin then
Begin=0
end
if not End then
End=0
end
for TPointID=Begin+1,#Template.route.points-End do
if Template.route.points[TPointID]then
Points[#Points+1]=routines.utils.deepCopy(Template.route.points[TPointID])
if Randomize then
if not Radius then
Radius=500
end
Points[#Points].x=Points[#Points].x+math.random(Radius*-1,Radius)
Points[#Points].y=Points[#Points].y+math.random(Radius*-1,Radius)
end
end
end
return Points
else
error("Template not found for Group : "..GroupName)
end
return nil
end
function GROUP:CalculateThreatLevelA2G()
local MaxThreatLevelA2G=0
for UnitName,UnitData in pairs(self:GetUnits())do
local ThreatUnit=UnitData
local ThreatLevelA2G=ThreatUnit:GetThreatLevel()
if ThreatLevelA2G>MaxThreatLevelA2G then
MaxThreatLevelA2G=ThreatLevelA2G
end
end
self:T3(MaxThreatLevelA2G)
return MaxThreatLevelA2G
end
function GROUP:GetThreatLevel()
local threatlevelMax=0
for UnitName,UnitData in pairs(self:GetUnits())do
local ThreatUnit=UnitData
local threatlevel=ThreatUnit:GetThreatLevel()
if threatlevel>threatlevelMax then
threatlevelMax=threatlevel
end
end
return threatlevelMax
end
function GROUP:InAir()
self:F2(self.GroupName)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local DCSUnit=DCSGroup:getUnit(1)
if DCSUnit then
local GroupInAir=DCSGroup:getUnit(1):inAir()
self:T3(GroupInAir)
return GroupInAir
end
end
return nil
end
function GROUP:IsAirborne(AllUnits)
self:F2(self.GroupName)
local units=self:GetUnits()
if units then
if AllUnits then
for _,_unit in pairs(units)do
local unit=_unit
if unit then
local inair=unit:InAir()
if not inair then
return false
end
end
end
return true
else
for _,_unit in pairs(units)do
local unit=_unit
if unit then
local inair=unit:InAir()
if inair then
return true
end
end
return false
end
end
end
return nil
end
function GROUP:GetDCSDesc(n)
n=n or 1
local unit=self:GetUnit(n)
if unit and unit:IsAlive()~=nil then
local desc=unit:GetDesc()
return desc
end
return nil
end
function GROUP:GetAttribute()
local attribute=GROUP.Attribute.OTHER_UNKNOWN
if self then
local transportplane=self:HasAttribute("Transports")and self:HasAttribute("Planes")
local awacs=self:HasAttribute("AWACS")
local fighter=self:HasAttribute("Fighters")or self:HasAttribute("Interceptors")or self:HasAttribute("Multirole fighters")or(self:HasAttribute("Bombers")and not self:HasAttribute("Strategic bombers"))
local bomber=self:HasAttribute("Strategic bombers")
local tanker=self:HasAttribute("Tankers")
local uav=self:HasAttribute("UAVs")
local transporthelo=self:HasAttribute("Transport helicopters")
local attackhelicopter=self:HasAttribute("Attack helicopters")
local apc=self:HasAttribute("APC")
local truck=self:HasAttribute("Trucks")and self:GetCategory()==Group.Category.GROUND
local infantry=self:HasAttribute("Infantry")
local artillery=self:HasAttribute("Artillery")
local tank=self:HasAttribute("Old Tanks")or self:HasAttribute("Modern Tanks")or self:HasAttribute("Tanks")
local aaa=self:HasAttribute("AAA")and(not self:HasAttribute("SAM elements"))
local ewr=self:HasAttribute("EWR")
local ifv=self:HasAttribute("IFV")
local sam=self:HasAttribute("SAM elements")or self:HasAttribute("Optical Tracker")
local train=self:GetCategory()==Group.Category.TRAIN
local aircraftcarrier=self:HasAttribute("Aircraft Carriers")
local warship=self:HasAttribute("Heavy armed ships")
local armedship=self:HasAttribute("Armed ships")
local unarmedship=self:HasAttribute("Unarmed ships")
if fighter then
attribute=GROUP.Attribute.AIR_FIGHTER
elseif bomber then
attribute=GROUP.Attribute.AIR_BOMBER
elseif awacs then
attribute=GROUP.Attribute.AIR_AWACS
elseif transportplane then
attribute=GROUP.Attribute.AIR_TRANSPORTPLANE
elseif tanker then
attribute=GROUP.Attribute.AIR_TANKER
elseif attackhelicopter then
attribute=GROUP.Attribute.AIR_ATTACKHELO
elseif transporthelo then
attribute=GROUP.Attribute.AIR_TRANSPORTHELO
elseif uav then
attribute=GROUP.Attribute.AIR_UAV
elseif ewr then
attribute=GROUP.Attribute.GROUND_EWR
elseif sam then
attribute=GROUP.Attribute.GROUND_SAM
elseif aaa then
attribute=GROUP.Attribute.GROUND_AAA
elseif artillery then
attribute=GROUP.Attribute.GROUND_ARTILLERY
elseif tank then
attribute=GROUP.Attribute.GROUND_TANK
elseif ifv then
attribute=GROUP.Attribute.GROUND_IFV
elseif apc then
attribute=GROUP.Attribute.GROUND_APC
elseif infantry then
attribute=GROUP.Attribute.GROUND_INFANTRY
elseif truck then
attribute=GROUP.Attribute.GROUND_TRUCK
elseif train then
attribute=GROUP.Attribute.GROUND_TRAIN
elseif aircraftcarrier then
attribute=GROUP.Attribute.NAVAL_AIRCRAFTCARRIER
elseif warship then
attribute=GROUP.Attribute.NAVAL_WARSHIP
elseif armedship then
attribute=GROUP.Attribute.NAVAL_ARMEDSHIP
elseif unarmedship then
attribute=GROUP.Attribute.NAVAL_UNARMEDSHIP
else
if self:IsGround()then
attribute=GROUP.Attribute.GROUND_OTHER
elseif self:IsShip()then
attribute=GROUP.Attribute.NAVAL_OTHER
elseif self:IsAir()then
attribute=GROUP.Attribute.AIR_OTHER
else
attribute=GROUP.Attribute.OTHER_UNKNOWN
end
end
end
return attribute
end
do
function GROUP:RouteRTB(RTBAirbase,Speed)
self:F({RTBAirbase:GetName(),Speed})
local DCSGroup=self:GetDCSObject()
if DCSGroup then
if RTBAirbase then
local Speed=Speed or self:GetSpeedMax()*0.8
local coord=self:GetCoordinate()
local PointFrom=coord:WaypointAirTurningPoint(nil,Speed)
local PointLanding=RTBAirbase:GetCoordinate():WaypointAirLanding(Speed,RTBAirbase)
local Points={PointFrom,PointLanding}
self:T3(Points)
local Template=self:GetTemplate()
Template.route.points=Points
self:Respawn(Template,true)
self:Route(Points)
else
self:ClearTasks()
end
end
return self
end
end
function GROUP:OnReSpawn(ReSpawnFunction)
self.ReSpawnFunction=ReSpawnFunction
end
do
function GROUP:HandleEvent(Event,EventFunction,...)
self:EventDispatcher():OnEventForGroup(self:GetName(),EventFunction,self,Event,...)
return self
end
function GROUP:UnHandleEvent(Event)
self:EventDispatcher():RemoveEvent(self,Event)
return self
end
function GROUP:ResetEvents()
self:EventDispatcher():Reset(self)
for UnitID,UnitData in pairs(self:GetUnits())do
UnitData:ResetEvents()
end
return self
end
end
do
function GROUP:GetPlayerNames()
local HasPlayers=false
local PlayerNames={}
local Units=self:GetUnits()
for UnitID,UnitData in pairs(Units)do
local Unit=UnitData
local PlayerName=Unit:GetPlayerName()
if PlayerName and PlayerName~=""then
PlayerNames=PlayerNames or{}
table.insert(PlayerNames,PlayerName)
HasPlayers=true
end
end
if HasPlayers==true then
self:F2(PlayerNames)
return PlayerNames
end
return nil
end
function GROUP:GetPlayerCount()
local PlayerCount=0
local Units=self:GetUnits()
for UnitID,UnitData in pairs(Units or{})do
local Unit=UnitData
local PlayerName=Unit:GetPlayerName()
if PlayerName and PlayerName~=""then
PlayerCount=PlayerCount+1
end
end
return PlayerCount
end
end
function GROUP:EnableEmission(switch)
self:F2(self.GroupName)
local switch=switch or false
local DCSUnit=self:GetDCSObject()
if DCSUnit then
DCSUnit:enableEmission(switch)
end
return self
end
function GROUP:SetCommandInvisible(switch)
self:F2(self.GroupName)
if switch==nil then
switch=false
end
local SetInvisible={id='SetInvisible',params={value=switch}}
self:SetCommand(SetInvisible)
return self
end
function GROUP:SetCommandImmortal(switch)
self:F2(self.GroupName)
if switch==nil then
switch=false
end
local SetImmortal={id='SetImmortal',params={value=switch}}
self:SetCommand(SetImmortal)
return self
end
function GROUP:GetSkill()
self:F2(self.GroupName)
local unit=self:GetUnit(1)
local name=unit:GetName()
local skill=_DATABASE.Templates.Units[name].Template.skill or"Random"
return skill
end
function GROUP:GetHighestThreat()
local units=self:GetUnits()
if units then
local threat=nil;local maxtl=0
for _,_unit in pairs(units or{})do
local unit=_unit
if unit and unit:IsAlive()then
local tl=unit:GetThreatLevel()
if tl>maxtl then
maxtl=tl
threat=unit
end
end
end
return threat,maxtl
end
return nil,nil
end
function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
local callsign="Ghost 1"
if self:IsAlive()then
local IsPlayer=self:IsPlayer()
local shortcallsign=self:GetCallsign()or"unknown91"
local callsignroot=string.match(shortcallsign,'(%a+)')
local groupname=self:GetName()
local callnumber=string.match(shortcallsign,"(%d+)$")or"91"
local callnumbermajor=string.char(string.byte(callnumber,1))
local callnumberminor=string.char(string.byte(callnumber,2))
local personalized=false
if IsPlayer and string.find(groupname,"#")then
if Keepnumber then
shortcallsign=string.match(groupname,"#(.+)")or"Ghost 111"
else
shortcallsign=string.match(groupname,"#%s*([%a]+)")or"Ghost"
end
personalized=true
elseif IsPlayer and string.find(self:GetPlayerName(),"|")then
shortcallsign=string.match(self:GetPlayerName(),"|%s*([%a]+)")or string.match(self:GetPlayerName(),"|%s*([%d]+)")or"Ghost"
personalized=true
end
if(not personalized)and CallsignTranslations and CallsignTranslations[callsignroot]then
callsignroot=CallsignTranslations[callsignroot]
end
if personalized then
shortcallsign=string.gsub(shortcallsign,"^%s*","")
shortcallsign=string.gsub(shortcallsign,"%s*$","")
if Keepnumber then
return shortcallsign
elseif ShortCallsign then
callsign=shortcallsign.." "..callnumbermajor
else
callsign=shortcallsign.." "..callnumbermajor.." "..callnumberminor
end
return callsign
end
if ShortCallsign then
callsign=callsignroot.." "..callnumbermajor
else
callsign=callsignroot.." "..callnumbermajor.." "..callnumberminor
end
end
return callsign
end
function GROUP:SetAsRecoveryTanker(CarrierGroup,Speed,ToKIAS,Altitude,Delay,LastWaypoint)
local speed=ToKIAS==true and UTILS.KnotsToAltKIAS(Speed,Altitude)or Speed
speed=UTILS.KnotsToMps(speed)
local alt=UTILS.FeetToMeters(Altitude)
local delay=Delay or 1
local task=self:TaskRecoveryTanker(CarrierGroup,speed,alt,LastWaypoint)
self:SetTask(task,delay)
local tankertask=self:EnRouteTaskTanker()
self:PushTask(tankertask,delay+2)
return self
end
UNIT={
ClassName="UNIT",
UnitName=nil,
GroupName=nil,
}
function UNIT:Register(UnitName)
local self=BASE:Inherit(self,CONTROLLABLE:New(UnitName))
self.UnitName=UnitName
local unit=Unit.getByName(self.UnitName)
if unit then
local group=unit:getGroup()
if group then
self.GroupName=group:getName()
end
end
self:SetEventPriority(3)
return self
end
function UNIT:Find(DCSUnit)
if DCSUnit then
local UnitName=DCSUnit:getName()
local UnitFound=_DATABASE:FindUnit(UnitName)
return UnitFound
end
return nil
end
function UNIT:FindByName(UnitName)
local UnitFound=_DATABASE:FindUnit(UnitName)
return UnitFound
end
function UNIT:Name()
return self.UnitName
end
function UNIT:GetDCSObject()
local DCSUnit=Unit.getByName(self.UnitName)
if DCSUnit then
return DCSUnit
end
return nil
end
function UNIT:GetAltitude(FromGround)
local DCSUnit=Unit.getByName(self.UnitName)
if DCSUnit then
local altitude=0
local point=DCSUnit:getPoint()
altitude=point.y
if FromGround then
local land=land.getHeight({x=point.x,y=point.z})or 0
altitude=altitude-land
end
return altitude
end
return nil
end
function UNIT:ReSpawnAt(Coordinate,Heading)
self:T(self:Name())
local SpawnGroupTemplate=UTILS.DeepCopy(_DATABASE:GetGroupTemplateFromUnitName(self:Name()))
self:T(SpawnGroupTemplate)
local SpawnGroup=self:GetGroup()
self:T({SpawnGroup=SpawnGroup})
if SpawnGroup then
local Vec3=SpawnGroup:GetVec3()
SpawnGroupTemplate.x=Coordinate.x
SpawnGroupTemplate.y=Coordinate.z
self:F(#SpawnGroupTemplate.units)
for UnitID,UnitData in pairs(SpawnGroup:GetUnits()or{})do
local GroupUnit=UnitData
self:F(GroupUnit:GetName())
if GroupUnit:IsAlive()then
local GroupUnitVec3=GroupUnit:GetVec3()
local GroupUnitHeading=GroupUnit:GetHeading()
SpawnGroupTemplate.units[UnitID].alt=GroupUnitVec3.y
SpawnGroupTemplate.units[UnitID].x=GroupUnitVec3.x
SpawnGroupTemplate.units[UnitID].y=GroupUnitVec3.z
SpawnGroupTemplate.units[UnitID].heading=GroupUnitHeading
self:F({UnitID,SpawnGroupTemplate.units[UnitID],SpawnGroupTemplate.units[UnitID]})
end
end
end
for UnitTemplateID,UnitTemplateData in pairs(SpawnGroupTemplate.units)do
self:T({UnitTemplateData.name,self:Name()})
SpawnGroupTemplate.units[UnitTemplateID].unitId=nil
if UnitTemplateData.name==self:Name()then
self:T("Adjusting")
SpawnGroupTemplate.units[UnitTemplateID].alt=Coordinate.y
SpawnGroupTemplate.units[UnitTemplateID].x=Coordinate.x
SpawnGroupTemplate.units[UnitTemplateID].y=Coordinate.z
SpawnGroupTemplate.units[UnitTemplateID].heading=Heading
self:F({UnitTemplateID,SpawnGroupTemplate.units[UnitTemplateID],SpawnGroupTemplate.units[UnitTemplateID]})
else
self:F(SpawnGroupTemplate.units[UnitTemplateID].name)
local GroupUnit=UNIT:FindByName(SpawnGroupTemplate.units[UnitTemplateID].name)
if GroupUnit and GroupUnit:IsAlive()then
local GroupUnitVec3=GroupUnit:GetVec3()
local GroupUnitHeading=GroupUnit:GetHeading()
UnitTemplateData.alt=GroupUnitVec3.y
UnitTemplateData.x=GroupUnitVec3.x
UnitTemplateData.y=GroupUnitVec3.z
UnitTemplateData.heading=GroupUnitHeading
else
if SpawnGroupTemplate.units[UnitTemplateID].name~=self:Name()then
self:T("nilling")
SpawnGroupTemplate.units[UnitTemplateID].delete=true
end
end
end
end
local i=1
while i<=#SpawnGroupTemplate.units do
local UnitTemplateData=SpawnGroupTemplate.units[i]
self:T(UnitTemplateData.name)
if UnitTemplateData.delete then
table.remove(SpawnGroupTemplate.units,i)
else
i=i+1
end
end
SpawnGroupTemplate.groupId=nil
self:T(SpawnGroupTemplate)
_DATABASE:Spawn(SpawnGroupTemplate)
end
function UNIT:IsActive()
self:F2(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitIsActive=DCSUnit:isActive()
return UnitIsActive
end
return nil
end
function UNIT:IsAlive()
self:F3(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitIsAlive=DCSUnit:isExist()and DCSUnit:isActive()
return UnitIsAlive
end
return nil
end
function UNIT:GetCallsign()
self:F2(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitCallSign=DCSUnit:getCallsign()
if UnitCallSign==""then
UnitCallSign=DCSUnit:getName()
end
return UnitCallSign
end
self:F(self.ClassName.." "..self.UnitName.." not found!")
return nil
end
function UNIT:IsPlayer()
local group=self:GetGroup()
if not group then return false end
local units=group:GetTemplate().units
for _,unit in pairs(units)do
if unit.name==self:GetName()and(unit.skill=="Client"or unit.skill=="Player")then
return true
end
end
return false
end
function UNIT:GetPlayerName()
self:F(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local PlayerName=DCSUnit:getPlayerName()
return PlayerName
end
return nil
end
function UNIT:IsClient()
if _DATABASE.CLIENTS[self.UnitName]then
return true
end
return false
end
function UNIT:GetClient()
local client=_DATABASE.CLIENTS[self.UnitName]
if client then
return client
end
return nil
end
function UNIT:GetNatoReportingName()
local typename=self:GetTypeName()
return UTILS.GetReportingName(typename)
end
function UNIT:GetNumber()
self:F2(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitNumber=DCSUnit:getNumber()
return UnitNumber
end
return nil
end
function UNIT:GetSpeedMax()
self:F2(self.UnitName)
local Desc=self:GetDesc()
if Desc then
local SpeedMax=Desc.speedMax
return SpeedMax*3.6
end
return 0
end
function UNIT:GetRange()
self:F2(self.UnitName)
local Desc=self:GetDesc()
if Desc then
local Range=Desc.range
if Range then
Range=Range*1000
else
Range=10000000
end
return Range
end
return nil
end
function UNIT:IsRefuelable()
self:F2(self.UnitName)
local refuelable=self:HasAttribute("Refuelable")
local system=nil
local Desc=self:GetDesc()
if Desc and Desc.tankerType then
system=Desc.tankerType
end
return refuelable,system
end
function UNIT:IsTanker()
self:F2(self.UnitName)
local tanker=self:HasAttribute("Tankers")
local system=nil
if tanker then
local Desc=self:GetDesc()
if Desc and Desc.tankerType then
system=Desc.tankerType
end
local typename=self:GetTypeName()
if typename=="IL-78M"then
system=1
elseif typename=="KC130"then
system=1
elseif typename=="KC135BDA"then
system=1
elseif typename=="KC135MPRS"then
system=1
elseif typename=="S-3B Tanker"then
system=1
end
end
return tanker,system
end
function UNIT:IsAmmoSupply()
local typename=self:GetTypeName()
if typename=="M 818"then
return true
elseif typename=="Ural-375"then
return true
elseif typename=="ZIL-135"then
return true
end
return false
end
function UNIT:IsFuelSupply()
local typename=self:GetTypeName()
if typename=="M978 HEMTT Tanker"then
return true
elseif typename=="ATMZ-5"then
return true
elseif typename=="ATMZ-10"then
return true
elseif typename=="ATZ-5"then
return true
end
return false
end
function UNIT:GetGroup()
self:F2(self.UnitName)
local UnitGroup=GROUP:FindByName(self.GroupName)
if UnitGroup then
return UnitGroup
else
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local grp=DCSUnit:getGroup()
if grp then
local UnitGroup=GROUP:FindByName(grp:getName())
return UnitGroup
end
end
end
return nil
end
function UNIT:GetPrefix()
self:F2(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitPrefix=string.match(self.UnitName,".*#"):sub(1,-2)
self:T3(UnitPrefix)
return UnitPrefix
end
return nil
end
function UNIT:GetAmmo()
self:F2(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitAmmo=DCSUnit:getAmmo()
return UnitAmmo
end
return nil
end
function UNIT:SetUnitInternalCargo(mass)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
trigger.action.setUnitInternalCargo(DCSUnit:getName(),mass)
end
return self
end
function UNIT:GetAmmunition()
local nammo=0
local nshells=0
local nrockets=0
local nmissiles=0
local nbombs=0
local narti=0
local unit=self
local ammotable=unit:GetAmmo()
if ammotable then
local weapons=#ammotable
for w=1,weapons do
local Nammo=ammotable[w]["count"]
local Tammo=ammotable[w]["desc"]["typeName"]
local Category=ammotable[w].desc.category
local MissileCategory=nil
if Category==Weapon.Category.MISSILE then
MissileCategory=ammotable[w].desc.missileCategory
end
if Category==Weapon.Category.SHELL then
nshells=nshells+Nammo
if ammotable[w].desc.warhead and ammotable[w].desc.warhead.explosiveMass and ammotable[w].desc.warhead.explosiveMass>0 then
narti=narti+Nammo
end
elseif Category==Weapon.Category.ROCKET then
nrockets=nrockets+Nammo
elseif Category==Weapon.Category.BOMB then
nbombs=nbombs+Nammo
elseif Category==Weapon.Category.MISSILE then
if MissileCategory==Weapon.MissileCategory.AAM then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.BM then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.OTHER then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.SAM then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.CRUISE then
nmissiles=nmissiles+Nammo
end
end
end
end
nammo=nshells+nrockets+nmissiles+nbombs
return nammo,nshells,nrockets,nbombs,nmissiles,narti
end
function UNIT:GetSensors()
self:F2(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitSensors=DCSUnit:getSensors()
return UnitSensors
end
return nil
end
function UNIT:HasSensors(...)
self:F2(arg)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local HasSensors=DCSUnit:hasSensors(unpack(arg))
return HasSensors
end
return nil
end
function UNIT:HasSEAD()
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitSEADAttributes=DCSUnit:getDesc().attributes
local HasSEAD=false
if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"]and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"]==true or
UnitSEADAttributes["RADAR_BAND2_FOR_ARM"]and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"]==true or
UnitSEADAttributes["Optical Tracker"]and UnitSEADAttributes["Optical Tracker"]==true
then
HasSEAD=true
end
return HasSEAD
end
return nil
end
function UNIT:GetRadar()
self:F2(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitRadarOn,UnitRadarObject=DCSUnit:getRadar()
return UnitRadarOn,UnitRadarObject
end
return nil,nil
end
function UNIT:GetFuel()
self:F3(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitFuel=DCSUnit:getFuel()
return UnitFuel
end
return nil
end
function UNIT:GetUnits()
self:F3({self.UnitName})
local DCSUnit=self:GetDCSObject()
local Units={}
if DCSUnit then
Units[1]=UNIT:Find(DCSUnit)
self:T3(Units)
return Units
end
return nil
end
function UNIT:GetLife()
self:F2(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitLife=DCSUnit:getLife()
return UnitLife
end
return-1
end
function UNIT:GetLife0()
self:F2(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitLife0=DCSUnit:getLife0()
return UnitLife0
end
return 0
end
function UNIT:GetLifeRelative()
self:F2(self.UnitName)
if self and self:IsAlive()then
local life0=self:GetLife0()
local lifeN=self:GetLife()
return lifeN/life0
end
return-1
end
function UNIT:GetDamageRelative()
self:F2(self.UnitName)
if self and self:IsAlive()then
return 1-self:GetLifeRelative()
end
return 1
end
function UNIT:GetUnitCategory()
self:F3(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
return DCSUnit:getDesc().category
end
return nil
end
function UNIT:GetCategoryName()
self:F3(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local CategoryNames={
[Unit.Category.AIRPLANE]="Airplane",
[Unit.Category.HELICOPTER]="Helicopter",
[Unit.Category.GROUND_UNIT]="Ground Unit",
[Unit.Category.SHIP]="Ship",
[Unit.Category.STRUCTURE]="Structure",
}
local UnitCategory=DCSUnit:getDesc().category
self:T3(UnitCategory)
return CategoryNames[UnitCategory]
end
return nil
end
function UNIT:GetThreatLevel()
local ThreatLevel=0
local ThreatText=""
local Descriptor=self:GetDesc()
if Descriptor then
local Attributes=Descriptor.attributes
if self:IsGround()then
local ThreatLevels={
"Unarmed",
"Infantry",
"Old Tanks & APCs",
"Tanks & IFVs without ATGM",
"Tanks & IFV with ATGM",
"Modern Tanks",
"AAA",
"IR Guided SAMs",
"SR SAMs",
"MR SAMs",
"LR SAMs"
}
if Attributes["LR SAM"]then ThreatLevel=10
elseif Attributes["MR SAM"]then ThreatLevel=9
elseif Attributes["SR SAM"]and
not Attributes["IR Guided SAM"]then ThreatLevel=8
elseif(Attributes["SR SAM"]or Attributes["MANPADS"])and
Attributes["IR Guided SAM"]then ThreatLevel=7
elseif Attributes["AAA"]then ThreatLevel=6
elseif Attributes["Modern Tanks"]then ThreatLevel=5
elseif(Attributes["Tanks"]or Attributes["IFV"])and
Attributes["ATGM"]then ThreatLevel=4
elseif(Attributes["Tanks"]or Attributes["IFV"])and
not Attributes["ATGM"]then ThreatLevel=3
elseif Attributes["Old Tanks"]or Attributes["APC"]or Attributes["Artillery"]then ThreatLevel=2
elseif Attributes["Infantry"]or Attributes["EWR"]then ThreatLevel=1
end
ThreatText=ThreatLevels[ThreatLevel+1]
end
if self:IsAir()then
local ThreatLevels={
"Unarmed",
"Tanker",
"AWACS",
"Transport Helicopter",
"UAV",
"Bomber",
"Strategic Bomber",
"Attack Helicopter",
"Battleplane",
"Multirole Fighter",
"Fighter"
}
if Attributes["Fighters"]then ThreatLevel=10
elseif Attributes["Multirole fighters"]then ThreatLevel=9
elseif Attributes["Battleplanes"]then ThreatLevel=8
elseif Attributes["Attack helicopters"]then ThreatLevel=7
elseif Attributes["Strategic bombers"]then ThreatLevel=6
elseif Attributes["Bombers"]then ThreatLevel=5
elseif Attributes["UAVs"]then ThreatLevel=4
elseif Attributes["Transport helicopters"]then ThreatLevel=3
elseif Attributes["AWACS"]then ThreatLevel=2
elseif Attributes["Tankers"]then ThreatLevel=1
end
ThreatText=ThreatLevels[ThreatLevel+1]
end
if self:IsShip()then
local ThreatLevels={
"Unarmed ship",
"Light armed ships",
"Corvettes",
"",
"Frigates",
"",
"Cruiser",
"",
"Destroyer",
"",
"Aircraft Carrier"
}
if Attributes["Aircraft Carriers"]then ThreatLevel=10
elseif Attributes["Destroyers"]then ThreatLevel=8
elseif Attributes["Cruisers"]then ThreatLevel=6
elseif Attributes["Frigates"]then ThreatLevel=4
elseif Attributes["Corvettes"]then ThreatLevel=2
elseif Attributes["Light armed ships"]then ThreatLevel=1
end
ThreatText=ThreatLevels[ThreatLevel+1]
end
end
return ThreatLevel,ThreatText
end
function UNIT:Explode(power,delay)
power=power or 100
local DCSUnit=self:GetDCSObject()
if DCSUnit then
if delay and delay>0 then
SCHEDULER:New(nil,self.Explode,{self,power},delay)
else
self:GetCoordinate():Explosion(power)
end
return self
end
return nil
end
function UNIT:OtherUnitInRadius(AwaitUnit,Radius)
self:F2({self.UnitName,AwaitUnit.UnitName,Radius})
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitVec3=self:GetVec3()
local AwaitUnitVec3=AwaitUnit:GetVec3()
if(((UnitVec3.x-AwaitUnitVec3.x)^2+(UnitVec3.z-AwaitUnitVec3.z)^2)^0.5<=Radius)then
self:T3("true")
return true
else
self:T3("false")
return false
end
end
return nil
end
function UNIT:IsFriendly(FriendlyCoalition)
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitCoalition=DCSUnit:getCoalition()
self:T3({UnitCoalition,FriendlyCoalition})
local IsFriendlyResult=(UnitCoalition==FriendlyCoalition)
self:F(IsFriendlyResult)
return IsFriendlyResult
end
return nil
end
function UNIT:IsShip()
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitDescriptor=DCSUnit:getDesc()
self:T3({UnitDescriptor.category,Unit.Category.SHIP})
local IsShipResult=(UnitDescriptor.category==Unit.Category.SHIP)
self:T3(IsShipResult)
return IsShipResult
end
return nil
end
function UNIT:InAir(NoHeloCheck)
self:F2(self.UnitName)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitInAir=DCSUnit:inAir()
local UnitCategory=DCSUnit:getDesc().category
if UnitInAir==true and UnitCategory==Unit.Category.HELICOPTER and(not NoHeloCheck)then
local VelocityVec3=DCSUnit:getVelocity()
local Velocity=UTILS.VecNorm(VelocityVec3)
local Coordinate=DCSUnit:getPoint()
local LandHeight=land.getHeight({x=Coordinate.x,y=Coordinate.z})
local Height=Coordinate.y-LandHeight
if Velocity<1 and Height<=60 then
UnitInAir=false
end
end
self:T3(UnitInAir)
return UnitInAir
end
return nil
end
do
function UNIT:HandleEvent(EventID,EventFunction)
self:EventDispatcher():OnEventForUnit(self:GetName(),EventFunction,self,EventID)
return self
end
function UNIT:UnHandleEvent(EventID)
self:EventDispatcher():RemoveEvent(self,EventID)
return self
end
function UNIT:ResetEvents()
self:EventDispatcher():Reset(self)
return self
end
end
do
function UNIT:IsDetected(TargetUnit)
local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity=self:IsTargetDetected(TargetUnit:GetDCSObject())
return TargetIsDetected
end
function UNIT:IsLOS(TargetUnit)
local IsLOS=self:GetPointVec3():IsLOS(TargetUnit:GetPointVec3())
return IsLOS
end
function UNIT:KnowUnit(TargetUnit,TypeKnown,DistanceKnown)
if TypeKnown~=false then
TypeKnown=true
end
if DistanceKnown~=false then
DistanceKnown=true
end
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=DCSControllable:getController()
if Controller then
local object=TargetUnit:GetDCSObject()
if object then
self:I(string.format("Unit %s now knows target unit %s. Type known=%s, distance known=%s",self:GetName(),TargetUnit:GetName(),tostring(TypeKnown),tostring(DistanceKnown)))
Controller:knowTarget(object,TypeKnown,DistanceKnown)
end
end
end
end
end
function UNIT:GetTemplate()
local group=self:GetGroup()
local name=self:GetName()
if group then
local template=group:GetTemplate()
if template then
for _,unit in pairs(template.units)do
if unit.name==name then
return UTILS.DeepCopy(unit)
end
end
end
end
return nil
end
function UNIT:GetTemplatePayload()
local unit=self:GetTemplate()
if unit then
return unit.payload
end
return nil
end
function UNIT:GetTemplatePylons()
local payload=self:GetTemplatePayload()
if payload then
return payload.pylons
end
return nil
end
function UNIT:GetTemplateFuel()
local payload=self:GetTemplatePayload()
if payload then
return payload.fuel
end
return nil
end
function UNIT:EnableEmission(switch)
self:F2(self.UnitName)
local switch=switch or false
local DCSUnit=self:GetDCSObject()
if DCSUnit then
DCSUnit:enableEmission(switch)
end
return self
end
function UNIT:GetSkill()
self:F2(self.UnitName)
local name=self.UnitName
local skill=_DATABASE.Templates.Units[name].Template.skill or"Random"
return skill
end
CLIENT={
ClassName="CLIENT",
ClientName=nil,
ClientAlive=false,
ClientTransport=false,
ClientBriefingShown=false,
_Menus={},
_Tasks={},
Messages={},
Players={},
}
function CLIENT:Find(DCSUnit,Error)
local ClientName=DCSUnit:getName()
local ClientFound=_DATABASE:FindClient(ClientName)
if ClientFound then
ClientFound:F(ClientName)
return ClientFound
end
if not Error then
error("CLIENT not found for: "..ClientName)
end
end
function CLIENT:FindByName(ClientName,ClientBriefing,Error)
local ClientFound=_DATABASE:FindClient(ClientName)
if ClientFound then
ClientFound:F({ClientName,ClientBriefing})
ClientFound:AddBriefing(ClientBriefing)
ClientFound.MessageSwitch=true
return ClientFound
end
if not Error then
error("CLIENT not found for: "..ClientName)
end
end
function CLIENT:Register(ClientName)
local self=BASE:Inherit(self,UNIT:Register(ClientName))
self.ClientName=ClientName
self.MessageSwitch=true
self.ClientAlive2=false
return self
end
function CLIENT:Transport()
self.ClientTransport=true
return self
end
function CLIENT:AddBriefing(ClientBriefing)
self.ClientBriefing=ClientBriefing
self.ClientBriefingShown=false
return self
end
function CLIENT:AddPlayer(PlayerName)
table.insert(self.Players,PlayerName)
return self
end
function CLIENT:GetPlayers()
return self.Players
end
function CLIENT:GetPlayer()
if#self.Players>0 then
return self.Players[1]
end
return nil
end
function CLIENT:RemovePlayer(PlayerName)
for i,playername in pairs(self.Players)do
if PlayerName==playername then
table.remove(self.Players,i)
break
end
end
return self
end
function CLIENT:RemovePlayers()
self.Players={}
return self
end
function CLIENT:ShowBriefing()
if not self.ClientBriefingShown then
self.ClientBriefingShown=true
local Briefing=""
if self.ClientBriefing and self.ClientBriefing~=""then
Briefing=Briefing..self.ClientBriefing
self:Message(Briefing,60,"Briefing")
end
end
return self
end
function CLIENT:ShowMissionBriefing(MissionBriefing)
self:F({self.ClientName})
if MissionBriefing then
self:Message(MissionBriefing,60,"Mission Briefing")
end
return self
end
function CLIENT:Reset(ClientName)
self:F()
self._Menus={}
end
function CLIENT:IsMultiSeated()
self:F(self.ClientName)
local ClientMultiSeatedTypes={
["Mi-8MT"]="Mi-8MT",
["UH-1H"]="UH-1H",
["P-51B"]="P-51B"
}
if self:IsAlive()then
local ClientTypeName=self:GetClientGroupUnit():GetTypeName()
if ClientMultiSeatedTypes[ClientTypeName]then
return true
end
end
return false
end
function CLIENT:Alive(CallBackFunction,...)
self:F()
self.ClientCallBack=CallBackFunction
self.ClientParameters=arg
self.AliveCheckScheduler=SCHEDULER:New(self,self._AliveCheckScheduler,{"Client Alive "..self.ClientName},0.1,5,0.5)
self.AliveCheckScheduler:NoTrace()
return self
end
function CLIENT:_AliveCheckScheduler(SchedulerName)
self:F3({SchedulerName,self.ClientName,self.ClientAlive2,self.ClientBriefingShown,self.ClientCallBack})
if self:IsAlive()then
if self.ClientAlive2==false then
self:ShowBriefing()
if self.ClientCallBack then
self:T("Calling Callback function")
self.ClientCallBack(self,unpack(self.ClientParameters))
end
self.ClientAlive2=true
end
else
if self.ClientAlive2==true then
self.ClientAlive2=false
end
end
return true
end
function CLIENT:GetDCSGroup()
self:F3()
local ClientUnit=Unit.getByName(self.ClientName)
local CoalitionsData={AlivePlayersRed=coalition.getPlayers(coalition.side.RED),AlivePlayersBlue=coalition.getPlayers(coalition.side.BLUE)}
for CoalitionId,CoalitionData in pairs(CoalitionsData)do
self:T3({"CoalitionData:",CoalitionData})
for UnitId,UnitData in pairs(CoalitionData)do
self:T3({"UnitData:",UnitData})
if UnitData and UnitData:isExist()then
if ClientUnit then
local ClientGroup=ClientUnit:getGroup()
if ClientGroup then
self:T3("ClientGroup = "..self.ClientName)
if ClientGroup:isExist()and UnitData:getGroup():isExist()then
if ClientGroup:getID()==UnitData:getGroup():getID()then
self:T3("Normal logic")
self:T3(self.ClientName.." : group found!")
self.ClientGroupID=ClientGroup:getID()
self.ClientGroupName=ClientGroup:getName()
return ClientGroup
end
else
self:T3("Bug 1.5 logic")
local ClientGroupTemplate=_DATABASE.Templates.Units[self.ClientName].GroupTemplate
self.ClientGroupID=ClientGroupTemplate.groupId
self.ClientGroupName=_DATABASE.Templates.Units[self.ClientName].GroupName
self:T3(self.ClientName.." : group found in bug 1.5 resolvement logic!")
return ClientGroup
end
end
else
end
end
end
end
if ClientUnit then
local ClientGroup=ClientUnit:getGroup()
if ClientGroup then
self:T3("ClientGroup = "..self.ClientName)
if ClientGroup:isExist()then
self:T3("Normal logic")
self:T3(self.ClientName.." : group found!")
return ClientGroup
end
end
end
self.ClientGroupID=nil
self.ClientGroupName=nil
return nil
end
function CLIENT:GetClientGroupID()
self:GetDCSGroup()
return self.ClientGroupID
end
function CLIENT:GetClientGroupName()
self:GetDCSGroup()
return self.ClientGroupName
end
function CLIENT:GetClientGroupUnit()
self:F2()
local ClientDCSUnit=Unit.getByName(self.ClientName)
self:T(self.ClientDCSUnit)
if ClientDCSUnit and ClientDCSUnit:isExist()then
local ClientUnit=_DATABASE:FindUnit(self.ClientName)
return ClientUnit
end
return nil
end
function CLIENT:GetClientGroupDCSUnit()
self:F2()
local ClientDCSUnit=Unit.getByName(self.ClientName)
if ClientDCSUnit and ClientDCSUnit:isExist()then
self:T2(ClientDCSUnit)
return ClientDCSUnit
end
end
function CLIENT:IsTransport()
self:F()
return self.ClientTransport
end
function CLIENT:ShowCargo()
self:F()
local CargoMsg=""
for CargoName,Cargo in pairs(CARGOS)do
if self==Cargo:IsLoadedInClient()then
CargoMsg=CargoMsg..Cargo.CargoName.." Type:"..Cargo.CargoType.." Weight: "..Cargo.CargoWeight.."\n"
end
end
if CargoMsg==""then
CargoMsg="empty"
end
self:Message(CargoMsg,15,"Co-Pilot: Cargo Status",30)
end
function CLIENT:Message(Message,MessageDuration,MessageCategory,MessageInterval,MessageID)
self:F({Message,MessageDuration,MessageCategory,MessageInterval})
if self.MessageSwitch==true then
if MessageCategory==nil then
MessageCategory="Messages"
end
if MessageID~=nil then
if self.Messages[MessageID]==nil then
self.Messages[MessageID]={}
self.Messages[MessageID].MessageId=MessageID
self.Messages[MessageID].MessageTime=timer.getTime()
self.Messages[MessageID].MessageDuration=MessageDuration
if MessageInterval==nil then
self.Messages[MessageID].MessageInterval=600
else
self.Messages[MessageID].MessageInterval=MessageInterval
end
MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self)
else
if self:GetClientGroupDCSUnit()and not self:GetClientGroupDCSUnit():inAir()then
if timer.getTime()-self.Messages[MessageID].MessageTime>=self.Messages[MessageID].MessageDuration+10 then
MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self)
self.Messages[MessageID].MessageTime=timer.getTime()
end
else
if timer.getTime()-self.Messages[MessageID].MessageTime>=self.Messages[MessageID].MessageDuration+self.Messages[MessageID].MessageInterval then
MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self)
self.Messages[MessageID].MessageTime=timer.getTime()
end
end
end
else
MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self)
end
end
end
STATIC={
ClassName="STATIC",
}
function STATIC:Register(StaticName)
local self=BASE:Inherit(self,POSITIONABLE:New(StaticName))
self.StaticName=StaticName
local DCSStatic=StaticObject.getByName(self.StaticName)
if DCSStatic then
local Life0=DCSStatic:getLife()or 1
self.Life0=Life0
end
return self
end
function STATIC:GetLife0()
return self.Life0 or 1
end
function STATIC:GetLife()
local DCSStatic=StaticObject.getByName(self.StaticName)
if DCSStatic then
return DCSStatic:getLife()or 1
end
return nil
end
function STATIC:Find(DCSStatic)
local StaticName=DCSStatic:getName()
local StaticFound=_DATABASE:FindStatic(StaticName)
return StaticFound
end
function STATIC:FindByName(StaticName,RaiseError)
local StaticFound=_DATABASE:FindStatic(StaticName)
self.StaticName=StaticName
if StaticFound then
return StaticFound
end
if RaiseError==nil or RaiseError==true then
error("STATIC not found for: "..StaticName)
end
return nil
end
function STATIC:Destroy(GenerateEvent)
self:F2(self.ObjectName)
local DCSObject=self:GetDCSObject()
if DCSObject then
local StaticName=DCSObject:getName()
self:F({StaticName=StaticName})
if GenerateEvent and GenerateEvent==true then
if self:IsAir()then
self:CreateEventCrash(timer.getTime(),DCSObject)
else
self:CreateEventDead(timer.getTime(),DCSObject)
end
elseif GenerateEvent==false then
else
self:CreateEventRemoveUnit(timer.getTime(),DCSObject)
end
DCSObject:destroy()
return true
end
return nil
end
function STATIC:GetDCSObject()
local DCSStatic=StaticObject.getByName(self.StaticName)
if DCSStatic then
return DCSStatic
end
return nil
end
function STATIC:GetUnits()
self:F2({self.StaticName})
local DCSStatic=self:GetDCSObject()
local Statics={}
if DCSStatic then
Statics[1]=STATIC:Find(DCSStatic)
self:T3(Statics)
return Statics
end
return nil
end
function STATIC:GetThreatLevel()
return 1,"Static"
end
function STATIC:SpawnAt(Coordinate,Heading,Delay)
Heading=Heading or 0
if Delay and Delay>0 then
SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay)
else
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName)
SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName)
end
return self
end
function STATIC:ReSpawn(CountryID,Delay)
if Delay and Delay>0 then
SCHEDULER:New(nil,self.ReSpawn,{self,CountryID},Delay)
else
CountryID=CountryID or self:GetCountry()
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID)
SpawnStatic:Spawn(nil,self.StaticName)
end
return self
end
function STATIC:ReSpawnAt(Coordinate,Heading,Delay)
if Delay and Delay>0 then
SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay)
else
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry())
SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName)
end
return self
end
AIRBASE={
ClassName="AIRBASE",
CategoryName={
[Airbase.Category.AIRDROME]="Airdrome",
[Airbase.Category.HELIPAD]="Helipad",
[Airbase.Category.SHIP]="Ship",
},
activerwyno=nil,
}
AIRBASE.Caucasus={
["Gelendzhik"]="Gelendzhik",
["Krasnodar_Pashkovsky"]="Krasnodar-Pashkovsky",
["Sukhumi_Babushara"]="Sukhumi-Babushara",
["Gudauta"]="Gudauta",
["Batumi"]="Batumi",
["Senaki_Kolkhi"]="Senaki-Kolkhi",
["Kobuleti"]="Kobuleti",
["Kutaisi"]="Kutaisi",
["Tbilisi_Lochini"]="Tbilisi-Lochini",
["Soganlug"]="Soganlug",
["Vaziani"]="Vaziani",
["Anapa_Vityazevo"]="Anapa-Vityazevo",
["Krasnodar_Center"]="Krasnodar-Center",
["Novorossiysk"]="Novorossiysk",
["Krymsk"]="Krymsk",
["Maykop_Khanskaya"]="Maykop-Khanskaya",
["Sochi_Adler"]="Sochi-Adler",
["Mineralnye_Vody"]="Mineralnye Vody",
["Nalchik"]="Nalchik",
["Mozdok"]="Mozdok",
["Beslan"]="Beslan",
}
AIRBASE.Nevada={
["Creech_AFB"]="Creech",
["Groom_Lake_AFB"]="Groom Lake",
["McCarran_International_Airport"]="McCarran International",
["Nellis_AFB"]="Nellis",
["Beatty_Airport"]="Beatty",
["Boulder_City_Airport"]="Boulder City",
["Echo_Bay"]="Echo Bay",
["Henderson_Executive_Airport"]="Henderson Executive",
["Jean_Airport"]="Jean",
["Laughlin_Airport"]="Laughlin",
["Lincoln_County"]="Lincoln County",
["Mesquite"]="Mesquite",
["Mina_Airport"]="Mina",
["North_Las_Vegas"]="North Las Vegas",
["Pahute_Mesa_Airstrip"]="Pahute Mesa",
["Tonopah_Airport"]="Tonopah",
["Tonopah_Test_Range_Airfield"]="Tonopah Test Range",
}
AIRBASE.Normandy={
["Saint_Pierre_du_Mont"]="Saint Pierre du Mont",
["Lignerolles"]="Lignerolles",
["Cretteville"]="Cretteville",
["Maupertus"]="Maupertus",
["Brucheville"]="Brucheville",
["Meautis"]="Meautis",
["Cricqueville_en_Bessin"]="Cricqueville-en-Bessin",
["Lessay"]="Lessay",
["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer",
["Biniville"]="Biniville",
["Cardonville"]="Cardonville",
["Deux_Jumeaux"]="Deux Jumeaux",
["Chippelle"]="Chippelle",
["Beuzeville"]="Beuzeville",
["Azeville"]="Azeville",
["Picauville"]="Picauville",
["Le_Molay"]="Le Molay",
["Longues_sur_Mer"]="Longues-sur-Mer",
["Carpiquet"]="Carpiquet",
["Bazenville"]="Bazenville",
["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer",
["Beny_sur_Mer"]="Beny-sur-Mer",
["Rucqueville"]="Rucqueville",
["Sommervieu"]="Sommervieu",
["Lantheuil"]="Lantheuil",
["Evreux"]="Evreux",
["Chailey"]="Chailey",
["Needs_Oar_Point"]="Needs Oar Point",
["Funtington"]="Funtington",
["Tangmere"]="Tangmere",
["Ford_AF"]="Ford_AF",
["Goulet"]="Goulet",
["Argentan"]="Argentan",
["Vrigny"]="Vrigny",
["Essay"]="Essay",
["Hauterive"]="Hauterive",
["Barville"]="Barville",
["Conches"]="Conches",
}
AIRBASE.PersianGulf={
["Abu_Dhabi_International_Airport"]="Abu Dhabi Intl",
["Abu_Musa_Island_Airport"]="Abu Musa Island",
["Al_Ain_International_Airport"]="Al Ain Intl",
["Al_Bateen_Airport"]="Al-Bateen",
["Al_Dhafra_AB"]="Al Dhafra AFB",
["Al_Maktoum_Intl"]="Al Maktoum Intl",
["Al_Minhad_AB"]="Al Minhad AFB",
["Bandar_Abbas_Intl"]="Bandar Abbas Intl",
["Bandar_Lengeh"]="Bandar Lengeh",
["Bandar_e_Jask_airfield"]="Bandar-e-Jask",
["Dubai_Intl"]="Dubai Intl",
["Fujairah_Intl"]="Fujairah Intl",
["Havadarya"]="Havadarya",
["Jiroft_Airport"]="Jiroft",
["Kerman_Airport"]="Kerman",
["Khasab"]="Khasab",
["Kish_International_Airport"]="Kish Intl",
["Lar_Airbase"]="Lar",
["Lavan_Island_Airport"]="Lavan Island",
["Liwa_Airbase"]="Liwa AFB",
["Qeshm_Island"]="Qeshm Island",
["Ras_Al_Khaimah"]="Ras Al Khaimah Intl",
["Sas_Al_Nakheel_Airport"]="Sas Al Nakheel",
["Sharjah_Intl"]="Sharjah Intl",
["Shiraz_International_Airport"]="Shiraz Intl",
["Sir_Abu_Nuayr"]="Sir Abu Nuayr",
["Sirri_Island"]="Sirri Island",
["Tunb_Island_AFB"]="Tunb Island AFB",
["Tunb_Kochak"]="Tunb Kochak",
}
AIRBASE.TheChannel={
["Abbeville_Drucat"]="Abbeville Drucat",
["Merville_Calonne"]="Merville Calonne",
["Saint_Omer_Longuenesse"]="Saint Omer Longuenesse",
["Dunkirk_Mardyck"]="Dunkirk Mardyck",
["Manston"]="Manston",
["Hawkinge"]="Hawkinge",
["Lympne"]="Lympne",
["Detling"]="Detling",
["High_Halden"]="High Halden",
["Biggin_Hill"]="Biggin Hill",
["Eastchurch"]="Eastchurch",
["Headcorn"]="Headcorn",
}
AIRBASE.Syria={
["Kuweires"]="Kuweires",
["Marj_Ruhayyil"]="Marj Ruhayyil",
["Kiryat_Shmona"]="Kiryat Shmona",
["Marj_as_Sultan_North"]="Marj as Sultan North",
["Eyn_Shemer"]="Eyn Shemer",
["Incirlik"]="Incirlik",
["Damascus"]="Damascus",
["Bassel_Al_Assad"]="Bassel Al-Assad",
["Rosh_Pina"]="Rosh Pina",
["Aleppo"]="Aleppo",
["Al_Qusayr"]="Al Qusayr",
["Wujah_Al_Hajar"]="Wujah Al Hajar",
["Al_Dumayr"]="Al-Dumayr",
["Gazipasa"]="Gazipasa",
["Hatay"]="Hatay",
["Nicosia"]="Nicosia",
["Pinarbashi"]="Pinarbashi",
["Paphos"]="Paphos",
["Kingsfield"]="Kingsfield",
["Thalah"]="Tha'lah",
["Haifa"]="Haifa",
["Khalkhalah"]="Khalkhalah",
["Megiddo"]="Megiddo",
["Lakatamia"]="Lakatamia",
["Rayak"]="Rayak",
["Larnaca"]="Larnaca",
["Mezzeh"]="Mezzeh",
["Gecitkale"]="Gecitkale",
["Akrotiri"]="Akrotiri",
["Naqoura"]="Naqoura",
["Gaziantep"]="Gaziantep",
["Sayqal"]="Sayqal",
["Tiyas"]="Tiyas",
["Shayrat"]="Shayrat",
["Taftanaz"]="Taftanaz",
["H4"]="H4",
["King_Hussein_Air_College"]="King Hussein Air College",
["Rene_Mouawad"]="Rene Mouawad",
["Jirah"]="Jirah",
["Ramat_David"]="Ramat David",
["Qabr_as_Sitt"]="Qabr as Sitt",
["Minakh"]="Minakh",
["Adana_Sakirpasa"]="Adana Sakirpasa",
["Palmyra"]="Palmyra",
["Hama"]="Hama",
["Ercan"]="Ercan",
["Marj_as_Sultan_South"]="Marj as Sultan South",
["Tabqa"]="Tabqa",
["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri",
["An_Nasiriyah"]="An Nasiriyah",
["Abu_al_Duhur"]="Abu al-Duhur",
["At_Tanf"]="At Tanf",
["H3"]="H3",
["H3_Northwest"]="H3 Northwest",
["H3_Southwest"]="H3 Southwest",
["Kharab_Ishk"]="Kharab Ishk",
["Raj_al_Issa_East"]="Raj al Issa East",
["Raj_al_Issa_West"]="Raj al Issa West",
["Ruwayshid"]="Ruwayshid",
["Sanliurfa"]="Sanliurfa",
["Tal_Siman"]="Tal Siman",
["Deir_ez_Zor"]="Deir ez-Zor",
}
AIRBASE.MarianaIslands={
["Rota_Intl"]="Rota Intl",
["Andersen_AFB"]="Andersen AFB",
["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl",
["Saipan_Intl"]="Saipan Intl",
["Tinian_Intl"]="Tinian Intl",
["Olf_Orote"]="Olf Orote",
}
AIRBASE.SouthAtlantic={
["Port_Stanley"]="Port Stanley",
["Mount_Pleasant"]="Mount Pleasant",
["San_Carlos_FOB"]="San Carlos FOB",
["Rio_Grande"]="Rio Grande",
["Rio_Gallegos"]="Rio Gallegos",
["Ushuaia"]="Ushuaia",
["Ushuaia_Helo_Port"]="Ushuaia Helo Port",
["Punta_Arenas"]="Punta Arenas",
["Pampa_Guanaco"]="Pampa Guanaco",
["San_Julian"]="San Julian",
["Puerto_Williams"]="Puerto Williams",
["Puerto_Natales"]="Puerto Natales",
["El_Calafate"]="El Calafate",
["Puerto_Santa_Cruz"]="Puerto Santa Cruz",
["Comandante_Luis_Piedrabuena"]="Comandante Luis Piedrabuena",
["Aerodromo_De_Tolhuin"]="Aerodromo De Tolhuin",
["Porvenir_Airfield"]="Porvenir Airfield",
["Almirante_Schroeders"]="Almirante Schroeders",
["Rio_Turbio"]="Rio Turbio",
["Rio_Chico"]="Rio Chico",
["Franco_Bianco"]="Franco Bianco",
["Goose_Green"]="Goose Green",
["Hipico"]="Hipico",
["CaletaTortel"]="CaletaTortel",
}
AIRBASE.TerminalType={
Runway=16,
HelicopterOnly=40,
Shelter=68,
OpenMed=72,
OpenBig=104,
OpenMedOrBig=176,
HelicopterUsable=216,
FighterAircraft=244,
}
AIRBASE.SpotStatus={
FREE="Free",
OCCUPIED="Occupied",
RESERVED="Reserved",
}
function AIRBASE:Register(AirbaseName)
local self=BASE:Inherit(self,POSITIONABLE:New(AirbaseName))
self.AirbaseName=AirbaseName
self.AirbaseID=self:GetID(true)
self.descriptors=self:GetDesc()
self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME
if self.category==Airbase.Category.AIRDROME then
self.isAirdrome=true
elseif self.category==Airbase.Category.HELIPAD then
self.isHelipad=true
elseif self.category==Airbase.Category.SHIP then
self.isShip=true
if self.descriptors.typeName=="Oil rig"or self.descriptors.typeName=="Ga"then
self.isHelipad=true
self.isShip=false
self.category=Airbase.Category.HELIPAD
_DATABASE:AddStatic(AirbaseName)
end
else
self:E("ERROR: Unknown airbase category!")
end
self:_InitRunways()
if self.isAirdrome then
self:SetActiveRunway()
end
self:_InitParkingSpots()
local vec2=self:GetVec2()
self:GetCoordinate()
if vec2 then
if self.isShip then
local unit=UNIT:FindByName(AirbaseName)
if unit then
self.AirbaseZone=ZONE_UNIT:New(AirbaseName,unit,2500)
end
else
self.AirbaseZone=ZONE_RADIUS:New(AirbaseName,vec2,2500)
end
else
self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s",AirbaseName))
end
self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName)))
return self
end
function AIRBASE:Find(DCSAirbase)
local AirbaseName=DCSAirbase:getName()
local AirbaseFound=_DATABASE:FindAirbase(AirbaseName)
return AirbaseFound
end
function AIRBASE:FindByName(AirbaseName)
local AirbaseFound=_DATABASE:FindAirbase(AirbaseName)
return AirbaseFound
end
function AIRBASE:FindByID(id)
for name,_airbase in pairs(_DATABASE.AIRBASES)do
local airbase=_airbase
local aid=tonumber(airbase:GetID(true))
if aid==id then
return airbase
end
end
return nil
end
function AIRBASE:GetDCSObject()
local DCSAirbase=Airbase.getByName(self.AirbaseName)
if DCSAirbase then
return DCSAirbase
end
return nil
end
function AIRBASE:GetZone()
return self.AirbaseZone
end
function AIRBASE.GetAllAirbases(coalition,category)
local airbases={}
for _,_airbase in pairs(_DATABASE.AIRBASES)do
local airbase=_airbase
if coalition==nil or airbase:GetCoalition()==coalition then
if category==nil or category==airbase:GetAirbaseCategory()then
table.insert(airbases,airbase)
end
end
end
return airbases
end
function AIRBASE.GetAllAirbaseNames(coalition,category)
local airbases={}
for airbasename,_airbase in pairs(_DATABASE.AIRBASES)do
local airbase=_airbase
if coalition==nil or airbase:GetCoalition()==coalition then
if category==nil or category==airbase:GetAirbaseCategory()then
table.insert(airbases,airbasename)
end
end
end
return airbases
end
function AIRBASE:GetID(unique)
if self.AirbaseID then
return unique and self.AirbaseID or math.abs(self.AirbaseID)
else
for DCSAirbaseId,DCSAirbase in ipairs(world.getAirbases())do
local AirbaseName=DCSAirbase:getName()
local airbaseID=tonumber(DCSAirbase:getID())
local airbaseCategory=self:GetAirbaseCategory()
if AirbaseName==self.AirbaseName then
if airbaseCategory==Airbase.Category.SHIP or airbaseCategory==Airbase.Category.HELIPAD then
return unique and-airbaseID or airbaseID
else
return airbaseID
end
end
end
end
return nil
end
function AIRBASE:SetParkingSpotWhitelist(TerminalIdWhitelist)
if TerminalIdWhitelist==nil then
self.parkingWhitelist={}
return self
end
if type(TerminalIdWhitelist)~="table"then
TerminalIdWhitelist={TerminalIdWhitelist}
end
self.parkingWhitelist=TerminalIdWhitelist
return self
end
function AIRBASE:SetParkingSpotBlacklist(TerminalIdBlacklist)
if TerminalIdBlacklist==nil then
self.parkingBlacklist={}
return self
end
if type(TerminalIdBlacklist)~="table"then
TerminalIdBlacklist={TerminalIdBlacklist}
end
self.parkingBlacklist=TerminalIdBlacklist
return self
end
function AIRBASE:SetRadioSilentMode(Silent)
local airbase=self:GetDCSObject()
if airbase then
airbase:setRadioSilentMode(Silent)
end
return self
end
function AIRBASE:GetRadioSilentMode()
local silent=nil
local airbase=self:GetDCSObject()
if airbase then
silent=airbase:getRadioSilentMode()
end
return silent
end
function AIRBASE:GetAirbaseCategory()
return self.category
end
function AIRBASE:IsAirdrome()
return self.isAirdrome
end
function AIRBASE:IsHelipad()
return self.isHelipad
end
function AIRBASE:IsShip()
return self.isShip
end
function AIRBASE:GetParkingData(available)
self:F2(available)
local DCSAirbase=self:GetDCSObject()
local parkingdata=nil
if DCSAirbase then
parkingdata=DCSAirbase:getParking(available)
end
self:T2({parkingdata=parkingdata})
return parkingdata
end
function AIRBASE:GetParkingSpotsNumber(termtype)
local parkingdata=self:GetParkingData(false)
local nspots=0
for _,parkingspot in pairs(parkingdata)do
if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then
nspots=nspots+1
end
end
return nspots
end
function AIRBASE:GetFreeParkingSpotsNumber(termtype,allowTOAC)
local parkingdata=self:GetParkingData(true)
local nfree=0
for _,parkingspot in pairs(parkingdata)do
if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then
if(allowTOAC and allowTOAC==true)or parkingspot.TO_AC==false then
nfree=nfree+1
end
end
end
return nfree
end
function AIRBASE:GetFreeParkingSpotsCoordinates(termtype,allowTOAC)
local parkingdata=self:GetParkingData(true)
local spots={}
for _,parkingspot in pairs(parkingdata)do
if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then
if(allowTOAC and allowTOAC==true)or parkingspot.TO_AC==false then
table.insert(spots,COORDINATE:NewFromVec3(parkingspot.vTerminalPos))
end
end
end
return spots
end
function AIRBASE:GetParkingSpotsCoordinates(termtype)
local parkingdata=self:GetParkingData(false)
local spots={}
for _,parkingspot in ipairs(parkingdata)do
if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then
local _coord=COORDINATE:NewFromVec3(parkingspot.vTerminalPos)
table.insert(spots,_coord)
end
end
return spots
end
function AIRBASE:_InitParkingSpots()
local parkingdata=self:GetParkingData(false)
self.parking={}
self.parkingByID={}
self.NparkingTotal=0
self.NparkingTerminal={}
for _,terminalType in pairs(AIRBASE.TerminalType)do
self.NparkingTerminal[terminalType]=0
end
local function isClient(coord)
local clients=_DATABASE.CLIENTS
for clientname,_client in pairs(clients)do
local client=_client
if client and client.SpawnCoord then
local dist=client.SpawnCoord:Get2DDistance(coord)
if dist<2 then
return true,clientname
end
end
end
return false,nil
end
for _,spot in pairs(parkingdata)do
local park={}
park.Vec3=spot.vTerminalPos
park.Coordinate=COORDINATE:NewFromVec3(spot.vTerminalPos)
park.DistToRwy=spot.fDistToRW
park.Free=nil
park.TerminalID=spot.Term_Index
park.TerminalID0=spot.Term_Index_0
park.TerminalType=spot.Term_Type
park.TOAC=spot.TO_AC
park.ClientSpot,park.ClientName=isClient(park.Coordinate)
park.AirbaseName=self.AirbaseName
self.NparkingTotal=self.NparkingTotal+1
for _,terminalType in pairs(AIRBASE.TerminalType)do
if self._CheckTerminalType(terminalType,park.TerminalType)then
self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1
end
end
self.parkingByID[park.TerminalID]=park
table.insert(self.parking,park)
end
return self
end
function AIRBASE:_GetParkingSpotByID(TerminalID)
return self.parkingByID[TerminalID]
end
function AIRBASE:GetParkingSpotsTable(termtype)
local parkingdata=self:GetParkingData(false)
local parkingfree=self:GetParkingData(true)
local function _isfree(_tocheck)
for _,_spot in pairs(parkingfree)do
if _spot.Term_Index==_tocheck.Term_Index then
return true
end
end
return false
end
local spots={}
for _,_spot in pairs(parkingdata)do
if AIRBASE._CheckTerminalType(_spot.Term_Type,termtype)then
local spot=self:_GetParkingSpotByID(_spot.Term_Index)
if spot then
spot.Free=_isfree(_spot)
spot.TOAC=_spot.TO_AC
spot.AirbaseName=self.AirbaseName
table.insert(spots,spot)
else
self:E(string.format("ERROR: Parking spot %s is nil!",tostring(_spot.Term_Index)))
end
end
end
return spots
end
function AIRBASE:GetFreeParkingSpotsTable(termtype,allowTOAC)
local parkingfree=self:GetParkingData(true)
local freespots={}
for _,_spot in pairs(parkingfree)do
if AIRBASE._CheckTerminalType(_spot.Term_Type,termtype)and _spot.Term_Index>0 then
if(allowTOAC and allowTOAC==true)or _spot.TO_AC==false then
local spot=self:_GetParkingSpotByID(_spot.Term_Index)
spot.Free=true
spot.TOAC=_spot.TO_AC
spot.AirbaseName=self.AirbaseName
table.insert(freespots,spot)
end
end
end
return freespots
end
function AIRBASE:GetParkingSpotData(TerminalID)
local parkingdata=self:GetParkingSpotsTable()
for _,_spot in pairs(parkingdata)do
local spot=_spot
self:T({TerminalID=spot.TerminalID,TerminalType=spot.TerminalType})
if TerminalID==spot.TerminalID then
return spot
end
end
self:E("ERROR: Could not find spot with Terminal ID="..tostring(TerminalID))
return nil
end
function AIRBASE:MarkParkingSpots(termtype,mark)
if mark==nil then
mark=true
end
local parkingdata=self:GetParkingSpotsTable(termtype)
local airbasename=self:GetName()
self:E(string.format("Parking spots at %s for terminal type %s:",airbasename,tostring(termtype)))
for _,_spot in pairs(parkingdata)do
local _text=string.format("Term Index=%d, Term Type=%d, Free=%s, TOAC=%s, Term ID0=%d, Dist2Rwy=%.1f m",
_spot.TerminalID,_spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)
if mark then
_spot.Coordinate:MarkToAll(_text)
end
local _text=string.format("%s, Term Index=%3d, Term Type=%03d, Free=%5s, TOAC=%5s, Term ID0=%3d, Dist2Rwy=%.1f m",
airbasename,_spot.TerminalID,_spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)
self:E(_text)
end
end
function AIRBASE:FindFreeParkingSpotForAircraft(group,terminaltype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nspots,parkingdata)
scanradius=scanradius or 50
if scanunits==nil then
scanunits=true
end
if scanstatics==nil then
scanstatics=true
end
if scanscenery==nil then
scanscenery=false
end
if verysafe==nil then
verysafe=false
end
local function _overlap(object1,object2,dist)
local pos1=object1
local pos2=object2
local r1=pos1:GetBoundingRadius()
local r2=pos2:GetBoundingRadius()
if r1 and r2 then
local safedist=(r1+r2)*1.1
local safe=(dist>safedist)
self:T2(string.format("r1=%.1f r2=%.1f s=%.1f d=%.1f ==> safe=%s",r1,r2,safedist,dist,tostring(safe)))
return safe
else
return true
end
end
local airport=self:GetName()
parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype)
local aircraft=nil
local _aircraftsize,ax,ay,az
if group and group.ClassName=="GROUP"then
aircraft=group:GetUnit(1)
_aircraftsize,ax,ay,az=aircraft:GetObjectSize()
else
_aircraftsize=23
ax=23
ay=7
az=17
end
local _nspots=nspots or group:GetSize()
self:T(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.",airport,_nspots,_aircraftsize,ax,ay,az,tostring(terminaltype)))
local validspots={}
local nvalid=0
local _test=false
if _test then
return validspots
end
local markobstacles=false
for _,parkingspot in pairs(parkingdata)do
local _spot=parkingspot.Coordinate
local _termid=parkingspot.TerminalID
if AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype)and self:_CheckParkingLists(_termid)then
if verysafe and(parkingspot.Free==false or parkingspot.TOAC==true)then
self:T(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.",airport,parkingspot.TerminalID,tostring(parkingspot.Free),tostring(parkingspot.TOAC)))
else
local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius,scanunits,scanstatics,scanscenery)
local occupied=false
for _,unit in pairs(_units)do
local _coord=unit:GetCoordinate()
local _dist=_coord:Get2DDistance(_spot)
local _safe=_overlap(aircraft,unit,_dist)
if markobstacles then
local l,x,y,z=unit:GetObjectSize()
_coord:MarkToAll(string.format("Unit %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s",unit:GetName(),x,y,z,l,_dist,_termid,tostring(_safe)))
end
if scanunits and not _safe then
occupied=true
end
end
for _,static in pairs(_statics)do
local _static=STATIC:Find(static)
local _vec3=static:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _dist=_coord:Get2DDistance(_spot)
local _safe=_overlap(aircraft,_static,_dist)
if markobstacles then
local l,x,y,z=_static:GetObjectSize()
_coord:MarkToAll(string.format("Static %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s",static:getName(),x,y,z,l,_dist,_termid,tostring(_safe)))
end
if scanstatics and not _safe then
occupied=true
end
end
for _,scenery in pairs(_sceneries)do
local _scenery=SCENERY:Register(scenery:getTypeName(),scenery)
local _vec3=scenery:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _dist=_coord:Get2DDistance(_spot)
local _safe=_overlap(aircraft,_scenery,_dist)
if markobstacles then
local l,x,y,z=scenery:GetObjectSize(scenery)
_coord:MarkToAll(string.format("Scenery %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s",scenery:getTypeName(),x,y,z,l,_dist,_termid,tostring(_safe)))
end
if scanscenery and not _safe then
occupied=true
end
end
for _,_takenspot in pairs(validspots)do
local _dist=_takenspot.Coordinate:Get2DDistance(_spot)
local _safe=_overlap(aircraft,aircraft,_dist)
if not _safe then
occupied=true
end
end
if occupied then
self:T(string.format("%s: Parking spot id %d occupied.",airport,_termid))
else
self:T(string.format("%s: Parking spot id %d free.",airport,_termid))
if nvalid<_nspots then
table.insert(validspots,{Coordinate=_spot,TerminalID=_termid})
end
nvalid=nvalid+1
self:T(string.format("%s: Parking spot id %d free. Nfree=%d/%d.",airport,_termid,nvalid,_nspots))
end
end
if nvalid>=_nspots then
return validspots
end
end
end
return validspots
end
function AIRBASE:_CheckParkingLists(TerminalID)
if self.parkingBlacklist and#self.parkingBlacklist>0 then
for _,terminalID in pairs(self.parkingBlacklist or{})do
if terminalID==TerminalID then
return false
end
end
end
if self.parkingWhitelist and#self.parkingWhitelist>0 then
for _,terminalID in pairs(self.parkingWhitelist or{})do
if terminalID==TerminalID then
return true
end
end
return false
end
return true
end
function AIRBASE._CheckTerminalType(Term_Type,termtype)
if Term_Type==nil then
return false
end
if termtype==nil then
if Term_Type==AIRBASE.TerminalType.Runway then
return false
else
return true
end
end
local match=false
if Term_Type==termtype then
match=true
end
if termtype==AIRBASE.TerminalType.OpenMedOrBig then
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig then
match=true
end
elseif termtype==AIRBASE.TerminalType.HelicopterUsable then
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.HelicopterOnly then
match=true
end
elseif termtype==AIRBASE.TerminalType.FighterAircraft then
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter then
match=true
end
end
return match
end
function AIRBASE:GetRunways()
return self.runways or{}
end
function AIRBASE:GetRunwayByName(Name)
if Name==nil then
return
end
if Name then
for _,_runway in pairs(self.runways)do
local runway=_runway
local name=self:GetRunwayName(runway)
if name==Name:upper()then
return runway
end
end
end
self:E("ERROR: Could not find runway with name "..tostring(Name))
return nil
end
function AIRBASE:_InitRunways(IncludeInverse)
if IncludeInverse==nil then
IncludeInverse=true
end
local Runways={}
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
self.runways={}
return{}
end
local function _createRunway(name,course,width,length,center)
local bearing=-1*course
local heading=math.deg(bearing)
local runway={}
runway.name=string.format("%02d",tonumber(name))
runway.magheading=tonumber(runway.name)*10
runway.heading=heading
runway.width=width or 0
runway.length=length or 0
runway.center=COORDINATE:NewFromVec3(center)
if runway.heading>360 then
runway.heading=runway.heading-360
elseif runway.heading<0 then
runway.heading=runway.heading+360
end
if math.abs(runway.heading-runway.magheading)>60 then
self:T(string.format("WARNING: Runway %s: heading=%.1f magheading=%.1f",runway.name,runway.heading,runway.magheading))
runway.heading=runway.heading-180
end
if runway.heading>360 then
runway.heading=runway.heading-360
elseif runway.heading<0 then
runway.heading=runway.heading+360
end
runway.position=runway.center:Translate(-runway.length/2,runway.heading)
runway.endpoint=runway.center:Translate(runway.length/2,runway.heading)
local init=runway.center:GetVec3()
local width=runway.width/2
local L2=runway.length/2
local offset1={x=init.x+(math.cos(bearing+math.pi)*L2),y=init.z+(math.sin(bearing+math.pi)*L2)}
local offset2={x=init.x-(math.cos(bearing+math.pi)*L2),y=init.z-(math.sin(bearing+math.pi)*L2)}
local points={}
points[1]={x=offset1.x+(math.cos(bearing+(math.pi/2))*width),y=offset1.y+(math.sin(bearing+(math.pi/2))*width)}
points[2]={x=offset1.x+(math.cos(bearing-(math.pi/2))*width),y=offset1.y+(math.sin(bearing-(math.pi/2))*width)}
points[3]={x=offset2.x+(math.cos(bearing-(math.pi/2))*width),y=offset2.y+(math.sin(bearing-(math.pi/2))*width)}
points[4]={x=offset2.x+(math.cos(bearing+(math.pi/2))*width),y=offset2.y+(math.sin(bearing+(math.pi/2))*width)}
runway.zone=ZONE_POLYGON_BASE:New(string.format("%s Runway %s",self.AirbaseName,runway.name),points)
return runway
end
local airbase=self:GetDCSObject()
if airbase then
local runways=airbase:getRunways()
self:T2(runways)
if runways then
for _,rwy in pairs(runways)do
self:T(rwy)
local runway=_createRunway(rwy.Name,rwy.course,rwy.width,rwy.length,rwy.position)
table.insert(Runways,runway)
if IncludeInverse then
local idx=tonumber(runway.name)
local name2=tostring(idx-18)
if idx<18 then
name2=tostring(idx+18)
end
local runway=_createRunway(name2,rwy.course-math.pi,rwy.width,rwy.length,rwy.position)
table.insert(Runways,runway)
end
end
end
end
local rpairs={}
for i,_ri in pairs(Runways)do
local ri=_ri
for j,_rj in pairs(Runways)do
local rj=_rj
if i<j then
if ri.name==rj.name then
rpairs[i]=j
end
end
end
end
local function isLeft(a,b,c)
return((b.z-a.z)*(c.x-a.x)-(b.x-a.x)*(c.z-a.z))>0
end
for i,j in pairs(rpairs)do
local ri=Runways[i]
local rj=Runways[j]
local c0=ri.center
local a=UTILS.VecTranslate(c0,1000,ri.heading)
local b=UTILS.VecSubstract(rj.center,ri.center)
b=UTILS.VecAdd(ri.center,b)
local left=isLeft(c0,a,b)
if left then
ri.isLeft=false
rj.isLeft=true
else
ri.isLeft=true
rj.isLeft=false
end
end
self.runways=Runways
return Runways
end
function AIRBASE:GetRunwayData(magvar,mark)
local runways={}
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
return{}
end
local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway)
if false then
for i,_coord in pairs(runwaycoords)do
local coord=_coord
coord:Translate(100,0):MarkToAll("Runway i="..i)
end
end
magvar=magvar or UTILS.GetMagneticDeclination()
local N=#runwaycoords
local N2=N/2
local exception=false
local name=self:GetName()
if name==AIRBASE.Nevada.Jean_Airport or
name==AIRBASE.Nevada.Creech_AFB or
name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or
name==AIRBASE.PersianGulf.Dubai_Intl or
name==AIRBASE.PersianGulf.Shiraz_International_Airport or
name==AIRBASE.PersianGulf.Kish_International_Airport or
name==AIRBASE.MarianaIslands.Andersen_AFB then
exception=1
elseif UTILS.GetDCSMap()==DCSMAP.Syria and N>=2 and
name~=AIRBASE.Syria.Minakh and
name~=AIRBASE.Syria.Damascus and
name~=AIRBASE.Syria.Khalkhalah and
name~=AIRBASE.Syria.Marj_Ruhayyil and
name~=AIRBASE.Syria.Beirut_Rafic_Hariri then
exception=2
end
local function f(i)
local j
if exception==1 then
j=N-(i-1)
elseif exception==2 then
if i<=N2 then
j=i+N2
else
j=i-N2
end
else
if i%2==0 then
j=i-1
else
j=i+1
end
end
if name==AIRBASE.Syria.Beirut_Rafic_Hariri then
if i==1 then
j=3
elseif i==2 then
j=6
elseif i==3 then
j=1
elseif i==4 then
j=5
elseif i==5 then
j=4
elseif i==6 then
j=2
end
end
if name==AIRBASE.Syria.Ramat_David then
if i==1 then
j=4
elseif i==2 then
j=6
elseif i==3 then
j=5
elseif i==4 then
j=1
elseif i==5 then
j=3
elseif i==6 then
j=2
end
end
return j
end
for i=1,N do
local j=f(i)
local c1=runwaycoords[i]
local c2=runwaycoords[j]
local hdg=c1:HeadingTo(c2)
local idx=string.format("%02d",UTILS.Round((hdg-magvar)/10,0))
local runway={}
runway.heading=hdg
runway.idx=idx
runway.length=c1:Get2DDistance(c2)
runway.position=c1
runway.endpoint=c2
if mark then
runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m, i=%d, j=%d",runway.idx,runway.heading,magvar,runway.length,i,j))
end
table.insert(runways,runway)
end
return runways
end
function AIRBASE:SetActiveRunway(Name,PreferLeft)
self:SetActiveRunwayTakeoff(Name,PreferLeft)
self:SetActiveRunwayLanding(Name,PreferLeft)
end
function AIRBASE:SetActiveRunwayLanding(Name,PreferLeft)
local runway=self:GetRunwayByName(Name)
if not runway then
runway=self:GetRunwayIntoWind(PreferLeft)
end
if runway then
self:T(string.format("%s: Setting active runway for landing as %s",self.AirbaseName,self:GetRunwayName(runway)))
else
self:E("ERROR: Could not set the runway for landing!")
end
self.runwayLanding=runway
return runway
end
function AIRBASE:GetActiveRunway()
return self.runwayLanding,self.runwayTakeoff
end
function AIRBASE:GetActiveRunwayLanding()
return self.runwayLanding
end
function AIRBASE:GetActiveRunwayTakeoff()
return self.runwayTakeoff
end
function AIRBASE:SetActiveRunwayTakeoff(Name,PreferLeft)
local runway=self:GetRunwayByName(Name)
if not runway then
runway=self:GetRunwayIntoWind(PreferLeft)
end
if runway then
self:T(string.format("%s: Setting active runway for takeoff as %s",self.AirbaseName,self:GetRunwayName(runway)))
else
self:E("ERROR: Could not set the runway for takeoff!")
end
self.runwayTakeoff=runway
return runway
end
function AIRBASE:GetRunwayIntoWind(PreferLeft)
local runways=self:GetRunways()
local Vwind=self:GetCoordinate():GetWindWithTurbulenceVec3()
local norm=UTILS.VecNorm(Vwind)
local iact=1
if norm>0 then
Vwind.x=Vwind.x/norm
Vwind.y=0
Vwind.z=Vwind.z/norm
local dotmin=nil
for i,_runway in pairs(runways)do
local runway=_runway
if PreferLeft==nil or PreferLeft==runway.isLeft then
local alpha=math.rad(runway.heading)
local Vrunway={x=math.cos(alpha),y=0,z=math.sin(alpha)}
local dot=UTILS.VecDot(Vwind,Vrunway)
if dotmin==nil or dot<dotmin then
dotmin=dot
iact=i
end
end
end
else
self:E("WARNING: Norm of wind is zero! Cannot determine runway based on wind direction")
end
return runways[iact]
end
function AIRBASE:GetRunwayName(Runway,LongLeftRight)
Runway=Runway or self:GetActiveRunway()
local name="XX"
if Runway then
name=Runway.name
if Runway.isLeft==true then
if LongLeftRight then
name=name.." Left"
else
name=name.."L"
end
elseif Runway.isLeft==false then
if LongLeftRight then
name=name.." Right"
else
name=name.."R"
end
end
end
return name
end
function AIRBASE:CheckOnRunWay(group,radius,despawn)
radius=radius or 50
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
return false
end
if group and group:IsAlive()then
self:T(string.format("%s, checking if group %s is on runway?",self:GetName(),group:GetName()))
local runwaypoints=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway)
local units=group:GetUnits()
for _,_unit in pairs(units)do
local unit=_unit
if unit and unit:IsAlive()and not unit:InAir()then
self:T(string.format("%s, checking if unit %s is on runway?",self:GetName(),unit:GetName()))
for _i,_coord in pairs(runwaypoints)do
local dist=unit:GetCoordinate():Get2DDistance(_coord)
if dist<radius then
self:E(string.format("%s, unit %s of group %s was spawned on runway #%d. Distance %.1f < radius %.1f m. Despawn = %s.",self:GetName(),unit:GetName(),group:GetName(),_i,dist,radius,tostring(despawn)))
if despawn then
group:Destroy(true)
end
return true
else
self:T(string.format("%s, unit %s of group %s was NOT spawned on runway #%d. Distance %.1f > radius %.1f m. Despawn = %s.",self:GetName(),unit:GetName(),group:GetName(),_i,dist,radius,tostring(despawn)))
end
end
else
self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(),unit:GetName(),group:GetName()))
end
end
else
self:T(string.format("%s, checking if group %s is on runway. Group is NOT alive.",self:GetName(),group:GetName()))
end
return false
end
SCENERY={
ClassName="SCENERY",
}
function SCENERY:Register(SceneryName,SceneryObject)
local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName))
self.SceneryName=SceneryName
self.SceneryObject=SceneryObject
if self.SceneryObject then
self.Life0=self.SceneryObject:getLife()
else
self.Life0=0
end
return self
end
function SCENERY:GetDCSObject()
return self.SceneryObject
end
function SCENERY:GetLife()
local life=0
if self.SceneryObject then
life=self.SceneryObject:getLife()
end
return life
end
function SCENERY:GetLife0()
return self.Life0 or 0
end
function SCENERY:IsAlive()
return self:GetLife()>=1 and true or false
end
function SCENERY:IsDead()
return self:GetLife()<1 and true or false
end
function SCENERY:GetThreatLevel()
return 0,"Scenery"
end
function SCENERY:FindByName(Name,Coordinate,Radius)
local radius=Radius or 100
local name=Name or"unknown"
local scenery=nil
local function SceneryScan(coordinate,radius,name)
if coordinate~=nil then
local scenerylist=coordinate:ScanScenery(radius)
local rscenery=nil
for _,_scenery in pairs(scenerylist)do
local scenery=_scenery
if tostring(scenery.SceneryName)==tostring(name)then
rscenery=scenery
break
end
end
return rscenery
end
return nil
end
if Coordinate then
scenery=SceneryScan(Coordinate,radius,name)
end
return scenery
end
function SCENERY:FindByNameInZone(Name,Zone,Radius)
local radius=Radius or 100
local name=Name or"unknown"
if type(Zone)=="string"then
Zone=ZONE:FindByName(Zone)
end
local coordinate=Zone:GetCoordinate()
return self:FindByName(Name,coordinate,Radius)
end
function SCENERY:FindByZoneName(ZoneName)
local zone=ZoneName
if type(ZoneName)=="string"then
zone=ZONE:FindByName(ZoneName)
end
local _id=zone:GetProperty('OBJECT ID')
if not _id then
BASE:E("**** Zone without object ID: "..ZoneName.." | Type: "..tostring(zone.ClassName))
if string.find(zone.ClassName,"POLYGON")then
zone:Scan({Object.Category.SCENERY})
local scanned=zone:GetScannedScenery()
for _,_scenery in(scanned)do
local scenery=_scenery
if scenery:IsAlive()then
return scenery
end
end
return nil
else
local coordinate=zone:GetCoordinate()
local scanned=coordinate:ScanScenery()
for _,_scenery in(scanned)do
local scenery=_scenery
if scenery:IsAlive()then
return scenery
end
end
return nil
end
else
return self:FindByName(_id,zone:GetCoordinate())
end
end
function SCENERY:FindAllByZoneName(ZoneName)
local zone=ZoneName
if type(ZoneName)=="string"then
zone=ZONE:FindByName(ZoneName)
end
local _id=zone:GetProperty('OBJECT ID')
if not _id then
zone:Scan({Object.Category.SCENERY})
local scanned=zone:GetScannedSceneryObjects()
if#scanned>0 then
return scanned
else
return nil
end
else
local obj=self:FindByName(_id,zone:GetCoordinate())
if obj then
return{obj}
else
return nil
end
end
end
function SCENERY:Destroy()
return self
end
MARKER={
ClassName="MARKER",
Debug=false,
lid=nil,
mid=nil,
coordinate=nil,
text=nil,
message=nil,
readonly=nil,
coalition=nil,
}
_MARKERID=0
MARKER.version="0.1.1"
function MARKER:New(Coordinate,Text)
local self=BASE:Inherit(self,FSM:New())
self.coordinate=UTILS.DeepCopy(Coordinate)
self.text=Text
self.readonly=false
self.message=""
_MARKERID=_MARKERID+1
self.myid=_MARKERID
self.lid=string.format("Marker #%d | ",self.myid)
self:SetStartState("Invisible")
self:AddTransition("Invisible","Added","Visible")
self:AddTransition("Visible","Removed","Invisible")
self:AddTransition("*","Changed","*")
self:AddTransition("*","TextUpdate","*")
self:AddTransition("*","CoordUpdate","*")
self:HandleEvent(EVENTS.MarkAdded)
self:HandleEvent(EVENTS.MarkRemoved)
self:HandleEvent(EVENTS.MarkChange)
return self
end
function MARKER:ReadOnly()
self.readonly=true
return self
end
function MARKER:ReadWrite()
self.readonly=false
return self
end
function MARKER:Message(Text)
self.message=Text or""
return self
end
function MARKER:ToAll(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.ToAll,self)
else
self.toall=true
self.tocoalition=nil
self.coalition=nil
self.togroup=nil
self.groupname=nil
self.groupid=nil
if self.shown then
self:Remove()
end
self.mid=UTILS.GetMarkID()
trigger.action.markToAll(self.mid,self.text,self.coordinate:GetVec3(),self.readonly,self.message)
end
return self
end
function MARKER:ToCoalition(Coalition,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.ToCoalition,self,Coalition)
else
self.coalition=Coalition
self.tocoalition=true
self.toall=false
self.togroup=false
self.groupname=nil
self.groupid=nil
if self.shown then
self:Remove()
end
self.mid=UTILS.GetMarkID()
trigger.action.markToCoalition(self.mid,self.text,self.coordinate:GetVec3(),self.coalition,self.readonly,self.message)
end
return self
end
function MARKER:ToBlue(Delay)
self:ToCoalition(coalition.side.BLUE,Delay)
return self
end
function MARKER:ToRed(Delay)
self:ToCoalition(coalition.side.RED,Delay)
return self
end
function MARKER:ToNeutral(Delay)
self:ToCoalition(coalition.side.NEUTRAL,Delay)
return self
end
function MARKER:ToGroup(Group,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.ToGroup,self,Group)
else
if Group and Group:IsAlive()~=nil then
self.groupid=Group:GetID()
if self.groupid then
self.groupname=Group:GetName()
self.togroup=true
self.tocoalition=nil
self.coalition=nil
self.toall=nil
if self.shown then
self:Remove()
end
self.mid=UTILS.GetMarkID()
trigger.action.markToGroup(self.mid,self.text,self.coordinate:GetVec3(),self.groupid,self.readonly,self.message)
end
else
end
end
return self
end
function MARKER:UpdateText(Text,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.UpdateText,self,Text)
else
self.text=tostring(Text)
self:Refresh()
self:TextUpdate(tostring(Text))
end
return self
end
function MARKER:UpdateCoordinate(Coordinate,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.UpdateCoordinate,self,Coordinate)
else
self.coordinate=Coordinate
self:Refresh()
self:CoordUpdate(Coordinate)
end
return self
end
function MARKER:Refresh(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.Refresh,self)
else
if self.toall then
self:ToAll()
elseif self.tocoalition then
self:ToCoalition(self.coalition)
elseif self.togroup then
local group=GROUP:FindByName(self.groupname)
self:ToGroup(group)
else
self:E(self.lid.."ERROR: unknown To in :Refresh()!")
end
end
return self
end
function MARKER:Remove(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.Remove,self)
else
if self.shown then
trigger.action.removeMark(self.mid)
end
end
return self
end
function MARKER:GetCoordinate()
return self.coordinate
end
function MARKER:GetText()
return self.text
end
function MARKER:SetText(Text)
self.text=Text and tostring(Text)or""
return self
end
function MARKER:IsVisible()
return self:Is("Visible")
end
function MARKER:IsInvisible()
return self:Is("Invisible")
end
function MARKER:OnEventMarkAdded(EventData)
if EventData and EventData.MarkID then
local MarkID=EventData.MarkID
self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s",tostring(MarkID)))
if MarkID==self.mid then
self.shown=true
self:Added(EventData)
end
end
end
function MARKER:OnEventMarkRemoved(EventData)
if EventData and EventData.MarkID then
local MarkID=EventData.MarkID
local MarkID=EventData.MarkID
self:T3(self.lid..string.format("Captured event MarkRemoved for Mark ID=%s",tostring(MarkID)))
if MarkID==self.mid then
self.shown=false
self:Removed(EventData)
end
end
end
function MARKER:OnEventMarkChange(EventData)
if EventData and EventData.MarkID then
local MarkID=EventData.MarkID
self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s",tostring(MarkID)))
if MarkID==self.mid then
local MarkID=EventData.MarkID
self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s",tostring(MarkID)))
if MarkID==self.mid then
self.text=tostring(EventData.MarkText)
self:Changed(EventData)
end
end
end
end
function MARKER:onafterAdded(From,Event,To,EventData)
local text=string.format("Captured event MarkAdded for myself:\n")
text=text..string.format("Marker ID  = %s\n",tostring(EventData.MarkID))
text=text..string.format("Coalition  = %s\n",tostring(EventData.MarkCoalition))
text=text..string.format("Group  ID  = %s\n",tostring(EventData.MarkGroupID))
text=text..string.format("Initiator  = %s\n",EventData.IniUnit and EventData.IniUnit:GetName()or"Nobody")
text=text..string.format("Coordinate = %s\n",EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS()or"Nowhere")
text=text..string.format("Text:          \n%s",tostring(EventData.MarkText))
self:T2(self.lid..text)
end
function MARKER:onafterRemoved(From,Event,To,EventData)
local text=string.format("Captured event MarkRemoved for myself:\n")
text=text..string.format("Marker ID  = %s\n",tostring(EventData.MarkID))
text=text..string.format("Coalition  = %s\n",tostring(EventData.MarkCoalition))
text=text..string.format("Group  ID  = %s\n",tostring(EventData.MarkGroupID))
text=text..string.format("Initiator  = %s\n",EventData.IniUnit and EventData.IniUnit:GetName()or"Nobody")
text=text..string.format("Coordinate = %s\n",EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS()or"Nowhere")
text=text..string.format("Text:          \n%s",tostring(EventData.MarkText))
self:T2(self.lid..text)
end
function MARKER:onafterChanged(From,Event,To,EventData)
local text=string.format("Captured event MarkChange for myself:\n")
text=text..string.format("Marker ID  = %s\n",tostring(EventData.MarkID))
text=text..string.format("Coalition  = %s\n",tostring(EventData.MarkCoalition))
text=text..string.format("Group  ID  = %s\n",tostring(EventData.MarkGroupID))
text=text..string.format("Initiator  = %s\n",EventData.IniUnit and EventData.IniUnit:GetName()or"Nobody")
text=text..string.format("Coordinate = %s\n",EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS()or"Nowhere")
text=text..string.format("Text:          \n%s",tostring(EventData.MarkText))
self:T2(self.lid..text)
end
function MARKER:onafterTextUpdate(From,Event,To,Text)
self:T(self.lid..string.format("New Marker Text:\n%s",Text))
end
function MARKER:onafterCoordUpdate(From,Event,To,Coordinate)
self:T(self.lid..string.format("New Marker Coordinate in LL DMS: %s",Coordinate:ToStringLLDMS()))
end
CARGOS={}
do
CARGO={
ClassName="CARGO",
Type=nil,
Name=nil,
Weight=nil,
CargoObject=nil,
CargoCarrier=nil,
Representable=false,
Slingloadable=false,
Moveable=false,
Containable=false,
Reported={},
}
function CARGO:New(Type,Name,Weight,LoadRadius,NearRadius)
local self=BASE:Inherit(self,FSM:New())
self:F({Type,Name,Weight,LoadRadius,NearRadius})
self:SetStartState("UnLoaded")
self:AddTransition({"UnLoaded","Boarding"},"Board","Boarding")
self:AddTransition("Boarding","Boarding","Boarding")
self:AddTransition("Boarding","CancelBoarding","UnLoaded")
self:AddTransition("Boarding","Load","Loaded")
self:AddTransition("UnLoaded","Load","Loaded")
self:AddTransition("Loaded","UnBoard","UnBoarding")
self:AddTransition("UnBoarding","UnBoarding","UnBoarding")
self:AddTransition("UnBoarding","UnLoad","UnLoaded")
self:AddTransition("Loaded","UnLoad","UnLoaded")
self:AddTransition("*","Damaged","Damaged")
self:AddTransition("*","Destroyed","Destroyed")
self:AddTransition("*","Respawn","UnLoaded")
self:AddTransition("*","Reset","UnLoaded")
self.Type=Type
self.Name=Name
self.Weight=Weight or 0
self.CargoObject=nil
self.CargoCarrier=nil
self.Representable=false
self.Slingloadable=false
self.Moveable=false
self.Containable=false
self.CargoLimit=0
self.LoadRadius=LoadRadius or 500
self:SetDeployed(false)
self.CargoScheduler=SCHEDULER:New()
CARGOS[self.Name]=self
return self
end
function CARGO:FindByName(CargoName)
local CargoFound=_DATABASE:FindCargo(CargoName)
return CargoFound
end
function CARGO:GetX()
if self:IsLoaded()then
return self.CargoCarrier:GetCoordinate().x
else
return self.CargoObject:GetCoordinate().x
end
end
function CARGO:GetY()
if self:IsLoaded()then
return self.CargoCarrier:GetCoordinate().z
else
return self.CargoObject:GetCoordinate().z
end
end
function CARGO:GetHeading()
if self:IsLoaded()then
return self.CargoCarrier:GetHeading()
else
return self.CargoObject:GetHeading()
end
end
function CARGO:CanSlingload()
return false
end
function CARGO:CanBoard()
return true
end
function CARGO:CanUnboard()
return true
end
function CARGO:CanLoad()
return true
end
function CARGO:CanUnload()
return true
end
function CARGO:Destroy()
if self.CargoObject then
self.CargoObject:Destroy()
end
self:Destroyed()
end
function CARGO:GetName()
return self.Name
end
function CARGO:GetObject()
if self:IsLoaded()then
return self.CargoCarrier
else
return self.CargoObject
end
end
function CARGO:GetObjectName()
if self:IsLoaded()then
return self.CargoCarrier:GetName()
else
return self.CargoObject:GetName()
end
end
function CARGO:GetCount()
return 1
end
function CARGO:GetType()
return self.Type
end
function CARGO:GetTransportationMethod()
return self.TransportationMethod
end
function CARGO:GetCoalition()
if self:IsLoaded()then
return self.CargoCarrier:GetCoalition()
else
return self.CargoObject:GetCoalition()
end
end
function CARGO:GetCoordinate()
return self.CargoObject:GetCoordinate()
end
function CARGO:IsDestroyed()
return self:Is("Destroyed")
end
function CARGO:IsLoaded()
return self:Is("Loaded")
end
function CARGO:IsLoadedInCarrier(Carrier)
return self.CargoCarrier and self.CargoCarrier:GetName()==Carrier:GetName()
end
function CARGO:IsUnLoaded()
return self:Is("UnLoaded")
end
function CARGO:IsBoarding()
return self:Is("Boarding")
end
function CARGO:IsUnboarding()
return self:Is("UnBoarding")
end
function CARGO:IsAlive()
if self:IsLoaded()then
return self.CargoCarrier:IsAlive()
else
return self.CargoObject:IsAlive()
end
end
function CARGO:SetDeployed(Deployed)
self.Deployed=Deployed
end
function CARGO:IsDeployed()
return self.Deployed
end
function CARGO:Spawn(PointVec2)
self:F()
end
function CARGO:Flare(FlareColor)
if self:IsUnLoaded()then
trigger.action.signalFlare(self.CargoObject:GetVec3(),FlareColor,0)
end
end
function CARGO:FlareWhite()
self:Flare(trigger.flareColor.White)
end
function CARGO:FlareYellow()
self:Flare(trigger.flareColor.Yellow)
end
function CARGO:FlareGreen()
self:Flare(trigger.flareColor.Green)
end
function CARGO:FlareRed()
self:Flare(trigger.flareColor.Red)
end
function CARGO:Smoke(SmokeColor,Radius)
if self:IsUnLoaded()then
if Radius then
trigger.action.smoke(self.CargoObject:GetRandomVec3(Radius),SmokeColor)
else
trigger.action.smoke(self.CargoObject:GetVec3(),SmokeColor)
end
end
end
function CARGO:SmokeGreen()
self:Smoke(trigger.smokeColor.Green,Range)
end
function CARGO:SmokeRed()
self:Smoke(trigger.smokeColor.Red,Range)
end
function CARGO:SmokeWhite()
self:Smoke(trigger.smokeColor.White,Range)
end
function CARGO:SmokeOrange()
self:Smoke(trigger.smokeColor.Orange,Range)
end
function CARGO:SmokeBlue()
self:Smoke(trigger.smokeColor.Blue,Range)
end
function CARGO:SetLoadRadius(LoadRadius)
self.LoadRadius=LoadRadius or 150
end
function CARGO:GetLoadRadius()
return self.LoadRadius
end
function CARGO:IsInLoadRadius(Coordinate)
self:F({Coordinate,LoadRadius=self.LoadRadius})
local Distance=0
if self:IsUnLoaded()then
local CargoCoordinate=self.CargoObject:GetCoordinate()
Distance=Coordinate:Get2DDistance(CargoCoordinate)
self:T(Distance)
if Distance<=self.LoadRadius then
return true
end
end
return false
end
function CARGO:IsInReportRadius(Coordinate)
self:F({Coordinate})
local Distance=0
if self:IsUnLoaded()then
Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate())
self:T(Distance)
if Distance<=self.LoadRadius then
return true
end
end
return false
end
function CARGO:IsNear(Coordinate,NearRadius)
if self.CargoObject:IsAlive()then
local Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate())
if Distance<=NearRadius then
return true
end
end
return false
end
function CARGO:IsInZone(Zone)
if self:IsLoaded()then
return Zone:IsPointVec2InZone(self.CargoCarrier:GetPointVec2())
else
if self.CargoObject:GetSize()~=0 then
return Zone:IsPointVec2InZone(self.CargoObject:GetPointVec2())
else
return false
end
end
return nil
end
function CARGO:GetPointVec2()
return self.CargoObject:GetPointVec2()
end
function CARGO:GetCoordinate()
return self.CargoObject:GetCoordinate()
end
function CARGO:GetWeight()
return self.Weight
end
function CARGO:SetWeight(Weight)
self.Weight=Weight
return self
end
function CARGO:GetVolume()
return self.Volume
end
function CARGO:SetVolume(Volume)
self.Volume=Volume
return self
end
function CARGO:MessageToGroup(Message,CarrierGroup,Name)
MESSAGE:New(Message,20,"Cargo "..self:GetName()):ToGroup(CarrierGroup)
end
function CARGO:Report(ReportText,Action,CarrierGroup)
if not self.Reported[CarrierGroup]or not self.Reported[CarrierGroup][Action]then
self.Reported[CarrierGroup]={}
self.Reported[CarrierGroup][Action]=true
self:MessageToGroup(ReportText,CarrierGroup)
if self.ReportFlareColor then
if not self.Reported[CarrierGroup]["Flaring"]then
self:Flare(self.ReportFlareColor)
self.Reported[CarrierGroup]["Flaring"]=true
end
end
if self.ReportSmokeColor then
if not self.Reported[CarrierGroup]["Smoking"]then
self:Smoke(self.ReportSmokeColor)
self.Reported[CarrierGroup]["Smoking"]=true
end
end
end
end
function CARGO:ReportFlare(FlareColor)
self.ReportFlareColor=FlareColor
end
function CARGO:ReportSmoke(SmokeColor)
self.ReportSmokeColor=SmokeColor
end
function CARGO:ReportReset(Action,CarrierGroup)
self.Reported[CarrierGroup][Action]=nil
end
function CARGO:ReportResetAll(CarrierGroup)
self.Reported[CarrierGroup]=nil
end
function CARGO:RespawnOnDestroyed(RespawnDestroyed)
if RespawnDestroyed then
self.onenterDestroyed=function(self)
self:Respawn()
end
else
self.onenterDestroyed=nil
end
end
end
do
CARGO_REPRESENTABLE={
ClassName="CARGO_REPRESENTABLE"
}
function CARGO_REPRESENTABLE:New(CargoObject,Type,Name,LoadRadius,NearRadius)
local self=BASE:Inherit(self,CARGO:New(Type,Name,0,LoadRadius,NearRadius))
self:F({Type,Name,LoadRadius,NearRadius})
local Desc=CargoObject:GetDesc()
self:T({Desc=Desc})
local Weight=math.random(80,120)
if Desc then
if Desc.typeName=="2B11 mortar"then
Weight=210
else
Weight=Desc.massEmpty
end
end
self:SetWeight(Weight)
return self
end
function CARGO_REPRESENTABLE:Destroy()
self:F({CargoName=self:GetName()})
return self
end
function CARGO_REPRESENTABLE:RouteTo(ToPointVec2,Speed)
self:F2(ToPointVec2)
local Points={}
local PointStartVec2=self.CargoObject:GetPointVec2()
Points[#Points+1]=PointStartVec2:WaypointGround(Speed)
Points[#Points+1]=ToPointVec2:WaypointGround(Speed)
local TaskRoute=self.CargoObject:TaskRoute(Points)
self.CargoObject:SetTask(TaskRoute,2)
return self
end
function CARGO_REPRESENTABLE:MessageToGroup(Message,TaskGroup,Name)
local CoordinateZone=ZONE_RADIUS:New("Zone",self:GetCoordinate():GetVec2(),500)
CoordinateZone:Scan({Object.Category.UNIT})
for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do
local NearUnit=UNIT:Find(DCSUnit)
self:F({NearUnit=NearUnit})
local NearUnitCoalition=NearUnit:GetCoalition()
local CargoCoalition=self:GetCoalition()
if NearUnitCoalition==CargoCoalition then
local Attributes=NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttribute("Trucks")then
MESSAGE:New(Message,20,NearUnit:GetCallsign().." reporting - Cargo "..self:GetName()):ToGroup(TaskGroup)
break
end
end
end
end
end
do
CARGO_REPORTABLE={
ClassName="CARGO_REPORTABLE"
}
function CARGO_REPORTABLE:New(Type,Name,Weight,LoadRadius,NearRadius)
local self=BASE:Inherit(self,CARGO:New(Type,Name,Weight,LoadRadius,NearRadius))
self:F({Type,Name,Weight,LoadRadius,NearRadius})
return self
end
function CARGO_REPORTABLE:MessageToGroup(Message,TaskGroup,Name)
MESSAGE:New(Message,20,"Cargo "..self:GetName().." reporting"):ToGroup(TaskGroup)
end
end
do
CARGO_PACKAGE={
ClassName="CARGO_PACKAGE"
}
function CARGO_PACKAGE:New(CargoCarrier,Type,Name,Weight,LoadRadius,NearRadius)
local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoCarrier,Type,Name,Weight,LoadRadius,NearRadius))
self:F({Type,Name,Weight,LoadRadius,NearRadius})
self:T(CargoCarrier)
self.CargoCarrier=CargoCarrier
return self
end
function CARGO_PACKAGE:onafterOnBoard(From,Event,To,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle)
self:F()
self.CargoInAir=self.CargoCarrier:InAir()
self:T(self.CargoInAir)
if not self.CargoInAir then
local Points={}
local StartPointVec2=self.CargoCarrier:GetPointVec2()
local CargoCarrierHeading=CargoCarrier:GetHeading()
local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
self:T({CargoCarrierHeading,CargoDeployHeading})
local CargoDeployPointVec2=CargoCarrier:GetPointVec2():Translate(BoardDistance,CargoDeployHeading)
Points[#Points+1]=StartPointVec2:WaypointGround(Speed)
Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed)
local TaskRoute=self.CargoCarrier:TaskRoute(Points)
self.CargoCarrier:SetTask(TaskRoute,1)
end
self:Boarded(CargoCarrier,Speed,BoardDistance,LoadDistance,Angle)
end
function CARGO_PACKAGE:IsNear(CargoCarrier)
self:F()
local CargoCarrierPoint=CargoCarrier:GetCoordinate()
local Distance=CargoCarrierPoint:Get2DDistance(self.CargoCarrier:GetCoordinate())
self:T(Distance)
if Distance<=self.NearRadius then
return true
else
return false
end
end
function CARGO_PACKAGE:onafterOnBoarded(From,Event,To,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle)
self:F()
if self:IsNear(CargoCarrier)then
self:__Load(1,CargoCarrier,Speed,LoadDistance,Angle)
else
self:__Boarded(1,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle)
end
end
function CARGO_PACKAGE:onafterUnBoard(From,Event,To,CargoCarrier,Speed,UnLoadDistance,UnBoardDistance,Radius,Angle)
self:F()
self.CargoInAir=self.CargoCarrier:InAir()
self:T(self.CargoInAir)
if not self.CargoInAir then
self:_Next(self.FsmP.UnLoad,UnLoadDistance,Angle)
local Points={}
local StartPointVec2=CargoCarrier:GetPointVec2()
local CargoCarrierHeading=self.CargoCarrier:GetHeading()
local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
self:T({CargoCarrierHeading,CargoDeployHeading})
local CargoDeployPointVec2=StartPointVec2:Translate(UnBoardDistance,CargoDeployHeading)
Points[#Points+1]=StartPointVec2:WaypointGround(Speed)
Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed)
local TaskRoute=CargoCarrier:TaskRoute(Points)
CargoCarrier:SetTask(TaskRoute,1)
end
self:__UnBoarded(1,CargoCarrier,Speed)
end
function CARGO_PACKAGE:onafterUnBoarded(From,Event,To,CargoCarrier,Speed)
self:F()
if self:IsNear(CargoCarrier)then
self:__UnLoad(1,CargoCarrier,Speed)
else
self:__UnBoarded(1,CargoCarrier,Speed)
end
end
function CARGO_PACKAGE:onafterLoad(From,Event,To,CargoCarrier,Speed,LoadDistance,Angle)
self:F()
self.CargoCarrier=CargoCarrier
local StartPointVec2=self.CargoCarrier:GetPointVec2()
local CargoCarrierHeading=self.CargoCarrier:GetHeading()
local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
local CargoDeployPointVec2=StartPointVec2:Translate(LoadDistance,CargoDeployHeading)
local Points={}
Points[#Points+1]=StartPointVec2:WaypointGround(Speed)
Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed)
local TaskRoute=self.CargoCarrier:TaskRoute(Points)
self.CargoCarrier:SetTask(TaskRoute,1)
end
function CARGO_PACKAGE:onafterUnLoad(From,Event,To,CargoCarrier,Speed,Distance,Angle)
self:F()
local StartPointVec2=self.CargoCarrier:GetPointVec2()
local CargoCarrierHeading=self.CargoCarrier:GetHeading()
local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
local CargoDeployPointVec2=StartPointVec2:Translate(Distance,CargoDeployHeading)
self.CargoCarrier=CargoCarrier
local Points={}
Points[#Points+1]=StartPointVec2:WaypointGround(Speed)
Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed)
local TaskRoute=self.CargoCarrier:TaskRoute(Points)
self.CargoCarrier:SetTask(TaskRoute,1)
end
end
do
CARGO_UNIT={
ClassName="CARGO_UNIT"
}
function CARGO_UNIT:New(CargoUnit,Type,Name,LoadRadius,NearRadius)
local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoUnit,Type,Name,LoadRadius,NearRadius))
self:T({Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
self.CargoObject=CargoUnit
self:SetEventPriority(5)
return self
end
function CARGO_UNIT:onenterUnBoarding(From,Event,To,ToPointVec2,NearRadius)
self:F({From,Event,To,ToPointVec2,NearRadius})
local Angle=180
local Speed=60
local DeployDistance=9
local RouteDistance=60
if From=="Loaded"then
if not self:IsDestroyed()then
local CargoCarrier=self.CargoCarrier
if CargoCarrier:IsAlive()then
local CargoCarrierPointVec2=CargoCarrier:GetPointVec2()
local CargoCarrierHeading=self.CargoCarrier:GetHeading()
local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
local CargoRoutePointVec2=CargoCarrierPointVec2:Translate(RouteDistance,CargoDeployHeading)
local FromDirectionVec3=CargoCarrierPointVec2:GetDirectionVec3(ToPointVec2 or CargoRoutePointVec2)
local FromAngle=CargoCarrierPointVec2:GetAngleDegrees(FromDirectionVec3)
local FromPointVec2=CargoCarrierPointVec2:Translate(DeployDistance,FromAngle)
ToPointVec2=ToPointVec2 or CargoCarrierPointVec2:GetRandomCoordinateInRadius(NearRadius,DeployDistance)
if self.CargoObject then
if CargoCarrier:IsShip()then
self.CargoObject:ReSpawnAt(ToPointVec2,CargoDeployHeading)
else
self.CargoObject:ReSpawnAt(FromPointVec2,CargoDeployHeading)
end
self:F({"CargoUnits:",self.CargoObject:GetGroup():GetName()})
self.CargoCarrier=nil
local Points={}
Points[#Points+1]=FromPointVec2:WaypointGround(Speed,"Vee")
Points[#Points+1]=ToPointVec2:WaypointGround(Speed,"Vee")
local TaskRoute=self.CargoObject:TaskRoute(Points)
self.CargoObject:SetTask(TaskRoute,1)
self:__UnBoarding(1,ToPointVec2,NearRadius)
end
else
self:Destroyed()
end
end
end
end
function CARGO_UNIT:onleaveUnBoarding(From,Event,To,ToPointVec2,NearRadius)
self:F({From,Event,To,ToPointVec2,NearRadius})
local Angle=180
local Speed=10
local Distance=5
if From=="UnBoarding"then
return true
end
end
function CARGO_UNIT:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius)
self:F({From,Event,To,ToPointVec2,NearRadius})
self.CargoInAir=self.CargoObject:InAir()
self:T(self.CargoInAir)
if not self.CargoInAir then
end
self:__UnLoad(1,ToPointVec2,NearRadius)
end
function CARGO_UNIT:onenterUnLoaded(From,Event,To,ToPointVec2)
self:F({ToPointVec2,From,Event,To})
local Angle=180
local Speed=10
local Distance=5
if From=="Loaded"then
local StartPointVec2=self.CargoCarrier:GetPointVec2()
local CargoCarrierHeading=self.CargoCarrier:GetHeading()
local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
local CargoDeployCoord=StartPointVec2:Translate(Distance,CargoDeployHeading)
ToPointVec2=ToPointVec2 or COORDINATE:New(CargoDeployCoord.x,CargoDeployCoord.z)
if self.CargoObject then
self.CargoObject:ReSpawnAt(ToPointVec2,0)
self.CargoCarrier=nil
end
end
if self.OnUnLoadedCallBack then
self.OnUnLoadedCallBack(self,unpack(self.OnUnLoadedParameters))
self.OnUnLoadedCallBack=nil
end
end
function CARGO_UNIT:onafterBoard(From,Event,To,CargoCarrier,NearRadius,...)
self:F({From,Event,To,CargoCarrier,NearRadius=NearRadius})
self.CargoInAir=self.CargoObject:InAir()
local Desc=self.CargoObject:GetDesc()
local MaxSpeed=Desc.speedMaxOffRoad
local TypeName=Desc.typeName
if not self.CargoInAir then
local NearRadius=NearRadius or CargoCarrier:GetBoundingRadius()+5
if self:IsNear(CargoCarrier:GetPointVec2(),NearRadius)then
self:Load(CargoCarrier,NearRadius,...)
else
if MaxSpeed and MaxSpeed==0 or TypeName and TypeName=="Stinger comm"then
self:Load(CargoCarrier,NearRadius,...)
else
local Speed=90
local Angle=180
local Distance=0
local CargoCarrierPointVec2=CargoCarrier:GetPointVec2()
local CargoCarrierHeading=CargoCarrier:GetHeading()
local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
local CargoDeployPointVec2=CargoCarrierPointVec2:Translate(Distance,CargoDeployHeading)
self.CargoObject:OptionAlarmStateGreen()
local Points={}
local PointStartVec2=self.CargoObject:GetPointVec2()
Points[#Points+1]=PointStartVec2:WaypointGround(Speed)
Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed)
local TaskRoute=self.CargoObject:TaskRoute(Points)
self.CargoObject:SetTask(TaskRoute,2)
self:__Boarding(-5,CargoCarrier,NearRadius,...)
self.RunCount=0
end
end
end
end
function CARGO_UNIT:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...)
self:F({From,Event,To,CargoCarrier:GetName(),NearRadius=NearRadius})
self:F({IsAlive=self.CargoObject:IsAlive()})
if CargoCarrier and CargoCarrier:IsAlive()then
if(CargoCarrier:IsAir()and not CargoCarrier:InAir())or true then
local NearRadius=NearRadius or CargoCarrier:GetBoundingRadius(NearRadius)+5
if self:IsNear(CargoCarrier:GetPointVec2(),NearRadius)then
self:__Load(-1,CargoCarrier,...)
else
if self:IsNear(CargoCarrier:GetPointVec2(),20)then
self:__Boarding(-1,CargoCarrier,NearRadius,...)
self.RunCount=self.RunCount+1
else
self:__Boarding(-2,CargoCarrier,NearRadius,...)
self.RunCount=self.RunCount+2
end
if self.RunCount>=40 then
self.RunCount=0
local Speed=90
local Angle=180
local Distance=0
local CargoCarrierPointVec2=CargoCarrier:GetPointVec2()
local CargoCarrierHeading=CargoCarrier:GetHeading()
local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
local CargoDeployPointVec2=CargoCarrierPointVec2:Translate(Distance,CargoDeployHeading)
self.CargoObject:OptionAlarmStateGreen()
local Points={}
local PointStartVec2=self.CargoObject:GetPointVec2()
Points[#Points+1]=PointStartVec2:WaypointGround(Speed,"Off road")
Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed,"Off road")
local TaskRoute=self.CargoObject:TaskRoute(Points)
self.CargoObject:SetTask(TaskRoute,0.2)
end
end
else
self.CargoObject:MessageToGroup("Cancelling Boarding... Get back on the ground!",5,CargoCarrier:GetGroup(),self:GetName())
self:CancelBoarding(CargoCarrier,NearRadius,...)
self.CargoObject:SetCommand(self.CargoObject:CommandStopRoute(true))
end
else
self:E("Something is wrong")
end
end
function CARGO_UNIT:onenterLoaded(From,Event,To,CargoCarrier)
self:F({From,Event,To,CargoCarrier})
self.CargoCarrier=CargoCarrier
if self.CargoObject then
self.CargoObject:Destroy(false)
end
end
function CARGO_UNIT:GetTransportationMethod()
if self:IsLoaded()then
return"for unboarding"
else
if self:IsUnLoaded()then
return"for boarding"
else
if self:IsDeployed()then
return"delivered"
end
end
end
return""
end
end
do
CARGO_SLINGLOAD={
ClassName="CARGO_SLINGLOAD"
}
function CARGO_SLINGLOAD:New(CargoStatic,Type,Name,LoadRadius,NearRadius)
local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoStatic,Type,Name,nil,LoadRadius,NearRadius))
self:F({Type,Name,NearRadius})
self.CargoObject=CargoStatic
_EVENTDISPATCHER:CreateEventNewCargo(self)
self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead)
self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead)
self:SetEventPriority(4)
self.NearRadius=NearRadius or 25
return self
end
function CARGO_SLINGLOAD:OnEventCargoDead(EventData)
local Destroyed=false
if self:IsDestroyed()or self:IsUnLoaded()then
if self.CargoObject:GetName()==EventData.IniUnitName then
if not self.NoDestroy then
Destroyed=true
end
end
end
if Destroyed then
self:I({"Cargo crate destroyed: "..self.CargoObject:GetName()})
self:Destroyed()
end
end
function CARGO_SLINGLOAD:CanSlingload()
return true
end
function CARGO_SLINGLOAD:CanBoard()
return false
end
function CARGO_SLINGLOAD:CanUnboard()
return false
end
function CARGO_SLINGLOAD:CanLoad()
return false
end
function CARGO_SLINGLOAD:CanUnload()
return false
end
function CARGO_SLINGLOAD:IsInReportRadius(Coordinate)
local Distance=0
if self:IsUnLoaded()then
Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate())
if Distance<=self.LoadRadius then
return true
end
end
return false
end
function CARGO_SLINGLOAD:IsInLoadRadius(Coordinate)
local Distance=0
if self:IsUnLoaded()then
Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate())
if Distance<=self.NearRadius then
return true
end
end
return false
end
function CARGO_SLINGLOAD:GetCoordinate()
return self.CargoObject:GetCoordinate()
end
function CARGO_SLINGLOAD:IsAlive()
local Alive=true
if self:IsLoaded()then
Alive=Alive==true and self.CargoCarrier:IsAlive()
else
Alive=Alive==true and self.CargoObject:IsAlive()
end
return Alive
end
function CARGO_SLINGLOAD:RouteTo(Coordinate)
end
function CARGO_SLINGLOAD:IsNear(CargoCarrier,NearRadius)
return self:IsNear(CargoCarrier:GetCoordinate(),NearRadius)
end
function CARGO_SLINGLOAD:Respawn()
if self.CargoObject then
self.CargoObject:ReSpawn()
self:__Reset(-0.1)
end
end
function CARGO_SLINGLOAD:onafterReset()
if self.CargoObject then
self:SetDeployed(false)
self:SetStartState("UnLoaded")
self.CargoCarrier=nil
_EVENTDISPATCHER:CreateEventNewCargo(self)
end
end
function CARGO_SLINGLOAD:GetTransportationMethod()
if self:IsLoaded()then
return"for sling loading"
else
if self:IsUnLoaded()then
return"for sling loading"
else
if self:IsDeployed()then
return"delivered"
end
end
end
return""
end
end
do
CARGO_CRATE={
ClassName="CARGO_CRATE"
}
function CARGO_CRATE:New(CargoStatic,Type,Name,LoadRadius,NearRadius)
local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoStatic,Type,Name,nil,LoadRadius,NearRadius))
self:F({Type,Name,NearRadius})
self.CargoObject=CargoStatic
_EVENTDISPATCHER:CreateEventNewCargo(self)
self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead)
self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead)
self:SetEventPriority(4)
self.NearRadius=NearRadius or 25
return self
end
function CARGO_CRATE:OnEventCargoDead(EventData)
local Destroyed=false
if self:IsDestroyed()or self:IsUnLoaded()or self:IsBoarding()then
if self.CargoObject:GetName()==EventData.IniUnitName then
if not self.NoDestroy then
Destroyed=true
end
end
else
if self:IsLoaded()then
local CarrierName=self.CargoCarrier:GetName()
if CarrierName==EventData.IniDCSUnitName then
MESSAGE:New("Cargo is lost from carrier "..CarrierName,15):ToAll()
Destroyed=true
self.CargoCarrier:ClearCargo()
end
end
end
if Destroyed then
self:I({"Cargo crate destroyed: "..self.CargoObject:GetName()})
self:Destroyed()
end
end
function CARGO_CRATE:onenterUnLoaded(From,Event,To,ToPointVec2)
local Angle=180
local Speed=10
local Distance=10
if From=="Loaded"then
local StartCoordinate=self.CargoCarrier:GetCoordinate()
local CargoCarrierHeading=self.CargoCarrier:GetHeading()
local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
local CargoDeployCoord=StartCoordinate:Translate(Distance,CargoDeployHeading)
ToPointVec2=ToPointVec2 or COORDINATE:NewFromVec2({x=CargoDeployCoord.x,y=CargoDeployCoord.z})
if self.CargoObject then
self.CargoObject:ReSpawnAt(ToPointVec2,0)
self.CargoCarrier=nil
end
end
if self.OnUnLoadedCallBack then
self.OnUnLoadedCallBack(self,unpack(self.OnUnLoadedParameters))
self.OnUnLoadedCallBack=nil
end
end
function CARGO_CRATE:onenterLoaded(From,Event,To,CargoCarrier)
self.CargoCarrier=CargoCarrier
if self.CargoObject then
self:T("Destroying")
self.NoDestroy=true
self.CargoObject:Destroy(false)
end
end
function CARGO_CRATE:CanBoard()
return false
end
function CARGO_CRATE:CanUnboard()
return false
end
function CARGO_CRATE:CanSlingload()
return false
end
function CARGO_CRATE:IsInReportRadius(Coordinate)
local Distance=0
if self:IsUnLoaded()then
Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate())
if Distance<=self.LoadRadius then
return true
end
end
return false
end
function CARGO_CRATE:IsInLoadRadius(Coordinate)
local Distance=0
if self:IsUnLoaded()then
Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate())
if Distance<=self.NearRadius then
return true
end
end
return false
end
function CARGO_CRATE:GetCoordinate()
return self.CargoObject:GetCoordinate()
end
function CARGO_CRATE:IsAlive()
local Alive=true
if self:IsLoaded()then
Alive=Alive==true and self.CargoCarrier:IsAlive()
else
Alive=Alive==true and self.CargoObject:IsAlive()
end
return Alive
end
function CARGO_CRATE:RouteTo(Coordinate)
self:F({Coordinate=Coordinate})
end
function CARGO_CRATE:IsNear(CargoCarrier,NearRadius)
self:F({NearRadius=NearRadius})
return self:IsNear(CargoCarrier:GetCoordinate(),NearRadius)
end
function CARGO_CRATE:Respawn()
self:F({"Respawning crate "..self:GetName()})
if self.CargoObject then
self.CargoObject:ReSpawn()
self:__Reset(-0.1)
end
end
function CARGO_CRATE:onafterReset()
self:F({"Reset crate "..self:GetName()})
if self.CargoObject then
self:SetDeployed(false)
self:SetStartState("UnLoaded")
self.CargoCarrier=nil
_EVENTDISPATCHER:CreateEventNewCargo(self)
end
end
function CARGO_CRATE:GetTransportationMethod()
if self:IsLoaded()then
return"for unloading"
else
if self:IsUnLoaded()then
return"for loading"
else
if self:IsDeployed()then
return"delivered"
end
end
end
return""
end
end
do
CARGO_GROUP={
ClassName="CARGO_GROUP",
}
function CARGO_GROUP:New(CargoGroup,Type,Name,LoadRadius,NearRadius)
local self=BASE:Inherit(self,CARGO_REPORTABLE:New(Type,Name,0,LoadRadius,NearRadius))
self:F({Type,Name,LoadRadius})
self.CargoSet=SET_CARGO:New()
self.CargoGroup=CargoGroup
self.Grouped=true
self.CargoUnitTemplate={}
self.NearRadius=NearRadius
self:SetDeployed(false)
local WeightGroup=0
local VolumeGroup=0
self.CargoGroup:Destroy()
local GroupName=CargoGroup:GetName()
self.CargoName=Name
self.CargoTemplate=UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName))
self.CargoTemplate.lateActivation=false
self.GroupTemplate=UTILS.DeepCopy(self.CargoTemplate)
self.GroupTemplate.name=self.CargoName.."#CARGO"
self.GroupTemplate.groupId=nil
self.GroupTemplate.units={}
for UnitID,UnitTemplate in pairs(self.CargoTemplate.units)do
UnitTemplate.name=UnitTemplate.name.."#CARGO"
local CargoUnitName=UnitTemplate.name
self.CargoUnitTemplate[CargoUnitName]=UnitTemplate
self.GroupTemplate.units[#self.GroupTemplate.units+1]=self.CargoUnitTemplate[CargoUnitName]
self.GroupTemplate.units[#self.GroupTemplate.units].unitId=nil
local Unit=UNIT:Register(CargoUnitName)
end
self.CargoGroup=GROUP:NewTemplate(self.GroupTemplate,self.GroupTemplate.CoalitionID,self.GroupTemplate.CategoryID,self.GroupTemplate.CountryID)
self.CargoObject=_DATABASE:Spawn(self.GroupTemplate)
for CargoUnitID,CargoUnit in pairs(self.CargoObject:GetUnits())do
local CargoUnitName=CargoUnit:GetName()
local Cargo=CARGO_UNIT:New(CargoUnit,Type,CargoUnitName,LoadRadius,NearRadius)
self.CargoSet:Add(CargoUnitName,Cargo)
WeightGroup=WeightGroup+Cargo:GetWeight()
end
self:SetWeight(WeightGroup)
self:T({"Weight Cargo",WeightGroup})
_EVENTDISPATCHER:CreateEventNewCargo(self)
self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead)
self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead)
self:SetEventPriority(4)
return self
end
function CARGO_GROUP:Respawn()
self:F({"Respawning"})
for CargoID,CargoData in pairs(self.CargoSet:GetSet())do
local Cargo=CargoData
Cargo:Destroy()
Cargo:SetStartState("UnLoaded")
end
_DATABASE:Spawn(self.GroupTemplate)
for CargoUnitID,CargoUnit in pairs(self.CargoObject:GetUnits())do
local CargoUnitName=CargoUnit:GetName()
local Cargo=CARGO_UNIT:New(CargoUnit,self.Type,CargoUnitName,self.LoadRadius)
self.CargoSet:Add(CargoUnitName,Cargo)
end
self:SetDeployed(false)
self:SetStartState("UnLoaded")
end
function CARGO_GROUP:Ungroup()
if self.Grouped==true then
self.Grouped=false
self.CargoGroup:Destroy()
for CargoUnitName,CargoUnit in pairs(self.CargoSet:GetSet())do
local CargoUnit=CargoUnit
if CargoUnit:IsUnLoaded()then
local GroupTemplate=UTILS.DeepCopy(self.CargoTemplate)
GroupTemplate.name=self.CargoName.."#CARGO#"..CargoUnitName
GroupTemplate.groupId=nil
if CargoUnit:IsUnLoaded()then
GroupTemplate.units={}
GroupTemplate.units[1]=self.CargoUnitTemplate[CargoUnitName]
GroupTemplate.units[#GroupTemplate.units].unitId=nil
GroupTemplate.units[#GroupTemplate.units].x=CargoUnit:GetX()
GroupTemplate.units[#GroupTemplate.units].y=CargoUnit:GetY()
GroupTemplate.units[#GroupTemplate.units].heading=CargoUnit:GetHeading()
end
local CargoGroup=GROUP:NewTemplate(GroupTemplate,GroupTemplate.CoalitionID,GroupTemplate.CategoryID,GroupTemplate.CountryID)
_DATABASE:Spawn(GroupTemplate)
end
end
self.CargoObject=nil
end
end
function CARGO_GROUP:Regroup()
self:F("Regroup")
if self.Grouped==false then
self.Grouped=true
local GroupTemplate=UTILS.DeepCopy(self.CargoTemplate)
GroupTemplate.name=self.CargoName.."#CARGO"
GroupTemplate.groupId=nil
GroupTemplate.units={}
for CargoUnitName,CargoUnit in pairs(self.CargoSet:GetSet())do
local CargoUnit=CargoUnit
self:F({CargoUnit:GetName(),UnLoaded=CargoUnit:IsUnLoaded()})
if CargoUnit:IsUnLoaded()then
CargoUnit.CargoObject:Destroy()
GroupTemplate.units[#GroupTemplate.units+1]=self.CargoUnitTemplate[CargoUnitName]
GroupTemplate.units[#GroupTemplate.units].unitId=nil
GroupTemplate.units[#GroupTemplate.units].x=CargoUnit:GetX()
GroupTemplate.units[#GroupTemplate.units].y=CargoUnit:GetY()
GroupTemplate.units[#GroupTemplate.units].heading=CargoUnit:GetHeading()
end
end
self.CargoGroup=GROUP:NewTemplate(GroupTemplate,GroupTemplate.CoalitionID,GroupTemplate.CategoryID,GroupTemplate.CountryID)
self:F({"Regroup",GroupTemplate})
self.CargoObject=_DATABASE:Spawn(GroupTemplate)
end
end
function CARGO_GROUP:OnEventCargoDead(EventData)
self:E(EventData)
local Destroyed=false
if self:IsDestroyed()or self:IsUnLoaded()or self:IsBoarding()or self:IsUnboarding()then
Destroyed=true
for CargoID,CargoData in pairs(self.CargoSet:GetSet())do
local Cargo=CargoData
if Cargo:IsAlive()then
Destroyed=false
else
Cargo:Destroyed()
end
end
else
local CarrierName=self.CargoCarrier:GetName()
if CarrierName==EventData.IniDCSUnitName then
MESSAGE:New("Cargo is lost from carrier "..CarrierName,15):ToAll()
Destroyed=true
self.CargoCarrier:ClearCargo()
end
end
if Destroyed then
self:Destroyed()
self:E({"Cargo group destroyed"})
end
end
function CARGO_GROUP:onafterBoard(From,Event,To,CargoCarrier,NearRadius,...)
self:F({CargoCarrier.UnitName,From,Event,To,NearRadius=NearRadius})
NearRadius=NearRadius or self.NearRadius
self.CargoSet:ForEach(
function(Cargo,...)
self:F({"Board Unit",Cargo:GetName(),Cargo:IsDestroyed(),Cargo.CargoObject:IsAlive()})
local CargoGroup=Cargo.CargoObject
CargoGroup:OptionAlarmStateGreen()
Cargo:__Board(1,CargoCarrier,NearRadius,...)
end,...
)
self:__Boarding(-1,CargoCarrier,NearRadius,...)
end
function CARGO_GROUP:onafterLoad(From,Event,To,CargoCarrier,...)
if From=="UnLoaded"then
for CargoID,Cargo in pairs(self.CargoSet:GetSet())do
if not Cargo:IsDestroyed()then
Cargo:Load(CargoCarrier)
end
end
end
self.CargoCarrier=CargoCarrier
self.CargoCarrier:AddCargo(self)
end
function CARGO_GROUP:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...)
local Boarded=true
local Cancelled=false
local Dead=true
self.CargoSet:Flush()
for CargoID,Cargo in pairs(self.CargoSet:GetSet())do
if not Cargo:is("Loaded")
and(not Cargo:is("Destroyed"))then
Boarded=false
end
if Cargo:is("UnLoaded")then
Cancelled=true
end
if not Cargo:is("Destroyed")then
Dead=false
end
end
if not Dead then
if not Cancelled then
if not Boarded then
self:__Boarding(-5,CargoCarrier,NearRadius,...)
else
self:F("Group Cargo is loaded")
self:__Load(1,CargoCarrier,...)
end
else
self:__CancelBoarding(1,CargoCarrier,NearRadius,...)
end
else
self:__Destroyed(1,CargoCarrier,NearRadius,...)
end
end
function CARGO_GROUP:onafterUnBoard(From,Event,To,ToPointVec2,NearRadius,...)
self:F({From,Event,To,ToPointVec2,NearRadius})
NearRadius=NearRadius or 25
local Timer=1
if From=="Loaded"then
if self.CargoObject then
self.CargoObject:Destroy()
end
self.CargoSet:ForEach(
function(Cargo,NearRadius)
if not Cargo:IsDestroyed()then
local ToVec=nil
if ToPointVec2==nil then
ToVec=self.CargoCarrier:GetPointVec2():GetRandomPointVec2InRadius(2*NearRadius,NearRadius)
else
ToVec=ToPointVec2
end
Cargo:__UnBoard(Timer,ToVec,NearRadius)
Timer=Timer+1
end
end,{NearRadius}
)
self:__UnBoarding(1,ToPointVec2,NearRadius,...)
end
end
function CARGO_GROUP:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius,...)
local Angle=180
local Speed=10
local Distance=5
if From=="UnBoarding"then
local UnBoarded=true
for CargoID,Cargo in pairs(self.CargoSet:GetSet())do
self:T({Cargo:GetName(),Cargo.current})
if not Cargo:is("UnLoaded")and not Cargo:IsDestroyed()then
UnBoarded=false
end
end
if UnBoarded then
self:__UnLoad(1,ToPointVec2,...)
else
self:__UnBoarding(1,ToPointVec2,NearRadius,...)
end
return false
end
end
function CARGO_GROUP:onafterUnLoad(From,Event,To,ToPointVec2,...)
if From=="Loaded"then
self.CargoSet:ForEach(
function(Cargo)
local RandomVec2=nil
if ToPointVec2 then
RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20,10)
end
Cargo:UnBoard(RandomVec2)
end
)
end
self.CargoCarrier:RemoveCargo(self)
self.CargoCarrier=nil
end
function CARGO_GROUP:GetCoordinate()
local Cargo=self:GetFirstAlive()
if Cargo then
return Cargo.CargoObject:GetCoordinate()
end
return nil
end
function CARGO:GetX()
local Cargo=self:GetFirstAlive()
if Cargo then
return Cargo:GetCoordinate().x
end
return nil
end
function CARGO:GetY()
local Cargo=self:GetFirstAlive()
if Cargo then
return Cargo:GetCoordinate().z
end
return nil
end
function CARGO_GROUP:IsAlive()
local Cargo=self:GetFirstAlive()
return Cargo~=nil
end
function CARGO_GROUP:GetFirstAlive()
local CargoFirstAlive=nil
for _,Cargo in pairs(self.CargoSet:GetSet())do
if not Cargo:IsDestroyed()then
CargoFirstAlive=Cargo
break
end
end
return CargoFirstAlive
end
function CARGO_GROUP:GetCount()
return self.CargoSet:Count()
end
function CARGO_GROUP:GetGroup(Cargo)
local Cargo=Cargo or self:GetFirstAlive()
return Cargo.CargoObject:GetGroup()
end
function CARGO_GROUP:RouteTo(Coordinate)
self.CargoSet:ForEach(
function(Cargo)
Cargo.CargoObject:RouteGroundTo(Coordinate,10,"vee",0)
end
)
end
function CARGO_GROUP:IsNear(CargoCarrier,NearRadius)
self:F({NearRadius=NearRadius})
for _,Cargo in pairs(self.CargoSet:GetSet())do
local Cargo=Cargo
if Cargo:IsAlive()then
if Cargo:IsNear(CargoCarrier:GetCoordinate(),NearRadius)then
self:F("Near")
return true
end
end
end
return nil
end
function CARGO_GROUP:IsInLoadRadius(Coordinate)
local Cargo=self:GetFirstAlive()
if Cargo then
local Distance=0
local CargoCoordinate
if Cargo:IsLoaded()then
CargoCoordinate=Cargo.CargoCarrier:GetCoordinate()
else
CargoCoordinate=Cargo.CargoObject:GetCoordinate()
end
if CargoCoordinate then
Distance=Coordinate:Get2DDistance(CargoCoordinate)
else
return false
end
self:F({Distance=Distance,LoadRadius=self.LoadRadius})
if Distance<=self.LoadRadius then
return true
else
return false
end
end
return nil
end
function CARGO_GROUP:IsInReportRadius(Coordinate)
local Cargo=self:GetFirstAlive()
if Cargo then
self:F({Cargo})
local Distance=0
if Cargo:IsUnLoaded()then
Distance=Coordinate:Get2DDistance(Cargo.CargoObject:GetCoordinate())
if Distance<=self.LoadRadius then
return true
end
end
end
return nil
end
function CARGO_GROUP:Flare(FlareColor)
local Cargo=self.CargoSet:GetFirst()
if Cargo then
Cargo:Flare(FlareColor)
end
end
function CARGO_GROUP:Smoke(SmokeColor,Radius)
local Cargo=self.CargoSet:GetFirst()
if Cargo then
Cargo:Smoke(SmokeColor,Radius)
end
end
function CARGO_GROUP:IsInZone(Zone)
local Cargo=self.CargoSet:GetFirst()
if Cargo then
return Cargo:IsInZone(Zone)
end
return nil
end
function CARGO_GROUP:GetTransportationMethod()
if self:IsLoaded()then
return"for unboarding"
else
if self:IsUnLoaded()then
return"for boarding"
else
if self:IsDeployed()then
return"delivered"
end
end
end
return""
end
end
SCORING={
ClassName="SCORING",
ClassID=0,
Players={},
AutoSave=true,
}
local _SCORINGCoalition={
[1]="Red",
[2]="Blue",
}
local _SCORINGCategory={
[Unit.Category.AIRPLANE]="Plane",
[Unit.Category.HELICOPTER]="Helicopter",
[Unit.Category.GROUND_UNIT]="Vehicle",
[Unit.Category.SHIP]="Ship",
[Unit.Category.STRUCTURE]="Structure",
}
function SCORING:New(GameName)
local self=BASE:Inherit(self,BASE:New())
if GameName then
self.GameName=GameName
else
error("A game name must be given to register the scoring results")
end
self.ScoringObjects={}
self.ScoringZones={}
self:SetMessagesToAll()
self:SetMessagesHit(false)
self:SetMessagesDestroy(true)
self:SetMessagesScore(true)
self:SetMessagesZone(true)
self:SetScaleDestroyScore(10)
self:SetScaleDestroyPenalty(30)
self:SetFratricide(self.ScaleDestroyPenalty*3)
self.penaltyonfratricide=true
self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty)
self.penaltyoncoalitionchange=true
self:SetDisplayMessagePrefix()
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Hit,self._EventOnHit)
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.PlayerLeaveUnit)
self.ScoringPlayerScan=BASE:ScheduleOnce(1,function()
for PlayerName,PlayerUnit in pairs(_DATABASE:GetPlayerUnits())do
self:_AddPlayerFromUnit(PlayerUnit)
self:SetScoringMenu(PlayerUnit:GetGroup())
end
end)
self.AutoSave=true
self:OpenCSV(GameName)
return self
end
function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix)
self.DisplayMessagePrefix=DisplayMessagePrefix or""
return self
end
function SCORING:SetScaleDestroyScore(Scale)
self.ScaleDestroyScore=Scale
return self
end
function SCORING:SetScaleDestroyPenalty(Scale)
self.ScaleDestroyPenalty=Scale
return self
end
function SCORING:AddUnitScore(ScoreUnit,Score)
local UnitName=ScoreUnit:GetName()
self.ScoringObjects[UnitName]=Score
return self
end
function SCORING:RemoveUnitScore(ScoreUnit)
local UnitName=ScoreUnit:GetName()
self.ScoringObjects[UnitName]=nil
return self
end
function SCORING:AddStaticScore(ScoreStatic,Score)
local StaticName=ScoreStatic:GetName()
self.ScoringObjects[StaticName]=Score
return self
end
function SCORING:RemoveStaticScore(ScoreStatic)
local StaticName=ScoreStatic:GetName()
self.ScoringObjects[StaticName]=nil
return self
end
function SCORING:AddScoreGroup(ScoreGroup,Score)
local ScoreUnits=ScoreGroup:GetUnits()
for ScoreUnitID,ScoreUnit in pairs(ScoreUnits)do
local UnitName=ScoreUnit:GetName()
self.ScoringObjects[UnitName]=Score
end
return self
end
function SCORING:AddZoneScore(ScoreZone,Score)
local ZoneName=ScoreZone:GetName()
self.ScoringZones[ZoneName]={}
self.ScoringZones[ZoneName].ScoreZone=ScoreZone
self.ScoringZones[ZoneName].Score=Score
return self
end
function SCORING:RemoveZoneScore(ScoreZone)
local ZoneName=ScoreZone:GetName()
self.ScoringZones[ZoneName]=nil
return self
end
function SCORING:SetMessagesHit(OnOff)
self.MessagesHit=OnOff
return self
end
function SCORING:IfMessagesHit()
return self.MessagesHit
end
function SCORING:SetMessagesDestroy(OnOff)
self.MessagesDestroy=OnOff
return self
end
function SCORING:IfMessagesDestroy()
return self.MessagesDestroy
end
function SCORING:SetMessagesScore(OnOff)
self.MessagesScore=OnOff
return self
end
function SCORING:IfMessagesScore()
return self.MessagesScore
end
function SCORING:SetMessagesZone(OnOff)
self.MessagesZone=OnOff
return self
end
function SCORING:IfMessagesZone()
return self.MessagesZone
end
function SCORING:SetMessagesToAll()
self.MessagesAudience=1
return self
end
function SCORING:IfMessagesToAll()
return self.MessagesAudience==1
end
function SCORING:SetMessagesToCoalition()
self.MessagesAudience=2
return self
end
function SCORING:IfMessagesToCoalition()
return self.MessagesAudience==2
end
function SCORING:SetFratricide(Fratricide)
self.Fratricide=Fratricide
return self
end
function SCORING:SwitchFratricide(OnOff)
self.penaltyonfratricide=OnOff
return self
end
function SCORING:SwitchTreason(OnOff)
self.penaltyoncoalitionchange=OnOff
return self
end
function SCORING:SetCoalitionChangePenalty(CoalitionChangePenalty)
self.CoalitionChangePenalty=CoalitionChangePenalty
return self
end
function SCORING:SetScoringMenu(ScoringGroup)
local Menu=MENU_GROUP:New(ScoringGroup,'Scoring and Statistics')
local ReportGroupSummary=MENU_GROUP_COMMAND:New(ScoringGroup,'Summary report players in group',Menu,SCORING.ReportScoreGroupSummary,self,ScoringGroup)
local ReportGroupDetailed=MENU_GROUP_COMMAND:New(ScoringGroup,'Detailed report players in group',Menu,SCORING.ReportScoreGroupDetailed,self,ScoringGroup)
local ReportToAllSummary=MENU_GROUP_COMMAND:New(ScoringGroup,'Summary report all players',Menu,SCORING.ReportScoreAllSummary,self,ScoringGroup)
self:SetState(ScoringGroup,"ScoringMenu",Menu)
return self
end
function SCORING:_AddPlayerFromUnit(UnitData)
self:F(UnitData)
if UnitData:IsAlive()then
local UnitName=UnitData:GetName()
local PlayerName=UnitData:GetPlayerName()
local UnitDesc=UnitData:GetDesc()
local UnitCategory=UnitDesc.category
local UnitCoalition=UnitData:GetCoalition()
local UnitTypeName=UnitData:GetTypeName()
local UnitThreatLevel,UnitThreatType=UnitData:GetThreatLevel()
self:T({PlayerName,UnitName,UnitCategory,UnitCoalition,UnitTypeName})
if self.Players[PlayerName]==nil then
self.Players[PlayerName]={}
self.Players[PlayerName].Hit={}
self.Players[PlayerName].Destroy={}
self.Players[PlayerName].Goals={}
self.Players[PlayerName].Mission={}
self.Players[PlayerName].HitPlayers={}
self.Players[PlayerName].Score=0
self.Players[PlayerName].Penalty=0
self.Players[PlayerName].PenaltyCoalition=0
self.Players[PlayerName].PenaltyWarning=0
end
if not self.Players[PlayerName].UnitCoalition then
self.Players[PlayerName].UnitCoalition=UnitCoalition
else
if self.Players[PlayerName].UnitCoalition~=UnitCoalition and self.penaltyoncoalitionchange then
self.Players[PlayerName].Penalty=self.Players[PlayerName].Penalty+self.CoalitionChangePenalty or 50
self.Players[PlayerName].PenaltyCoalition=self.Players[PlayerName].PenaltyCoalition+1
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' changed coalition from ".._SCORINGCoalition[self.Players[PlayerName].UnitCoalition].." to ".._SCORINGCoalition[UnitCoalition]..
"(changed "..self.Players[PlayerName].PenaltyCoalition.." times the coalition). "..self.CoalitionChangePenalty.."Penalty points added.",
MESSAGE.Type.Information
):ToAll()
self:ScoreCSV(PlayerName,"","COALITION_PENALTY",1,-1*self.CoalitionChangePenalty,self.Players[PlayerName].UnitName,_SCORINGCoalition[self.Players[PlayerName].UnitCoalition],_SCORINGCategory[self.Players[PlayerName].UnitCategory],self.Players[PlayerName].UnitType,
UnitName,_SCORINGCoalition[UnitCoalition],_SCORINGCategory[UnitCategory],UnitData:GetTypeName())
end
end
self.Players[PlayerName].UnitName=UnitName
self.Players[PlayerName].UnitCoalition=UnitCoalition
self.Players[PlayerName].UnitCategory=UnitCategory
self.Players[PlayerName].UnitType=UnitTypeName
self.Players[PlayerName].UNIT=UnitData
self.Players[PlayerName].ThreatLevel=UnitThreatLevel
self.Players[PlayerName].ThreatType=UnitThreatType
if self.Players[PlayerName].Penalty>self.Fratricide*0.50 and self.penaltyonfratricide then
if self.Players[PlayerName].PenaltyWarning<1 then
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than "..self.Fratricide..", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: "..self.Players[PlayerName].Penalty,
MESSAGE.Type.Information)
:ToAll()
self.Players[PlayerName].PenaltyWarning=self.Players[PlayerName].PenaltyWarning+1
end
end
if self.Players[PlayerName].Penalty>self.Fratricide and self.penaltyonfratricide then
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!",
MESSAGE.Type.Information)
:ToAll()
UnitData:GetGroup():Destroy()
end
end
end
function SCORING:AddGoalScorePlayer(PlayerName,GoalTag,Text,Score)
self:F({PlayerName,PlayerName,GoalTag,Text,Score})
if PlayerName then
local PlayerData=self.Players[PlayerName]
PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0}
PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score
PlayerData.Score=PlayerData.Score+Score
MESSAGE:NewType(self.DisplayMessagePrefix..Text,
MESSAGE.Type.Information)
:ToAll()
self:ScoreCSV(PlayerName,"","GOAL_"..string.upper(GoalTag),1,Score,nil)
end
end
function SCORING:AddGoalScore(PlayerUnit,GoalTag,Text,Score)
local PlayerName=PlayerUnit:GetPlayerName()
self:F({PlayerUnit.UnitName,PlayerName,GoalTag,Text,Score})
if PlayerName then
local PlayerData=self.Players[PlayerName]
PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0}
PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score
PlayerData.Score=PlayerData.Score+Score
MESSAGE:NewType(self.DisplayMessagePrefix..Text,
MESSAGE.Type.Information)
:ToAll()
self:ScoreCSV(PlayerName,"","GOAL_"..string.upper(GoalTag),1,Score,PlayerUnit:GetName())
end
end
function SCORING:_AddMissionTaskScore(Mission,PlayerUnit,Text,Score)
local PlayerName=PlayerUnit:GetPlayerName()
local MissionName=Mission:GetName()
self:F({Mission:GetName(),PlayerUnit.UnitName,PlayerName,Text,Score})
if PlayerName then
local PlayerData=self.Players[PlayerName]
if not PlayerData.Mission[MissionName]then
PlayerData.Mission[MissionName]={}
PlayerData.Mission[MissionName].ScoreTask=0
PlayerData.Mission[MissionName].ScoreMission=0
end
self:T(PlayerName)
self:T(PlayerData.Mission[MissionName])
PlayerData.Score=self.Players[PlayerName].Score+Score
PlayerData.Mission[MissionName].ScoreTask=self.Players[PlayerName].Mission[MissionName].ScoreTask+Score
MESSAGE:NewType(self.DisplayMessagePrefix..Mission:GetText().." : "..Text.." Score: "..Score,
MESSAGE.Type.Information)
:ToAll()
self:ScoreCSV(PlayerName,"","TASK_"..MissionName:gsub(' ','_'),1,Score,PlayerUnit:GetName())
end
end
function SCORING:_AddMissionGoalScore(Mission,PlayerName,Text,Score)
local MissionName=Mission:GetName()
self:F({Mission:GetName(),PlayerName,Text,Score})
if PlayerName then
local PlayerData=self.Players[PlayerName]
if not PlayerData.Mission[MissionName]then
PlayerData.Mission[MissionName]={}
PlayerData.Mission[MissionName].ScoreTask=0
PlayerData.Mission[MissionName].ScoreMission=0
end
self:T(PlayerName)
self:T(PlayerData.Mission[MissionName])
PlayerData.Score=self.Players[PlayerName].Score+Score
PlayerData.Mission[MissionName].ScoreTask=self.Players[PlayerName].Mission[MissionName].ScoreTask+Score
MESSAGE:NewType(string.format("%s%s: %s! Player %s receives %d score!",self.DisplayMessagePrefix,Mission:GetText(),Text,PlayerName,Score),MESSAGE.Type.Information):ToAll()
self:ScoreCSV(PlayerName,"","TASK_"..MissionName:gsub(' ','_'),1,Score)
end
end
function SCORING:_AddMissionScore(Mission,Text,Score)
local MissionName=Mission:GetName()
self:F({Mission,Text,Score})
self:F(self.Players)
for PlayerName,PlayerData in pairs(self.Players)do
self:F(PlayerData)
if PlayerData.Mission[MissionName]then
PlayerData.Score=PlayerData.Score+Score
PlayerData.Mission[MissionName].ScoreMission=PlayerData.Mission[MissionName].ScoreMission+Score
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' has "..Text.." in "..Mission:GetText()..". "..Score.." mission score!",
MESSAGE.Type.Information)
:ToAll()
self:ScoreCSV(PlayerName,"","MISSION_"..MissionName:gsub(' ','_'),1,Score)
end
end
end
function SCORING:OnEventBirth(Event)
if Event.IniUnit then
if Event.IniObjectCategory==1 then
local PlayerName=Event.IniUnit:GetPlayerName()
if PlayerName then
self:_AddPlayerFromUnit(Event.IniUnit)
self:SetScoringMenu(Event.IniGroup)
end
end
end
end
function SCORING:OnEventPlayerLeaveUnit(Event)
if Event.IniUnit then
local Menu=self:GetState(Event.IniUnit:GetGroup(),"ScoringMenu")
if Menu then
end
end
end
function SCORING:_EventOnHit(Event)
self:F({Event})
local InitUnit=nil
local InitUNIT=nil
local InitUnitName=""
local InitGroup=nil
local InitGroupName=""
local InitPlayerName=nil
local InitCoalition=nil
local InitCategory=nil
local InitType=nil
local InitUnitCoalition=nil
local InitUnitCategory=nil
local InitUnitType=nil
local TargetUnit=nil
local TargetUNIT=nil
local TargetUnitName=""
local TargetGroup=nil
local TargetGroupName=""
local TargetPlayerName=nil
local TargetCoalition=nil
local TargetCategory=nil
local TargetType=nil
local TargetUnitCoalition=nil
local TargetUnitCategory=nil
local TargetUnitType=nil
if Event.IniDCSUnit then
InitUnit=Event.IniDCSUnit
InitUNIT=Event.IniUnit
InitUnitName=Event.IniDCSUnitName
InitGroup=Event.IniDCSGroup
InitGroupName=Event.IniDCSGroupName
InitPlayerName=Event.IniPlayerName
InitCoalition=Event.IniCoalition
InitCategory=Event.IniCategory
InitType=Event.IniTypeName
InitUnitCoalition=_SCORINGCoalition[InitCoalition]
InitUnitCategory=_SCORINGCategory[InitCategory]
InitUnitType=InitType
self:T({InitUnitName,InitGroupName,InitPlayerName,InitCoalition,InitCategory,InitType,InitUnitCoalition,InitUnitCategory,InitUnitType})
end
if Event.TgtDCSUnit then
TargetUnit=Event.TgtDCSUnit
TargetUNIT=Event.TgtUnit
TargetUnitName=Event.TgtDCSUnitName
TargetGroup=Event.TgtDCSGroup
TargetGroupName=Event.TgtDCSGroupName
TargetPlayerName=Event.TgtPlayerName
TargetCoalition=Event.TgtCoalition
TargetCategory=Event.TgtCategory
TargetType=Event.TgtTypeName
TargetUnitCoalition=_SCORINGCoalition[TargetCoalition]
TargetUnitCategory=_SCORINGCategory[TargetCategory]
TargetUnitType=TargetType
self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType,TargetUnitCoalition,TargetUnitCategory,TargetUnitType})
end
if InitPlayerName~=nil then
self:_AddPlayerFromUnit(InitUNIT)
if self.Players[InitPlayerName]then
if TargetPlayerName~=nil then
self:_AddPlayerFromUnit(TargetUNIT)
end
self:T("Hitting Something")
if TargetCategory then
local Player=self.Players[InitPlayerName]
Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{}
Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{}
local PlayerHit=Player.Hit[TargetCategory][TargetUnitName]
PlayerHit.Score=PlayerHit.Score or 0
PlayerHit.Penalty=PlayerHit.Penalty or 0
PlayerHit.ScoreHit=PlayerHit.ScoreHit or 0
PlayerHit.PenaltyHit=PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp=PlayerHit.TimeStamp or 0
PlayerHit.UNIT=PlayerHit.UNIT or TargetUNIT
PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel()
if timer.getTime()-PlayerHit.TimeStamp>1 then
PlayerHit.TimeStamp=timer.getTime()
if TargetPlayerName~=nil then
Player.HitPlayers[TargetPlayerName]=true
end
local Score=0
if InitCoalition then
if InitCoalition==TargetCoalition then
Player.Penalty=Player.Penalty+10
PlayerHit.Penalty=PlayerHit.Penalty+10
PlayerHit.PenaltyHit=PlayerHit.PenaltyHit+1
if TargetPlayerName~=nil then
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit friendly player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.PenaltyHit.." times. "..
"Penalty: -"..PlayerHit.Penalty..".  Score Total:"..Player.Score-Player.Penalty,
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
else
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit friendly target "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.PenaltyHit.." times. "..
"Penalty: -"..PlayerHit.Penalty..".  Score Total:"..Player.Score-Player.Penalty,
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
end
self:ScoreCSV(InitPlayerName,TargetPlayerName,"HIT_PENALTY",1,-10,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
else
Player.Score=Player.Score+1
PlayerHit.Score=PlayerHit.Score+1
PlayerHit.ScoreHit=PlayerHit.ScoreHit+1
if TargetPlayerName~=nil then
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit enemy player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.ScoreHit.." times. "..
"Score: "..PlayerHit.Score..".  Score Total:"..Player.Score-Player.Penalty,
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
else
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit enemy target "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.ScoreHit.." times. "..
"Score: "..PlayerHit.Score..".  Score Total:"..Player.Score-Player.Penalty,
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
end
self:ScoreCSV(InitPlayerName,TargetPlayerName,"HIT_SCORE",1,1,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
end
else
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit scenery object.",
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
self:ScoreCSV(InitPlayerName,"","HIT_SCORE",1,0,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Scenery",TargetUnitType)
end
end
end
end
elseif InitPlayerName==nil then
end
if Event.WeaponPlayerName~=nil then
self:_AddPlayerFromUnit(Event.WeaponUNIT)
if self.Players[Event.WeaponPlayerName]then
if TargetPlayerName~=nil then
self:_AddPlayerFromUnit(TargetUNIT)
end
self:T("Hitting Scenery")
if TargetCategory then
local Player=self.Players[Event.WeaponPlayerName]
Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{}
Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{}
local PlayerHit=Player.Hit[TargetCategory][TargetUnitName]
PlayerHit.Score=PlayerHit.Score or 0
PlayerHit.Penalty=PlayerHit.Penalty or 0
PlayerHit.ScoreHit=PlayerHit.ScoreHit or 0
PlayerHit.PenaltyHit=PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp=PlayerHit.TimeStamp or 0
PlayerHit.UNIT=PlayerHit.UNIT or TargetUNIT
PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel()
if timer.getTime()-PlayerHit.TimeStamp>1 then
PlayerHit.TimeStamp=timer.getTime()
local Score=0
if InitCoalition then
if InitCoalition==TargetCoalition then
Player.Penalty=Player.Penalty+10
PlayerHit.Penalty=PlayerHit.Penalty+10
PlayerHit.PenaltyHit=PlayerHit.PenaltyHit+1*self.ScaleDestroyPenalty
MESSAGE
:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit friendly target "..
TargetUnitCategory.." ( "..TargetType.." ) "..
"Penalty: -"..PlayerHit.Penalty.." = "..Player.Score-Player.Penalty,
MESSAGE.Type.Update
)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
self:ScoreCSV(Event.WeaponPlayerName,TargetPlayerName,"HIT_PENALTY",1,-10,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
else
Player.Score=Player.Score+1
PlayerHit.Score=PlayerHit.Score+1
PlayerHit.ScoreHit=PlayerHit.ScoreHit+1
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit enemy target "..TargetUnitCategory.." ( "..TargetType.." ) "..
"Score: +"..PlayerHit.Score.." = "..Player.Score-Player.Penalty,
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
self:ScoreCSV(Event.WeaponPlayerName,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
end
else
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit scenery object.",
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType)
end
end
end
end
end
end
function SCORING:_EventOnDeadOrCrash(Event)
self:F({Event})
local TargetUnit=nil
local TargetGroup=nil
local TargetUnitName=""
local TargetGroupName=""
local TargetPlayerName=""
local TargetCoalition=nil
local TargetCategory=nil
local TargetType=nil
local TargetUnitCoalition=nil
local TargetUnitCategory=nil
local TargetUnitType=nil
if Event.IniDCSUnit then
TargetUnit=Event.IniUnit
TargetUnitName=Event.IniDCSUnitName
TargetGroup=Event.IniDCSGroup
TargetGroupName=Event.IniDCSGroupName
TargetPlayerName=Event.IniPlayerName
TargetCoalition=Event.IniCoalition
TargetCategory=Event.IniCategory
TargetType=Event.IniTypeName
TargetUnitCoalition=_SCORINGCoalition[TargetCoalition]
TargetUnitCategory=_SCORINGCategory[TargetCategory]
TargetUnitType=TargetType
self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType})
end
for PlayerName,Player in pairs(self.Players)do
if Player then
self:T("Something got destroyed")
local InitUnitName=Player.UnitName
local InitUnitType=Player.UnitType
local InitCoalition=Player.UnitCoalition
local InitCategory=Player.UnitCategory
local InitUnitCoalition=_SCORINGCoalition[InitCoalition]
local InitUnitCategory=_SCORINGCategory[InitCategory]
self:T({InitUnitName,InitUnitType,InitUnitCoalition,InitCoalition,InitUnitCategory,InitCategory})
local Destroyed=false
if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 then
local TargetThreatLevel=Player.Hit[TargetCategory][TargetUnitName].ThreatLevel
local TargetThreatType=Player.Hit[TargetCategory][TargetUnitName].ThreatType
Player.Destroy[TargetCategory]=Player.Destroy[TargetCategory]or{}
Player.Destroy[TargetCategory][TargetType]=Player.Destroy[TargetCategory][TargetType]or{}
local TargetDestroy=Player.Destroy[TargetCategory][TargetType]
TargetDestroy.Score=TargetDestroy.Score or 0
TargetDestroy.ScoreDestroy=TargetDestroy.ScoreDestroy or 0
TargetDestroy.Penalty=TargetDestroy.Penalty or 0
TargetDestroy.PenaltyDestroy=TargetDestroy.PenaltyDestroy or 0
if TargetCoalition then
if InitCoalition==TargetCoalition then
local ThreatLevelTarget=TargetThreatLevel
local ThreatTypeTarget=TargetThreatType
local ThreatLevelPlayer=Player.ThreatLevel/10+1
local ThreatPenalty=math.ceil((ThreatLevelTarget/ThreatLevelPlayer)*self.ScaleDestroyPenalty/10)
self:F({ThreatLevel=ThreatPenalty,ThreatLevelTarget=ThreatLevelTarget,ThreatTypeTarget=ThreatTypeTarget,ThreatLevelPlayer=ThreatLevelPlayer})
Player.Penalty=Player.Penalty+ThreatPenalty
TargetDestroy.Penalty=TargetDestroy.Penalty+ThreatPenalty
TargetDestroy.PenaltyDestroy=TargetDestroy.PenaltyDestroy+1
if Player.HitPlayers[TargetPlayerName]then
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed friendly player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) "..
"Penalty: -"..TargetDestroy.Penalty.." = "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition())
else
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed friendly target "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) "..
"Penalty: -"..TargetDestroy.Penalty.." = "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition())
end
self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_PENALTY",1,ThreatPenalty,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
Destroyed=true
else
local ThreatLevelTarget=TargetThreatLevel
local ThreatTypeTarget=TargetThreatType
local ThreatLevelPlayer=Player.ThreatLevel/10+1
local ThreatScore=math.ceil((ThreatLevelTarget/ThreatLevelPlayer)*self.ScaleDestroyScore/10)
self:F({ThreatLevel=ThreatScore,ThreatLevelTarget=ThreatLevelTarget,ThreatTypeTarget=ThreatTypeTarget,ThreatLevelPlayer=ThreatLevelPlayer})
Player.Score=Player.Score+ThreatScore
TargetDestroy.Score=TargetDestroy.Score+ThreatScore
TargetDestroy.ScoreDestroy=TargetDestroy.ScoreDestroy+1
if Player.HitPlayers[TargetPlayerName]then
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed enemy player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) "..
"Score: +"..TargetDestroy.Score.." = "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition())
else
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed enemy "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) "..
"Score: +"..TargetDestroy.Score.." = "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition())
end
self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,ThreatScore,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
Destroyed=true
local UnitName=TargetUnit:GetName()
local Score=self.ScoringObjects[UnitName]
if Score then
Player.Score=Player.Score+Score
TargetDestroy.Score=TargetDestroy.Score+Score
MESSAGE:NewType(self.DisplayMessagePrefix.."Special target '"..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".." destroyed! "..
"Player '"..PlayerName.."' receives an extra "..Score.." points! Total: "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesScore()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesScore()and self:IfMessagesToCoalition())
self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
Destroyed=true
end
for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do
self:F({ScoringZone=ScoreZoneData})
local ScoreZone=ScoreZoneData.ScoreZone
local Score=ScoreZoneData.Score
if ScoreZone:IsVec2InZone(TargetUnit:GetVec2())then
Player.Score=Player.Score+Score
TargetDestroy.Score=TargetDestroy.Score+Score
MESSAGE:NewType(self.DisplayMessagePrefix.."Target destroyed in zone '"..ScoreZone:GetName().."'."..
"Player '"..PlayerName.."' receives an extra "..Score.." points! ".."Total: "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesZone()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesZone()and self:IfMessagesToCoalition())
self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
Destroyed=true
end
end
end
else
for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do
self:F({ScoringZone=ScoreZoneData})
local ScoreZone=ScoreZoneData.ScoreZone
local Score=ScoreZoneData.Score
if ScoreZone:IsVec2InZone(TargetUnit:GetVec2())then
Player.Score=Player.Score+Score
TargetDestroy.Score=TargetDestroy.Score+Score
MESSAGE:NewType(self.DisplayMessagePrefix.."Scenery destroyed in zone '"..ScoreZone:GetName().."'."..
"Player '"..PlayerName.."' receives an extra "..Score.." points! ".."Total: "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesZone()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesZone()and self:IfMessagesToCoalition())
self:ScoreCSV(PlayerName,"","DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Scenery",TargetUnitType)
Destroyed=true
end
end
end
if Destroyed then
Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0
end
end
end
end
end
function SCORING:ReportDetailedPlayerHits(PlayerName)
local ScoreMessage=""
local PlayerScore=0
local PlayerPenalty=0
local PlayerData=self.Players[PlayerName]
if PlayerData then
self:T("Score Player: "..PlayerName)
local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType=PlayerData.UnitType
local InitUnitName=PlayerData.UnitName
local ScoreMessageHits=""
for CategoryID,CategoryName in pairs(_SCORINGCategory)do
self:T(CategoryName)
if PlayerData.Hit[CategoryID]then
self:T("Hit scores exist for player "..PlayerName)
local Score=0
local ScoreHit=0
local Penalty=0
local PenaltyHit=0
for UnitName,UnitData in pairs(PlayerData.Hit[CategoryID])do
Score=Score+UnitData.Score
ScoreHit=ScoreHit+UnitData.ScoreHit
Penalty=Penalty+UnitData.Penalty
PenaltyHit=UnitData.PenaltyHit
end
local ScoreMessageHit=string.format("%s:%d  ",CategoryName,Score-Penalty)
self:T(ScoreMessageHit)
ScoreMessageHits=ScoreMessageHits..ScoreMessageHit
PlayerScore=PlayerScore+Score
PlayerPenalty=PlayerPenalty+Penalty
else
end
end
if ScoreMessageHits~=""then
ScoreMessage="Hits: "..ScoreMessageHits
end
end
return ScoreMessage,PlayerScore,PlayerPenalty
end
function SCORING:ReportDetailedPlayerDestroys(PlayerName)
local ScoreMessage=""
local PlayerScore=0
local PlayerPenalty=0
local PlayerData=self.Players[PlayerName]
if PlayerData then
self:T("Score Player: "..PlayerName)
local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType=PlayerData.UnitType
local InitUnitName=PlayerData.UnitName
local ScoreMessageDestroys=""
for CategoryID,CategoryName in pairs(_SCORINGCategory)do
if PlayerData.Destroy[CategoryID]then
self:T("Destroy scores exist for player "..PlayerName)
local Score=0
local ScoreDestroy=0
local Penalty=0
local PenaltyDestroy=0
for UnitName,UnitData in pairs(PlayerData.Destroy[CategoryID])do
self:F({UnitData=UnitData})
if UnitData~={}then
Score=Score+UnitData.Score
ScoreDestroy=ScoreDestroy+UnitData.ScoreDestroy
Penalty=Penalty+UnitData.Penalty
PenaltyDestroy=PenaltyDestroy+UnitData.PenaltyDestroy
end
end
local ScoreMessageDestroy=string.format("  %s:%d  ",CategoryName,Score-Penalty)
self:T(ScoreMessageDestroy)
ScoreMessageDestroys=ScoreMessageDestroys..ScoreMessageDestroy
PlayerScore=PlayerScore+Score
PlayerPenalty=PlayerPenalty+Penalty
else
end
end
if ScoreMessageDestroys~=""then
ScoreMessage="Destroys: "..ScoreMessageDestroys
end
end
return ScoreMessage,PlayerScore,PlayerPenalty
end
function SCORING:ReportDetailedPlayerCoalitionChanges(PlayerName)
local ScoreMessage=""
local PlayerScore=0
local PlayerPenalty=0
local PlayerData=self.Players[PlayerName]
if PlayerData then
self:T("Score Player: "..PlayerName)
local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType=PlayerData.UnitType
local InitUnitName=PlayerData.UnitName
local ScoreMessageCoalitionChangePenalties=""
if PlayerData.PenaltyCoalition~=0 then
ScoreMessageCoalitionChangePenalties=ScoreMessageCoalitionChangePenalties..string.format(" -%d (%d changed)",PlayerData.Penalty,PlayerData.PenaltyCoalition)
PlayerPenalty=PlayerPenalty+PlayerData.Penalty
end
if ScoreMessageCoalitionChangePenalties~=""then
ScoreMessage=ScoreMessage.."Coalition Penalties: "..ScoreMessageCoalitionChangePenalties
end
end
return ScoreMessage,PlayerScore,PlayerPenalty
end
function SCORING:ReportDetailedPlayerGoals(PlayerName)
local ScoreMessage=""
local PlayerScore=0
local PlayerPenalty=0
local PlayerData=self.Players[PlayerName]
if PlayerData then
self:T("Score Player: "..PlayerName)
local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType=PlayerData.UnitType
local InitUnitName=PlayerData.UnitName
local ScoreMessageGoal=""
local ScoreGoal=0
local ScoreTask=0
for GoalName,GoalData in pairs(PlayerData.Goals)do
ScoreGoal=ScoreGoal+GoalData.Score
ScoreMessageGoal=ScoreMessageGoal.."'"..GoalName.."':"..GoalData.Score.."; "
end
PlayerScore=PlayerScore+ScoreGoal
if ScoreMessageGoal~=""then
ScoreMessage="Goals: "..ScoreMessageGoal
end
end
return ScoreMessage,PlayerScore,PlayerPenalty
end
function SCORING:ReportDetailedPlayerMissions(PlayerName)
local ScoreMessage=""
local PlayerScore=0
local PlayerPenalty=0
local PlayerData=self.Players[PlayerName]
if PlayerData then
self:T("Score Player: "..PlayerName)
local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType=PlayerData.UnitType
local InitUnitName=PlayerData.UnitName
local ScoreMessageMission=""
local ScoreMission=0
local ScoreTask=0
for MissionName,MissionData in pairs(PlayerData.Mission)do
ScoreMission=ScoreMission+MissionData.ScoreMission
ScoreTask=ScoreTask+MissionData.ScoreTask
ScoreMessageMission=ScoreMessageMission.."'"..MissionName.."'; "
end
PlayerScore=PlayerScore+ScoreMission+ScoreTask
if ScoreMessageMission~=""then
ScoreMessage="Tasks: "..ScoreTask.." Mission: "..ScoreMission.." ( "..ScoreMessageMission..")"
end
end
return ScoreMessage,PlayerScore,PlayerPenalty
end
function SCORING:ReportScoreGroupSummary(PlayerGroup)
local PlayerMessage=""
self:T("Report Score Group Summary")
local PlayerUnits=PlayerGroup:GetUnits()
for UnitID,PlayerUnit in pairs(PlayerUnits)do
local PlayerUnit=PlayerUnit
local PlayerName=PlayerUnit:GetPlayerName()
if PlayerName then
local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName)
ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits
self:F({ReportHits,ScoreHits,PenaltyHits})
local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName)
ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys
self:F({ReportDestroys,ScoreDestroys,PenaltyDestroys})
local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName)
ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges
self:F({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges})
local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName)
ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals
self:F({ReportGoals,ScoreGoals,PenaltyGoals})
local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName)
ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions
self:F({ReportMissions,ScoreMissions,PenaltyMissions})
local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions
local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+PenaltyGoals+PenaltyMissions
PlayerMessage=string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )",
PlayerName,
PlayerScore-PlayerPenalty,
PlayerScore,
PlayerPenalty
)
MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup)
end
end
end
function SCORING:ReportScoreGroupDetailed(PlayerGroup)
local PlayerMessage=""
self:T("Report Score Group Detailed")
local PlayerUnits=PlayerGroup:GetUnits()
for UnitID,PlayerUnit in pairs(PlayerUnits)do
local PlayerUnit=PlayerUnit
local PlayerName=PlayerUnit:GetPlayerName()
if PlayerName then
local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName)
ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits
self:F({ReportHits,ScoreHits,PenaltyHits})
local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName)
ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys
self:F({ReportDestroys,ScoreDestroys,PenaltyDestroys})
local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName)
ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges
self:F({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges})
local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName)
ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals
self:F({ReportGoals,ScoreGoals,PenaltyGoals})
local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName)
ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions
self:F({ReportMissions,ScoreMissions,PenaltyMissions})
local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions
local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+PenaltyGoals+PenaltyMissions
PlayerMessage=
string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s",
PlayerName,
PlayerScore-PlayerPenalty,
PlayerScore,
PlayerPenalty,
ReportHits,
ReportDestroys,
ReportCoalitionChanges,
ReportGoals,
ReportMissions
)
MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup)
end
end
end
function SCORING:ReportScoreAllSummary(PlayerGroup)
local PlayerMessage=""
self:T({"Summary Score Report of All Players",Players=self.Players})
for PlayerName,PlayerData in pairs(self.Players)do
self:T({PlayerName=PlayerName,PlayerGroup=PlayerGroup})
if PlayerName then
local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName)
ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits
self:F({ReportHits,ScoreHits,PenaltyHits})
local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName)
ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys
self:F({ReportDestroys,ScoreDestroys,PenaltyDestroys})
local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName)
ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges
self:F({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges})
local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName)
ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals
self:F({ReportGoals,ScoreGoals,PenaltyGoals})
local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName)
ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions
self:F({ReportMissions,ScoreMissions,PenaltyMissions})
local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions
local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+PenaltyGoals+PenaltyMissions
PlayerMessage=
string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )",
PlayerName,
PlayerScore-PlayerPenalty,
PlayerScore,
PlayerPenalty
)
MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup)
end
end
end
function SCORING:SecondsToClock(sSeconds)
local nSeconds=sSeconds
if nSeconds==0 then
return"00:00:00";
else
nHours=string.format("%02.f",math.floor(nSeconds/3600));
nMins=string.format("%02.f",math.floor(nSeconds/60-(nHours*60)));
nSecs=string.format("%02.f",math.floor(nSeconds-nHours*3600-nMins*60));
return nHours..":"..nMins..":"..nSecs
end
end
function SCORING:OpenCSV(ScoringCSV)
self:F(ScoringCSV)
if lfs and io and os and self.AutoSave then
if ScoringCSV then
self.ScoringCSV=ScoringCSV
local fdir=lfs.writedir()..[[Logs\]]..self.ScoringCSV.." "..os.date("%Y-%m-%d %H-%M-%S")..".csv"
self.CSVFile,self.err=io.open(fdir,"w+")
if not self.CSVFile then
error("Error: Cannot open CSV file in "..lfs.writedir())
end
self.CSVFile:write('"GameName","RunTime","Time","PlayerName","TargetPlayerName","ScoreType","PlayerUnitCoalition","PlayerUnitCategory","PlayerUnitType","PlayerUnitName","TargetUnitCoalition","TargetUnitCategory","TargetUnitType","TargetUnitName","Times","Score"\n')
self.RunTime=os.date("%y-%m-%d_%H-%M-%S")
else
error("A string containing the CSV file name must be given.")
end
else
self:F("The MissionScripting.lua file has not been changed to allow lfs, io and os modules to be used...")
end
return self
end
function SCORING:ScoreCSV(PlayerName,TargetPlayerName,ScoreType,ScoreTimes,ScoreAmount,PlayerUnitName,PlayerUnitCoalition,PlayerUnitCategory,PlayerUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
local ScoreTime=self:SecondsToClock(timer.getTime())
PlayerName=PlayerName:gsub('"','_')
TargetPlayerName=TargetPlayerName or""
TargetPlayerName=TargetPlayerName:gsub('"','_')
if PlayerUnitName and PlayerUnitName~=''then
local PlayerUnit=Unit.getByName(PlayerUnitName)
if PlayerUnit then
if not PlayerUnitCategory then
PlayerUnitCategory=_SCORINGCategory[PlayerUnit:getDesc().category]
end
if not PlayerUnitCoalition then
PlayerUnitCoalition=_SCORINGCoalition[PlayerUnit:getCoalition()]
end
if not PlayerUnitType then
PlayerUnitType=PlayerUnit:getTypeName()
end
else
PlayerUnitName=''
PlayerUnitCategory=''
PlayerUnitCoalition=''
PlayerUnitType=''
end
else
PlayerUnitName=''
PlayerUnitCategory=''
PlayerUnitCoalition=''
PlayerUnitType=''
end
TargetUnitCoalition=TargetUnitCoalition or""
TargetUnitCategory=TargetUnitCategory or""
TargetUnitType=TargetUnitType or""
TargetUnitName=TargetUnitName or""
if lfs and io and os and self.AutoSave then
self.CSVFile:write(
'"'..self.GameName..'"'..','..
'"'..self.RunTime..'"'..','..
''..ScoreTime..''..','..
'"'..PlayerName..'"'..','..
'"'..TargetPlayerName..'"'..','..
'"'..ScoreType..'"'..','..
'"'..PlayerUnitCoalition..'"'..','..
'"'..PlayerUnitCategory..'"'..','..
'"'..PlayerUnitType..'"'..','..
'"'..PlayerUnitName..'"'..','..
'"'..TargetUnitCoalition..'"'..','..
'"'..TargetUnitCategory..'"'..','..
'"'..TargetUnitType..'"'..','..
'"'..TargetUnitName..'"'..','..
''..ScoreTimes..''..','..
''..ScoreAmount
)
self.CSVFile:write("\n")
end
end
function SCORING:CloseCSV()
if lfs and io and os and self.AutoSave then
self.CSVFile:close()
end
end
function SCORING:SwitchAutoSave(OnOff)
self.AutoSave=OnOff
return self
end
CLEANUP_AIRBASE={
ClassName="CLEANUP_AIRBASE",
TimeInterval=0.2,
CleanUpList={},
}
CLEANUP_AIRBASE.__={}
CLEANUP_AIRBASE.__.Airbases={}
function CLEANUP_AIRBASE:New(AirbaseNames)
local self=BASE:Inherit(self,BASE:New())
self:F({AirbaseNames})
if type(AirbaseNames)=='table'then
for AirbaseID,AirbaseName in pairs(AirbaseNames)do
self:AddAirbase(AirbaseName)
end
else
local AirbaseName=AirbaseNames
self:AddAirbase(AirbaseName)
end
self:HandleEvent(EVENTS.Birth,self.__.OnEventBirth)
self.__.CleanUpScheduler=SCHEDULER:New(self,self.__.CleanUpSchedule,{},1,self.TimeInterval)
self:HandleEvent(EVENTS.EngineShutdown,self.__.EventAddForCleanUp)
self:HandleEvent(EVENTS.EngineStartup,self.__.EventAddForCleanUp)
self:HandleEvent(EVENTS.Hit,self.__.EventAddForCleanUp)
self:HandleEvent(EVENTS.PilotDead,self.__.OnEventCrash)
self:HandleEvent(EVENTS.Dead,self.__.OnEventCrash)
self:HandleEvent(EVENTS.Crash,self.__.OnEventCrash)
for UnitName,Unit in pairs(_DATABASE.UNITS)do
local Unit=Unit
if Unit:IsAlive()~=nil then
if self:IsInAirbase(Unit:GetVec2())then
self:F({UnitName=UnitName})
self.CleanUpList[UnitName]={}
self.CleanUpList[UnitName].CleanUpUnit=Unit
self.CleanUpList[UnitName].CleanUpGroup=Unit:GetGroup()
self.CleanUpList[UnitName].CleanUpGroupName=Unit:GetGroup():GetName()
self.CleanUpList[UnitName].CleanUpUnitName=Unit:GetName()
end
end
end
return self
end
function CLEANUP_AIRBASE:AddAirbase(AirbaseName)
self.__.Airbases[AirbaseName]=AIRBASE:FindByName(AirbaseName)
self:F({"Airbase:",AirbaseName,self.__.Airbases[AirbaseName]:GetDesc()})
return self
end
function CLEANUP_AIRBASE:RemoveAirbase(AirbaseName)
self.__.Airbases[AirbaseName]=nil
return self
end
function CLEANUP_AIRBASE:SetCleanMissiles(CleanMissiles)
if CleanMissiles then
self:HandleEvent(EVENTS.Shot,self.__.OnEventShot)
else
self:UnHandleEvent(EVENTS.Shot)
end
end
function CLEANUP_AIRBASE.__:IsInAirbase(Vec2)
local InAirbase=false
for AirbaseName,Airbase in pairs(self.__.Airbases)do
local Airbase=Airbase
if Airbase:GetZone():IsVec2InZone(Vec2)then
InAirbase=true
break;
end
end
return InAirbase
end
function CLEANUP_AIRBASE.__:DestroyUnit(CleanUpUnit)
self:F({CleanUpUnit})
if CleanUpUnit then
local CleanUpUnitName=CleanUpUnit:GetName()
local CleanUpGroup=CleanUpUnit:GetGroup()
if CleanUpGroup:IsAlive()then
local CleanUpGroupUnits=CleanUpGroup:GetUnits()
if#CleanUpGroupUnits==1 then
local CleanUpGroupName=CleanUpGroup:GetName()
CleanUpGroup:Destroy()
else
CleanUpUnit:Destroy()
end
self.CleanUpList[CleanUpUnitName]=nil
end
end
end
function CLEANUP_AIRBASE.__:DestroyMissile(MissileObject)
self:F({MissileObject})
if MissileObject and MissileObject:isExist()then
MissileObject:destroy()
self:T("MissileObject Destroyed")
end
end
function CLEANUP_AIRBASE.__:OnEventBirth(EventData)
self:F({EventData})
if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive()~=nil then
if self:IsInAirbase(EventData.IniUnit:GetVec2())then
self.CleanUpList[EventData.IniDCSUnitName]={}
self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnit=EventData.IniUnit
self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroup=EventData.IniGroup
self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroupName=EventData.IniDCSGroupName
self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnitName=EventData.IniDCSUnitName
end
end
end
function CLEANUP_AIRBASE.__:OnEventCrash(Event)
self:F({Event})
if Event.IniDCSUnitName and Event.IniCategory==Object.Category.UNIT then
self.CleanUpList[Event.IniDCSUnitName]={}
self.CleanUpList[Event.IniDCSUnitName].CleanUpUnit=Event.IniUnit
self.CleanUpList[Event.IniDCSUnitName].CleanUpGroup=Event.IniGroup
self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName=Event.IniDCSGroupName
self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName=Event.IniDCSUnitName
end
end
function CLEANUP_AIRBASE.__:OnEventShot(Event)
self:F({Event})
if self:IsInAirbase(Event.IniUnit:GetVec2())then
self:DestroyMissile(Event.Weapon)
end
end
function CLEANUP_AIRBASE.__:OnEventHit(Event)
self:F({Event})
if Event.IniUnit then
if self:IsInAirbase(Event.IniUnit:GetVec2())then
self:T({"Life: ",Event.IniDCSUnitName,' = ',Event.IniUnit:GetLife(),"/",Event.IniUnit:GetLife0()})
if Event.IniUnit:GetLife()<Event.IniUnit:GetLife0()then
self:T("CleanUp: Destroy: "..Event.IniDCSUnitName)
CLEANUP_AIRBASE.__:DestroyUnit(Event.IniUnit)
end
end
end
if Event.TgtUnit then
if self:IsInAirbase(Event.TgtUnit:GetVec2())then
self:T({"Life: ",Event.TgtDCSUnitName,' = ',Event.TgtUnit:GetLife(),"/",Event.TgtUnit:GetLife0()})
if Event.TgtUnit:GetLife()<Event.TgtUnit:GetLife0()then
self:T("CleanUp: Destroy: "..Event.TgtDCSUnitName)
CLEANUP_AIRBASE.__:DestroyUnit(Event.TgtUnit)
end
end
end
end
function CLEANUP_AIRBASE.__:AddForCleanUp(CleanUpUnit,CleanUpUnitName)
self:F({CleanUpUnit,CleanUpUnitName})
self.CleanUpList[CleanUpUnitName]={}
self.CleanUpList[CleanUpUnitName].CleanUpUnit=CleanUpUnit
self.CleanUpList[CleanUpUnitName].CleanUpUnitName=CleanUpUnitName
local CleanUpGroup=CleanUpUnit:GetGroup()
self.CleanUpList[CleanUpUnitName].CleanUpGroup=CleanUpGroup
self.CleanUpList[CleanUpUnitName].CleanUpGroupName=CleanUpGroup:GetName()
self.CleanUpList[CleanUpUnitName].CleanUpTime=timer.getTime()
self.CleanUpList[CleanUpUnitName].CleanUpMoved=false
self:T({"CleanUp: Add to CleanUpList: ",CleanUpGroup:GetName(),CleanUpUnitName})
end
function CLEANUP_AIRBASE.__:EventAddForCleanUp(Event)
self:F({Event})
if Event.IniDCSUnit and Event.IniCategory==Object.Category.UNIT then
if self.CleanUpList[Event.IniDCSUnitName]==nil then
if self:IsInAirbase(Event.IniUnit:GetVec2())then
self:AddForCleanUp(Event.IniUnit,Event.IniDCSUnitName)
end
end
end
if Event.TgtDCSUnit and Event.TgtCategory==Object.Category.UNIT then
if self.CleanUpList[Event.TgtDCSUnitName]==nil then
if self:IsInAirbase(Event.TgtUnit:GetVec2())then
self:AddForCleanUp(Event.TgtUnit,Event.TgtDCSUnitName)
end
end
end
end
function CLEANUP_AIRBASE.__:CleanUpSchedule()
local CleanUpCount=0
for CleanUpUnitName,CleanUpListData in pairs(self.CleanUpList)do
CleanUpCount=CleanUpCount+1
local CleanUpUnit=CleanUpListData.CleanUpUnit
local CleanUpGroupName=CleanUpListData.CleanUpGroupName
if CleanUpUnit:IsAlive()~=nil then
if self:IsInAirbase(CleanUpUnit:GetVec2())then
if _DATABASE:GetStatusGroup(CleanUpGroupName)~="ReSpawn"then
local CleanUpCoordinate=CleanUpUnit:GetCoordinate()
self:T({"CleanUp Scheduler",CleanUpUnitName})
if CleanUpUnit:GetLife()<=CleanUpUnit:GetLife0()*0.95 then
if CleanUpUnit:IsAboveRunway()then
if CleanUpUnit:InAir()then
local CleanUpLandHeight=CleanUpCoordinate:GetLandHeight()
local CleanUpUnitHeight=CleanUpCoordinate.y-CleanUpLandHeight
if CleanUpUnitHeight<100 then
self:T({"CleanUp Scheduler","Destroy "..CleanUpUnitName.." because below safe height and damaged."})
self:DestroyUnit(CleanUpUnit)
end
else
self:T({"CleanUp Scheduler","Destroy "..CleanUpUnitName.." because on runway and damaged."})
self:DestroyUnit(CleanUpUnit)
end
end
end
if CleanUpUnit and not CleanUpUnit:GetPlayerName()then
local CleanUpUnitVelocity=CleanUpUnit:GetVelocityKMH()
if CleanUpUnitVelocity<1 then
if CleanUpListData.CleanUpMoved then
if CleanUpListData.CleanUpTime+180<=timer.getTime()then
self:T({"CleanUp Scheduler","Destroy due to not moving anymore "..CleanUpUnitName})
self:DestroyUnit(CleanUpUnit)
end
end
else
CleanUpListData.CleanUpTime=timer.getTime()
CleanUpListData.CleanUpMoved=true
end
end
else
self.CleanUpList[CleanUpUnitName]=nil
end
else
self.CleanUpList[CleanUpUnitName]=nil
end
else
self:T("CleanUp: Group "..CleanUpUnitName.." cannot be found in DCS RTE, removing ...")
self.CleanUpList[CleanUpUnitName]=nil
end
end
self:T(CleanUpCount)
return true
end
MOVEMENT={
ClassName="MOVEMENT",
}
function MOVEMENT:New(MovePrefixes,MoveMaximum)
local self=BASE:Inherit(self,BASE:New())
self:F({MovePrefixes,MoveMaximum})
if type(MovePrefixes)=='table'then
self.MovePrefixes=MovePrefixes
else
self.MovePrefixes={MovePrefixes}
end
self.MoveCount=0
self.MoveMaximum=MoveMaximum
self.AliveUnits=0
self.MoveUnits={}
self:HandleEvent(EVENTS.Birth)
self:ScheduleStart()
return self
end
function MOVEMENT:ScheduleStart()
self:F()
self.MoveFunction=SCHEDULER:New(self,self._Scheduler,{},1,120)
end
function MOVEMENT:ScheduleStop()
self:F()
end
function MOVEMENT:OnEventBirth(EventData)
self:F({EventData})
if timer.getTime0()<timer.getAbsTime()then
if EventData.IniDCSUnit then
self:T("Birth object : "..EventData.IniDCSUnitName)
if EventData.IniDCSGroup and EventData.IniDCSGroup:isExist()then
for MovePrefixID,MovePrefix in pairs(self.MovePrefixes)do
if string.find(EventData.IniDCSUnitName,MovePrefix,1,true)then
self.AliveUnits=self.AliveUnits+1
self.MoveUnits[EventData.IniDCSUnitName]=EventData.IniDCSGroupName
self:T(self.AliveUnits)
end
end
end
end
EventData.IniUnit:HandleEvent(EVENTS.DEAD,self.OnDeadOrCrash)
end
end
function MOVEMENT:OnDeadOrCrash(Event)
self:F({Event})
if Event.IniDCSUnit then
self:T("Dead object : "..Event.IniDCSUnitName)
for MovePrefixID,MovePrefix in pairs(self.MovePrefixes)do
if string.find(Event.IniDCSUnitName,MovePrefix,1,true)then
self.AliveUnits=self.AliveUnits-1
self.MoveUnits[Event.IniDCSUnitName]=nil
self:T(self.AliveUnits)
end
end
end
end
function MOVEMENT:_Scheduler()
self:F({self.MovePrefixes,self.MoveMaximum,self.AliveUnits,self.MovementGroups})
if self.AliveUnits>0 then
local MoveProbability=(self.MoveMaximum*100)/self.AliveUnits
self:T('Move Probability = '..MoveProbability)
for MovementUnitName,MovementGroupName in pairs(self.MoveUnits)do
local MovementGroup=Group.getByName(MovementGroupName)
if MovementGroup and MovementGroup:isExist()then
local MoveOrStop=math.random(1,100)
self:T('MoveOrStop = '..MoveOrStop)
if MoveOrStop<=MoveProbability then
self:T('Group continues moving = '..MovementGroupName)
trigger.action.groupContinueMoving(MovementGroup)
else
self:T('Group stops moving = '..MovementGroupName)
trigger.action.groupStopMoving(MovementGroup)
end
else
self.MoveUnits[MovementUnitName]=nil
end
end
end
return true
end
SEAD={
ClassName="SEAD",
TargetSkill={
Average={Evade=30,DelayOn={40,60}},
Good={Evade=20,DelayOn={30,50}},
High={Evade=15,DelayOn={20,40}},
Excellent={Evade=10,DelayOn={10,30}}
},
SEADGroupPrefixes={},
SuppressedGroups={},
EngagementRange=75,
Padding=10,
CallBack=nil,
UseCallBack=false,
debug=false,
}
SEAD.Harms={
["AGM_88"]="AGM_88",
["AGM_45"]="AGM_45",
["AGM_122"]="AGM_122",
["AGM_84"]="AGM_84",
["AGM_45"]="AGM_45",
["ALARM"]="ALARM",
["LD-10"]="LD-10",
["X_58"]="X_58",
["X_28"]="X_28",
["X_25"]="X_25",
["X_31"]="X_31",
["Kh25"]="Kh25",
["BGM_109"]="BGM_109",
["AGM_154"]="AGM_154",
["HY-2"]="HY-2",
}
SEAD.HarmData={
["AGM_88"]={150,3},
["AGM_45"]={12,2},
["AGM_122"]={16.5,2.3},
["AGM_84"]={280,0.8},
["ALARM"]={45,2},
["LD-10"]={60,4},
["X_58"]={70,4},
["X_28"]={80,2.5},
["X_25"]={25,0.76},
["X_31"]={150,3},
["Kh25"]={25,0.8},
["BGM_109"]={460,0.705},
["AGM_154"]={130,0.61},
["HY-2"]={90,1},
}
function SEAD:New(SEADGroupPrefixes,Padding)
local self=BASE:Inherit(self,FSM:New())
self:T(SEADGroupPrefixes)
if type(SEADGroupPrefixes)=='table'then
for SEADGroupPrefixID,SEADGroupPrefix in pairs(SEADGroupPrefixes)do
self.SEADGroupPrefixes[SEADGroupPrefix]=SEADGroupPrefix
end
else
self.SEADGroupPrefixes[SEADGroupPrefixes]=SEADGroupPrefixes
end
local padding=Padding or 10
if padding<10 then padding=10 end
self.Padding=padding
self.UseEmissionsOnOff=true
self.debug=false
self.CallBack=nil
self.UseCallBack=false
self:HandleEvent(EVENTS.Shot,self.HandleEventShot)
self:SetStartState("Running")
self:AddTransition("*","ManageEvasion","*")
self:AddTransition("*","CalculateHitZone","*")
self:I("*** SEAD - Started Version 0.4.3")
return self
end
function SEAD:UpdateSet(SEADGroupPrefixes)
self:T(SEADGroupPrefixes)
if type(SEADGroupPrefixes)=='table'then
for SEADGroupPrefixID,SEADGroupPrefix in pairs(SEADGroupPrefixes)do
self.SEADGroupPrefixes[SEADGroupPrefix]=SEADGroupPrefix
end
else
self.SEADGroupPrefixes[SEADGroupPrefixes]=SEADGroupPrefixes
end
return self
end
function SEAD:SetEngagementRange(range)
self:T({range})
range=range or 75
if range<0 or range>100 then
range=75
end
self.EngagementRange=range
self:T(string.format("*** SEAD - Engagement range set to %s",range))
return self
end
function SEAD:SetPadding(Padding)
self:T({Padding})
local padding=Padding or 10
if padding<10 then padding=10 end
self.Padding=padding
return self
end
function SEAD:SwitchEmissions(Switch)
self:T({Switch})
self.UseEmissionsOnOff=Switch
return self
end
function SEAD:AddCallBack(Object)
self:T({Class=Object.ClassName})
self.CallBack=Object
self.UseCallBack=true
return self
end
function SEAD:_CheckHarms(WeaponName)
self:T({WeaponName})
local hit=false
local name=""
for _,_name in pairs(SEAD.Harms)do
if string.find(WeaponName,_name,1,true)then
hit=true
name=_name
break
end
end
return hit,name
end
function SEAD:_GetDistance(_point1,_point2)
self:T("_GetDistance")
if _point1 and _point2 then
local distance1=_point1:Get2DDistance(_point2)
local distance2=_point1:DistanceFromPointVec2(_point2)
if distance1 and type(distance1)=="number"then
return distance1
elseif distance2 and type(distance2)=="number"then
return distance2
else
self:E("*****Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
else
self:E("******Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
end
function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup,SEADWeaponName)
self:T("**** Calculating hit zone for "..(SEADWeaponName or"None"))
if SEADWeapon and SEADWeapon:isExist()then
local position=SEADWeapon:getPosition()
local mheight=height
local wph=math.atan2(position.x.z,position.x.x)
if wph<0 then
wph=wph+2*math.pi
end
wph=math.deg(wph)
local wpndata=SEAD.HarmData["AGM_88"]
if string.find(SEADWeaponName,"154",1)then
wpndata=SEAD.HarmData["AGM_154"]
end
local mveloc=math.floor(wpndata[2]*340.29)
local c1=(2*mheight*9.81)/(mveloc^2)
local c2=(mveloc^2)/9.81
local Ropt=c2*math.sqrt(c1+1)
if height<=5000 then
Ropt=Ropt*0.72
elseif height<=7500 then
Ropt=Ropt*0.82
elseif height<=10000 then
Ropt=Ropt*0.87
elseif height<=12500 then
Ropt=Ropt*0.98
end
for n=1,3 do
local dist=Ropt-((n-1)*20000)
local predpos=pos0:Translate(dist,wph)
if predpos then
local targetzone=ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
if self.debug then
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
end
local seadset=SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce()
local tgtcoord=targetzone:GetRandomPointVec2()
local tgtgrp=seadset:GetRandom()
local _targetgroup=nil
local _targetgroupname="none"
local _targetskill="Random"
if tgtgrp and tgtgrp:IsAlive()then
_targetgroup=tgtgrp
_targetgroupname=tgtgrp:GetName()
_targetskill=tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".._targetgroupname)
self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup,20)
end
end
end
end
return self
end
function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,timeoffset)
local timeoffset=timeoffset or 0
if _targetskill=="Random"then
local Skills={"Average","Good","High","Excellent"}
_targetskill=Skills[math.random(1,4)]
end
if self.TargetSkill[_targetskill]then
local _evade=math.random(1,100)
if(_evade>self.TargetSkill[_targetskill].Evade)then
self:T("*** SEAD - Evading")
local _targetpos=_targetgroup:GetCoordinate()
local _distance=self:_GetDistance(SEADPlanePos,_targetpos)
local hit,data=self:_CheckHarms(SEADWeaponName)
local wpnspeed=666
local reach=10
if hit then
local wpndata=SEAD.HarmData[data]
reach=wpndata[1]*1,1
local mach=wpndata[2]
wpnspeed=math.floor(mach*340.29)
end
local _tti=math.floor(_distance/wpnspeed)-timeoffset
if _distance>0 then
_distance=math.floor(_distance/1000)
else
_distance=0
end
self:T(string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec",_targetskill,_distance,reach,_tti))
if reach>=_distance then
self:T("*** SEAD - Shot in Reach")
local function SuppressionStart(args)
self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2]))
local grp=args[1]
local name=args[2]
local attacker=args[3]
if self.UseEmissionsOnOff then
grp:EnableEmission(false)
end
grp:OptionAlarmStateGreen()
grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond")
if self.UseCallBack then
local object=self.CallBack
object:SeadSuppressionStart(grp,name,attacker)
end
end
local function SuppressionStop(args)
self:T(string.format("*** SEAD - %s Radar On",args[2]))
local grp=args[1]
local name=args[2]
if self.UseEmissionsOnOff then
grp:EnableEmission(true)
end
grp:OptionAlarmStateRed()
grp:OptionEngageRange(self.EngagementRange)
self.SuppressedGroups[name]=false
if self.UseCallBack then
local object=self.CallBack
object:SeadSuppressionEnd(grp,name)
end
end
local delay=math.random(self.TargetSkill[_targetskill].DelayOn[1],self.TargetSkill[_targetskill].DelayOn[2])
if delay>_tti then delay=delay/2 end
if _tti>600 then delay=_tti-90 end
local SuppressionStartTime=timer.getTime()+delay
local SuppressionEndTime=timer.getTime()+_tti+self.Padding
local _targetgroupname=_targetgroup:GetName()
if not self.SuppressedGroups[_targetgroupname]then
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname,SEADGroup},SuppressionStartTime)
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
self.SuppressedGroups[_targetgroupname]=true
if self.UseCallBack then
local object=self.CallBack
object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime,SEADGroup)
end
end
end
end
end
return self
end
function SEAD:HandleEventShot(EventData)
self:T({EventData.id})
local SEADPlane=EventData.IniUnit
local SEADGroup=EventData.IniGroup
local SEADPlanePos=SEADPlane:GetCoordinate()
local SEADUnit=EventData.IniDCSUnit
local SEADUnitName=EventData.IniDCSUnitName
local SEADWeapon=EventData.Weapon
local SEADWeaponName=EventData.WeaponName
self:T("*** SEAD - Missile Launched = "..SEADWeaponName)
if self:_CheckHarms(SEADWeaponName)then
self:T('*** SEAD - Weapon Match')
local _targetskill="Random"
local _targetgroupname="none"
local _target=EventData.Weapon:getTarget()
if not _target or self.debug then
self:E("***** SEAD - No target data for "..(SEADWeaponName or"None"))
if string.find(SEADWeaponName,"AGM_88",1,true)or string.find(SEADWeaponName,"AGM_154",1,true)then
self:I("**** Tracking AGM-88/154 with no target data.")
local pos0=SEADPlane:GetCoordinate()
local fheight=SEADPlane:GetHeight()
self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName)
end
return self
end
local targetcat=_target:getCategory()
local _targetUnit=nil
local _targetgroup=nil
self:T(string.format("*** Targetcat = %d",targetcat))
if targetcat==Object.Category.UNIT then
self:T("*** Target Category UNIT")
_targetUnit=UNIT:Find(_target)
if _targetUnit and _targetUnit:IsAlive()then
_targetgroup=_targetUnit:GetGroup()
_targetgroupname=_targetgroup:GetName()
local _targetUnitName=_targetUnit:GetName()
_targetUnit:GetSkill()
_targetskill=_targetUnit:GetSkill()
end
elseif targetcat==Object.Category.STATIC then
self:T("*** Target Category STATIC")
local seadset=SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterOnce()
local targetpoint=_target:getPoint()or{x=0,y=0,z=0}
local tgtcoord=COORDINATE:NewFromVec3(targetpoint)
local tgtgrp=seadset:FindNearestGroupFromPointVec2(tgtcoord)
if tgtgrp and tgtgrp:IsAlive()then
_targetgroup=tgtgrp
_targetgroupname=tgtgrp:GetName()
_targetskill=tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".._targetgroupname)
end
end
local SEADGroupFound=false
for SEADGroupPrefixID,SEADGroupPrefix in pairs(self.SEADGroupPrefixes)do
self:T("Target = ".._targetgroupname.." | Prefix = "..SEADGroupPrefix)
if string.find(_targetgroupname,SEADGroupPrefix,1,true)then
SEADGroupFound=true
self:T('*** SEAD - Group Match Found')
break
end
end
if SEADGroupFound==true then
self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup)
end
end
return self
end
ESCORT={
ClassName="ESCORT",
EscortName=nil,
EscortClient=nil,
EscortGroup=nil,
EscortMode=1,
MODE={
FOLLOW=1,
MISSION=2,
},
Targets={},
FollowScheduler=nil,
ReportTargets=true,
OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE,
OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION,
SmokeDirectionVector=false,
TaskPoints={}
}
function ESCORT:New(EscortClient,EscortGroup,EscortName,EscortBriefing)
local self=BASE:Inherit(self,BASE:New())
self:F({EscortClient,EscortGroup,EscortName})
self.EscortClient=EscortClient
self.EscortGroup=EscortGroup
self.EscortName=EscortName
self.EscortBriefing=EscortBriefing
self.EscortSetGroup=SET_GROUP:New()
self.EscortSetGroup:AddObject(self.EscortGroup)
self.EscortSetGroup:Flush()
self.Detection=DETECTION_UNITS:New(self.EscortSetGroup,15000)
self.EscortGroup.Detection=self.Detection
if not self.EscortClient._EscortGroups then
self.EscortClient._EscortGroups={}
end
if not self.EscortClient._EscortGroups[EscortGroup:GetName()]then
self.EscortClient._EscortGroups[EscortGroup:GetName()]={}
self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortGroup=self.EscortGroup
self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortName=self.EscortName
self.EscortClient._EscortGroups[EscortGroup:GetName()].Detection=self.EscortGroup.Detection
end
self.EscortMenu=MENU_GROUP:New(self.EscortClient:GetGroup(),self.EscortName)
self.EscortGroup:WayPointInitialize(1)
self.EscortGroup:OptionROTVertical()
self.EscortGroup:OptionROEOpenFire()
if not EscortBriefing then
EscortGroup:MessageToClient(EscortGroup:GetCategoryName().." '"..EscortName.."' ("..EscortGroup:GetCallsign()..") reporting! "..
"We're escorting your flight. "..
"Use the Radio Menu and F10 and use the options under + "..EscortName.."\n",
60,EscortClient
)
else
EscortGroup:MessageToClient(EscortGroup:GetCategoryName().." '"..EscortName.."' ("..EscortGroup:GetCallsign()..") "..EscortBriefing,
60,EscortClient
)
end
self.FollowDistance=100
self.CT1=0
self.GT1=0
self.FollowScheduler,self.FollowSchedule=SCHEDULER:New(self,self._FollowScheduler,{},1,.5,.01)
self.FollowScheduler:Stop(self.FollowSchedule)
self.EscortMode=ESCORT.MODE.MISSION
return self
end
function ESCORT:SetDetection(Detection)
self.Detection=Detection
self.EscortGroup.Detection=self.Detection
self.EscortClient._EscortGroups[self.EscortGroup:GetName()].Detection=self.EscortGroup.Detection
Detection:__Start(1)
end
function ESCORT:TestSmokeDirectionVector(SmokeDirection)
self.SmokeDirectionVector=(SmokeDirection==true)and true or false
end
function ESCORT:Menus()
self:F()
self:MenuFollowAt(100)
self:MenuFollowAt(200)
self:MenuFollowAt(300)
self:MenuFollowAt(400)
self:MenuScanForTargets(100,60)
self:MenuHoldAtEscortPosition(30)
self:MenuHoldAtLeaderPosition(30)
self:MenuFlare()
self:MenuSmoke()
self:MenuReportTargets(60)
self:MenuAssistedAttack()
self:MenuROE()
self:MenuEvasion()
self:MenuResumeMission()
return self
end
function ESCORT:MenuFollowAt(Distance)
self:F(Distance)
if self.EscortGroup:IsAir()then
if not self.EscortMenuReportNavigation then
self.EscortMenuReportNavigation=MENU_GROUP:New(self.EscortClient:GetGroup(),"Navigation",self.EscortMenu)
end
if not self.EscortMenuJoinUpAndFollow then
self.EscortMenuJoinUpAndFollow={}
end
self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1]=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Join-Up and Follow at "..Distance,self.EscortMenuReportNavigation,ESCORT._JoinUpAndFollow,self,Distance)
self.EscortMode=ESCORT.MODE.FOLLOW
end
return self
end
function ESCORT:MenuHoldAtEscortPosition(Height,Seconds,MenuTextFormat)
self:F({Height,Seconds,MenuTextFormat})
if self.EscortGroup:IsAir()then
if not self.EscortMenuHold then
self.EscortMenuHold=MENU_GROUP:New(self.EscortClient:GetGroup(),"Hold position",self.EscortMenu)
end
if not Height then
Height=30
end
if not Seconds then
Seconds=0
end
local MenuText=""
if not MenuTextFormat then
if Seconds==0 then
MenuText=string.format("Hold at %d meter",Height)
else
MenuText=string.format("Hold at %d meter for %d seconds",Height,Seconds)
end
else
if Seconds==0 then
MenuText=string.format(MenuTextFormat,Height)
else
MenuText=string.format(MenuTextFormat,Height,Seconds)
end
end
if not self.EscortMenuHoldPosition then
self.EscortMenuHoldPosition={}
end
self.EscortMenuHoldPosition[#self.EscortMenuHoldPosition+1]=MENU_GROUP_COMMAND
:New(
self.EscortClient:GetGroup(),
MenuText,
self.EscortMenuHold,
ESCORT._HoldPosition,
self,
self.EscortGroup,
Height,
Seconds
)
end
return self
end
function ESCORT:MenuHoldAtLeaderPosition(Height,Seconds,MenuTextFormat)
self:F({Height,Seconds,MenuTextFormat})
if self.EscortGroup:IsAir()then
if not self.EscortMenuHold then
self.EscortMenuHold=MENU_GROUP:New(self.EscortClient:GetGroup(),"Hold position",self.EscortMenu)
end
if not Height then
Height=30
end
if not Seconds then
Seconds=0
end
local MenuText=""
if not MenuTextFormat then
if Seconds==0 then
MenuText=string.format("Rejoin and hold at %d meter",Height)
else
MenuText=string.format("Rejoin and hold at %d meter for %d seconds",Height,Seconds)
end
else
if Seconds==0 then
MenuText=string.format(MenuTextFormat,Height)
else
MenuText=string.format(MenuTextFormat,Height,Seconds)
end
end
if not self.EscortMenuHoldAtLeaderPosition then
self.EscortMenuHoldAtLeaderPosition={}
end
self.EscortMenuHoldAtLeaderPosition[#self.EscortMenuHoldAtLeaderPosition+1]=MENU_GROUP_COMMAND
:New(
self.EscortClient:GetGroup(),
MenuText,
self.EscortMenuHold,
ESCORT._HoldPosition,
{ParamSelf=self,
ParamOrbitGroup=self.EscortClient,
ParamHeight=Height,
ParamSeconds=Seconds
}
)
end
return self
end
function ESCORT:MenuScanForTargets(Height,Seconds,MenuTextFormat)
self:F({Height,Seconds,MenuTextFormat})
if self.EscortGroup:IsAir()then
if not self.EscortMenuScan then
self.EscortMenuScan=MENU_GROUP:New(self.EscortClient:GetGroup(),"Scan for targets",self.EscortMenu)
end
if not Height then
Height=100
end
if not Seconds then
Seconds=30
end
local MenuText=""
if not MenuTextFormat then
if Seconds==0 then
MenuText=string.format("At %d meter",Height)
else
MenuText=string.format("At %d meter for %d seconds",Height,Seconds)
end
else
if Seconds==0 then
MenuText=string.format(MenuTextFormat,Height)
else
MenuText=string.format(MenuTextFormat,Height,Seconds)
end
end
if not self.EscortMenuScanForTargets then
self.EscortMenuScanForTargets={}
end
self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1]=MENU_GROUP_COMMAND
:New(
self.EscortClient:GetGroup(),
MenuText,
self.EscortMenuScan,
ESCORT._ScanTargets,
self,
30
)
end
return self
end
function ESCORT:MenuFlare(MenuTextFormat)
self:F()
if not self.EscortMenuReportNavigation then
self.EscortMenuReportNavigation=MENU_GROUP:New(self.EscortClient:GetGroup(),"Navigation",self.EscortMenu)
end
local MenuText=""
if not MenuTextFormat then
MenuText="Flare"
else
MenuText=MenuTextFormat
end
if not self.EscortMenuFlare then
self.EscortMenuFlare=MENU_GROUP:New(self.EscortClient:GetGroup(),MenuText,self.EscortMenuReportNavigation,ESCORT._Flare,self)
self.EscortMenuFlareGreen=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release green flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Green,"Released a green flare!")
self.EscortMenuFlareRed=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release red flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Red,"Released a red flare!")
self.EscortMenuFlareWhite=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release white flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.White,"Released a white flare!")
self.EscortMenuFlareYellow=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release yellow flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Yellow,"Released a yellow flare!")
end
return self
end
function ESCORT:MenuSmoke(MenuTextFormat)
self:F()
if not self.EscortGroup:IsAir()then
if not self.EscortMenuReportNavigation then
self.EscortMenuReportNavigation=MENU_GROUP:New(self.EscortClient:GetGroup(),"Navigation",self.EscortMenu)
end
local MenuText=""
if not MenuTextFormat then
MenuText="Smoke"
else
MenuText=MenuTextFormat
end
if not self.EscortMenuSmoke then
self.EscortMenuSmoke=MENU_GROUP:New(self.EscortClient:GetGroup(),"Smoke",self.EscortMenuReportNavigation,ESCORT._Smoke,self)
self.EscortMenuSmokeGreen=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release green smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Green,"Releasing green smoke!")
self.EscortMenuSmokeRed=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release red smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Red,"Releasing red smoke!")
self.EscortMenuSmokeWhite=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release white smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.White,"Releasing white smoke!")
self.EscortMenuSmokeOrange=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release orange smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Orange,"Releasing orange smoke!")
self.EscortMenuSmokeBlue=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release blue smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Blue,"Releasing blue smoke!")
end
end
return self
end
function ESCORT:MenuReportTargets(Seconds)
self:F({Seconds})
if not self.EscortMenuReportNearbyTargets then
self.EscortMenuReportNearbyTargets=MENU_GROUP:New(self.EscortClient:GetGroup(),"Report targets",self.EscortMenu)
end
if not Seconds then
Seconds=30
end
self.EscortMenuReportNearbyTargetsNow=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Report targets now!",self.EscortMenuReportNearbyTargets,ESCORT._ReportNearbyTargetsNow,self)
self.EscortMenuReportNearbyTargetsOn=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Report targets on",self.EscortMenuReportNearbyTargets,ESCORT._SwitchReportNearbyTargets,self,true)
self.EscortMenuReportNearbyTargetsOff=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Report targets off",self.EscortMenuReportNearbyTargets,ESCORT._SwitchReportNearbyTargets,self,false)
self.EscortMenuAttackNearbyTargets=MENU_GROUP:New(self.EscortClient:GetGroup(),"Attack targets",self.EscortMenu)
self.ReportTargetsScheduler=SCHEDULER:New(self,self._ReportTargetsScheduler,{},1,Seconds)
return self
end
function ESCORT:MenuAssistedAttack()
self:F()
self.EscortMenuTargetAssistance=MENU_GROUP:New(self.EscortClient:GetGroup(),"Request assistance from",self.EscortMenu)
return self
end
function ESCORT:MenuROE(MenuTextFormat)
self:F(MenuTextFormat)
if not self.EscortMenuROE then
self.EscortMenuROE=MENU_GROUP:New(self.EscortClient:GetGroup(),"ROE",self.EscortMenu)
if self.EscortGroup:OptionROEHoldFirePossible()then
self.EscortMenuROEHoldFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Hold Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEHoldFire(),"Holding weapons!")
end
if self.EscortGroup:OptionROEReturnFirePossible()then
self.EscortMenuROEReturnFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Return Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEReturnFire(),"Returning fire!")
end
if self.EscortGroup:OptionROEOpenFirePossible()then
self.EscortMenuROEOpenFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Open Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEOpenFire(),"Opening fire on designated targets!!")
end
if self.EscortGroup:OptionROEWeaponFreePossible()then
self.EscortMenuROEWeaponFree=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Weapon Free",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEWeaponFree(),"Opening fire on targets of opportunity!")
end
end
return self
end
function ESCORT:MenuEvasion(MenuTextFormat)
self:F(MenuTextFormat)
if self.EscortGroup:IsAir()then
if not self.EscortMenuEvasion then
self.EscortMenuEvasion=MENU_GROUP:New(self.EscortClient:GetGroup(),"Evasion",self.EscortMenu)
if self.EscortGroup:OptionROTNoReactionPossible()then
self.EscortMenuEvasionNoReaction=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Fight until death",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTNoReaction(),"Fighting until death!")
end
if self.EscortGroup:OptionROTPassiveDefensePossible()then
self.EscortMenuEvasionPassiveDefense=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Use flares, chaff and jammers",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTPassiveDefense(),"Defending using jammers, chaff and flares!")
end
if self.EscortGroup:OptionROTEvadeFirePossible()then
self.EscortMenuEvasionEvadeFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Evade enemy fire",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTEvadeFire(),"Evading on enemy fire!")
end
if self.EscortGroup:OptionROTVerticalPossible()then
self.EscortMenuOptionEvasionVertical=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Go below radar and evade fire",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTVertical(),"Evading on enemy fire with vertical manoeuvres!")
end
end
end
return self
end
function ESCORT:MenuResumeMission()
self:F()
if not self.EscortMenuResumeMission then
self.EscortMenuResumeMission=MENU_GROUP:New(self.EscortClient:GetGroup(),"Resume mission from",self.EscortMenu)
end
return self
end
function ESCORT:_HoldPosition(OrbitGroup,OrbitHeight,OrbitSeconds)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
local OrbitUnit=OrbitGroup:GetUnit(1)
self.FollowScheduler:Stop(self.FollowSchedule)
local PointFrom={}
local GroupVec3=EscortGroup:GetUnit(1):GetVec3()
PointFrom={}
PointFrom.x=GroupVec3.x
PointFrom.y=GroupVec3.z
PointFrom.speed=250
PointFrom.type=AI.Task.WaypointType.TURNING_POINT
PointFrom.alt=GroupVec3.y
PointFrom.alt_type=AI.Task.AltitudeType.BARO
local OrbitPoint=OrbitUnit:GetVec2()
local PointTo={}
PointTo.x=OrbitPoint.x
PointTo.y=OrbitPoint.y
PointTo.speed=250
PointTo.type=AI.Task.WaypointType.TURNING_POINT
PointTo.alt=OrbitHeight
PointTo.alt_type=AI.Task.AltitudeType.BARO
PointTo.task=EscortGroup:TaskOrbitCircleAtVec2(OrbitPoint,OrbitHeight,0)
local Points={PointFrom,PointTo}
EscortGroup:OptionROEHoldFire()
EscortGroup:OptionROTPassiveDefense()
EscortGroup:SetTask(EscortGroup:TaskRoute(Points))
EscortGroup:MessageToClient("Orbiting at location.",10,EscortClient)
end
function ESCORT:_JoinUpAndFollow(Distance)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
self.Distance=Distance
self:JoinUpAndFollow(EscortGroup,EscortClient,self.Distance)
end
function ESCORT:JoinUpAndFollow(EscortGroup,EscortClient,Distance)
self:F({EscortGroup,EscortClient,Distance})
self.FollowScheduler:Stop(self.FollowSchedule)
EscortGroup:OptionROEHoldFire()
EscortGroup:OptionROTPassiveDefense()
self.EscortMode=ESCORT.MODE.FOLLOW
self.CT1=0
self.GT1=0
self.FollowScheduler:Start(self.FollowSchedule)
EscortGroup:MessageToClient("Rejoining and Following at "..Distance.."!",30,EscortClient)
end
function ESCORT:_Flare(Color,Message)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
EscortGroup:GetUnit(1):Flare(Color)
EscortGroup:MessageToClient(Message,10,EscortClient)
end
function ESCORT:_Smoke(Color,Message)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
EscortGroup:GetUnit(1):Smoke(Color)
EscortGroup:MessageToClient(Message,10,EscortClient)
end
function ESCORT:_ReportNearbyTargetsNow()
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
self:_ReportTargetsScheduler()
end
function ESCORT:_SwitchReportNearbyTargets(ReportTargets)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
self.ReportTargets=ReportTargets
if self.ReportTargets then
if not self.ReportTargetsScheduler then
self.ReportTargetsScheduler:Schedule(self,self._ReportTargetsScheduler,{},1,30)
end
else
routines.removeFunction(self.ReportTargetsScheduler)
self.ReportTargetsScheduler=nil
end
end
function ESCORT:_ScanTargets(ScanDuration)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
self.FollowScheduler:Stop(self.FollowSchedule)
if EscortGroup:IsHelicopter()then
EscortGroup:PushTask(
EscortGroup:TaskControlled(
EscortGroup:TaskOrbitCircle(200,20),
EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil)
),1)
elseif EscortGroup:IsAirPlane()then
EscortGroup:PushTask(
EscortGroup:TaskControlled(
EscortGroup:TaskOrbitCircle(1000,500),
EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil)
),1)
end
EscortGroup:MessageToClient("Scanning targets for "..ScanDuration.." seconds.",ScanDuration,EscortClient)
if self.EscortMode==ESCORT.MODE.FOLLOW then
self.FollowScheduler:Start(self.FollowSchedule)
end
end
function _Resume(EscortGroup)
env.info('_Resume')
local Escort=EscortGroup:GetState(EscortGroup,"Escort")
env.info("EscortMode = "..Escort.EscortMode)
if Escort.EscortMode==ESCORT.MODE.FOLLOW then
Escort:JoinUpAndFollow(EscortGroup,Escort.EscortClient,Escort.Distance)
end
end
function ESCORT:_AttackTarget(DetectedItem)
local EscortGroup=self.EscortGroup
self:F(EscortGroup)
local EscortClient=self.EscortClient
self.FollowScheduler:Stop(self.FollowSchedule)
if EscortGroup:IsAir()then
EscortGroup:OptionROEOpenFire()
EscortGroup:OptionROTPassiveDefense()
EscortGroup:SetState(EscortGroup,"Escort",self)
local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem)
local Tasks={}
DetectedSet:ForEachUnit(
function(DetectedUnit,Tasks)
if DetectedUnit:IsAlive()then
Tasks[#Tasks+1]=EscortGroup:TaskAttackUnit(DetectedUnit)
end
end,Tasks
)
Tasks[#Tasks+1]=EscortGroup:TaskFunction("_Resume",{"''"})
EscortGroup:SetTask(
EscortGroup:TaskCombo(
Tasks
),1
)
else
local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem)
local Tasks={}
DetectedSet:ForEachUnit(
function(DetectedUnit,Tasks)
if DetectedUnit:IsAlive()then
Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50)
end
end,Tasks
)
EscortGroup:SetTask(
EscortGroup:TaskCombo(
Tasks
),1
)
end
EscortGroup:MessageToClient("Engaging Designated Unit!",10,EscortClient)
end
function ESCORT:_AssistTarget(EscortGroupAttack,DetectedItem)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
self.FollowScheduler:Stop(self.FollowSchedule)
if EscortGroupAttack:IsAir()then
EscortGroupAttack:OptionROEOpenFire()
EscortGroupAttack:OptionROTVertical()
local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem)
local Tasks={}
DetectedSet:ForEachUnit(
function(DetectedUnit,Tasks)
if DetectedUnit:IsAlive()then
Tasks[#Tasks+1]=EscortGroupAttack:TaskAttackUnit(DetectedUnit)
end
end,Tasks
)
Tasks[#Tasks+1]=EscortGroupAttack:TaskOrbitCircle(500,350)
EscortGroupAttack:SetTask(
EscortGroupAttack:TaskCombo(
Tasks
),1
)
else
local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem)
local Tasks={}
DetectedSet:ForEachUnit(
function(DetectedUnit,Tasks)
if DetectedUnit:IsAlive()then
Tasks[#Tasks+1]=EscortGroupAttack:TaskFireAtPoint(DetectedUnit:GetVec2(),50)
end
end,Tasks
)
EscortGroupAttack:SetTask(
EscortGroupAttack:TaskCombo(
Tasks
),1
)
end
EscortGroupAttack:MessageToClient("Assisting with the destroying the enemy unit!",10,EscortClient)
end
function ESCORT:_ROE(EscortROEFunction,EscortROEMessage)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
pcall(function()EscortROEFunction()end)
EscortGroup:MessageToClient(EscortROEMessage,10,EscortClient)
end
function ESCORT:_ROT(EscortROTFunction,EscortROTMessage)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
pcall(function()EscortROTFunction()end)
EscortGroup:MessageToClient(EscortROTMessage,10,EscortClient)
end
function ESCORT:_ResumeMission(WayPoint)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
self.FollowScheduler:Stop(self.FollowSchedule)
local WayPoints=EscortGroup:GetTaskRoute()
self:T(WayPoint,WayPoints)
for WayPointIgnore=1,WayPoint do
table.remove(WayPoints,1)
end
SCHEDULER:New(EscortGroup,EscortGroup.SetTask,{EscortGroup:TaskRoute(WayPoints)},1)
EscortGroup:MessageToClient("Resuming mission from waypoint "..WayPoint..".",10,EscortClient)
end
function ESCORT:RegisterRoute()
self:F()
local EscortGroup=self.EscortGroup
local TaskPoints=EscortGroup:GetTaskRoute()
self:T(TaskPoints)
return TaskPoints
end
function ESCORT:_FollowScheduler()
self:F({self.FollowDistance})
self:T({self.EscortClient.UnitName,self.EscortGroup.GroupName})
if self.EscortGroup:IsAlive()and self.EscortClient:IsAlive()then
local ClientUnit=self.EscortClient:GetClientGroupUnit()
local GroupUnit=self.EscortGroup:GetUnit(1)
local FollowDistance=self.FollowDistance
self:T({ClientUnit.UnitName,GroupUnit.UnitName})
if self.CT1==0 and self.GT1==0 then
self.CV1=ClientUnit:GetVec3()
self:T({"self.CV1",self.CV1})
self.CT1=timer.getTime()
self.GV1=GroupUnit:GetVec3()
self.GT1=timer.getTime()
else
local CT1=self.CT1
local CT2=timer.getTime()
local CV1=self.CV1
local CV2=ClientUnit:GetVec3()
self.CT1=CT2
self.CV1=CV2
local CD=((CV2.x-CV1.x)^2+(CV2.y-CV1.y)^2+(CV2.z-CV1.z)^2)^0.5
local CT=CT2-CT1
local CS=(3600/CT)*(CD/1000)
self:T2({"Client:",CS,CD,CT,CV2,CV1,CT2,CT1})
local GT1=self.GT1
local GT2=timer.getTime()
local GV1=self.GV1
local GV2=GroupUnit:GetVec3()
self.GT1=GT2
self.GV1=GV2
local GD=((GV2.x-GV1.x)^2+(GV2.y-GV1.y)^2+(GV2.z-GV1.z)^2)^0.5
local GT=GT2-GT1
local GS=(3600/GT)*(GD/1000)
self:T2({"Group:",GS,GD,GT,GV2,GV1,GT2,GT1})
local GV={x=GV2.x-CV2.x,y=GV2.y-CV2.y,z=GV2.z-CV2.z}
local GH2={x=GV2.x,y=CV2.y,z=GV2.z}
local alpha=math.atan2(GV.z,GV.x)
local CVI={x=CV2.x+FollowDistance*math.cos(alpha),
y=GH2.y,
z=CV2.z+FollowDistance*math.sin(alpha),
}
local DV={x=CV2.x-CVI.x,y=CV2.y-CVI.y,z=CV2.z-CVI.z}
local DVu={x=DV.x/FollowDistance,y=DV.y/FollowDistance,z=DV.z/FollowDistance}
local GDV={x=DVu.x*CS*8+CVI.x,y=CVI.y,z=DVu.z*CS*8+CVI.z}
if self.SmokeDirectionVector==true then
trigger.action.smoke(GDV,trigger.smokeColor.Red)
end
self:T2({"CV2:",CV2})
self:T2({"CVI:",CVI})
self:T2({"GDV:",GDV})
local CatchUpDistance=((GDV.x-GV2.x)^2+(GDV.y-GV2.y)^2+(GDV.z-GV2.z)^2)^0.5
local Time=10
local CatchUpSpeed=(CatchUpDistance-(CS*8.4))/Time
local Speed=CS+CatchUpSpeed
if Speed<0 then
Speed=0
end
self:T({"Client Speed, Escort Speed, Speed, FollowDistance, Time:",CS,GS,Speed,FollowDistance,Time})
self.EscortGroup:RouteToVec3(GDV,Speed/3.6)
end
return true
end
return false
end
function ESCORT:_ReportTargetsScheduler()
self:F(self.EscortGroup:GetName())
if self.EscortGroup:IsAlive()and self.EscortClient:IsAlive()then
if true then
local EscortGroupName=self.EscortGroup:GetName()
self.EscortMenuAttackNearbyTargets:RemoveSubMenus()
if self.EscortMenuTargetAssistance then
self.EscortMenuTargetAssistance:RemoveSubMenus()
end
local DetectedItems=self.Detection:GetDetectedItems()
self:F(DetectedItems)
local DetectedTargets=false
local DetectedMsgs={}
for ClientEscortGroupName,EscortGroupData in pairs(self.EscortClient._EscortGroups)do
local ClientEscortTargets=EscortGroupData.Detection
for DetectedItemIndex,DetectedItem in pairs(DetectedItems)do
self:F({DetectedItemIndex,DetectedItem})
local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,EscortGroupData.EscortGroup,_DATABASE:GetPlayerSettings(self.EscortClient:GetPlayerName()))
if ClientEscortGroupName==EscortGroupName then
local DetectedMsg=DetectedItemReportSummary:Text("\n")
DetectedMsgs[#DetectedMsgs+1]=DetectedMsg
self:T(DetectedMsg)
MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),
DetectedMsg,
self.EscortMenuAttackNearbyTargets,
ESCORT._AttackTarget,
self,
DetectedItem
)
else
if self.EscortMenuTargetAssistance then
local DetectedMsg=DetectedItemReportSummary:Text("\n")
self:T(DetectedMsg)
local MenuTargetAssistance=MENU_GROUP:New(self.EscortClient:GetGroup(),EscortGroupData.EscortName,self.EscortMenuTargetAssistance)
MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),
DetectedMsg,
MenuTargetAssistance,
ESCORT._AssistTarget,
self,
EscortGroupData.EscortGroup,
DetectedItem
)
end
end
DetectedTargets=true
end
end
self:F(DetectedMsgs)
if DetectedTargets then
self.EscortGroup:MessageToClient("Reporting detected targets:\n"..table.concat(DetectedMsgs,"\n"),20,self.EscortClient)
else
self.EscortGroup:MessageToClient("No targets detected.",10,self.EscortClient)
end
return true
else
end
end
return false
end
MISSILETRAINER={
ClassName="MISSILETRAINER",
TrackingMissiles={},
}
function MISSILETRAINER._Alive(Client,self)
if self.Briefing then
Client:Message(self.Briefing,15,"Trainer")
end
if self.MenusOnOff==true then
Client:Message("Use the 'Radio Menu' -> 'Other (F10)' -> 'Missile Trainer' menu options to change the Missile Trainer settings (for all players).",15,"Trainer")
Client.MainMenu=MENU_GROUP:New(Client:GetGroup(),"Missile Trainer",nil)
Client.MenuMessages=MENU_GROUP:New(Client:GetGroup(),"Messages",Client.MainMenu)
Client.MenuOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Messages On",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=true})
Client.MenuOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Messages Off",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=false})
Client.MenuTracking=MENU_GROUP:New(Client:GetGroup(),"Tracking",Client.MainMenu)
Client.MenuTrackingToAll=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To All",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=true})
Client.MenuTrackingToTarget=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To Target",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=false})
Client.MenuTrackOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Tracking On",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=true})
Client.MenuTrackOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Tracking Off",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=false})
Client.MenuTrackIncrease=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Frequency Increase",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=-1})
Client.MenuTrackDecrease=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Frequency Decrease",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=1})
Client.MenuAlerts=MENU_GROUP:New(Client:GetGroup(),"Alerts",Client.MainMenu)
Client.MenuAlertsToAll=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To All",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=true})
Client.MenuAlertsToTarget=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To Target",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=false})
Client.MenuHitsOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Hits On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=true})
Client.MenuHitsOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Hits Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=false})
Client.MenuLaunchesOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Launches On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=true})
Client.MenuLaunchesOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Launches Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=false})
Client.MenuDetails=MENU_GROUP:New(Client:GetGroup(),"Details",Client.MainMenu)
Client.MenuDetailsDistanceOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Range On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=true})
Client.MenuDetailsDistanceOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Range Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=false})
Client.MenuDetailsBearingOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Bearing On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=true})
Client.MenuDetailsBearingOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Bearing Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=false})
Client.MenuDistance=MENU_GROUP:New(Client:GetGroup(),"Set distance to plane",Client.MainMenu)
Client.MenuDistance50=MENU_GROUP_COMMAND:New(Client:GetGroup(),"50 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=50/1000})
Client.MenuDistance100=MENU_GROUP_COMMAND:New(Client:GetGroup(),"100 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=100/1000})
Client.MenuDistance150=MENU_GROUP_COMMAND:New(Client:GetGroup(),"150 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=150/1000})
Client.MenuDistance200=MENU_GROUP_COMMAND:New(Client:GetGroup(),"200 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=200/1000})
else
if Client.MainMenu then
Client.MainMenu:Remove()
end
end
local ClientID=Client:GetID()
self:T(ClientID)
if not self.TrackingMissiles[ClientID]then
self.TrackingMissiles[ClientID]={}
end
self.TrackingMissiles[ClientID].Client=Client
if not self.TrackingMissiles[ClientID].MissileData then
self.TrackingMissiles[ClientID].MissileData={}
end
end
function MISSILETRAINER:New(Distance,Briefing)
local self=BASE:Inherit(self,BASE:New())
self:F(Distance)
if Briefing then
self.Briefing=Briefing
end
self.Schedulers={}
self.SchedulerID=0
self.MessageInterval=2
self.MessageLastTime=timer.getTime()
self.Distance=Distance/1000
self:HandleEvent(EVENTS.Shot)
self.DBClients=SET_CLIENT:New():FilterStart()
self.DBClients:ForEachClient(
function(Client)
self:F("ForEach:"..Client.UnitName)
Client:Alive(self._Alive,self)
end
)
self.MessagesOnOff=true
self.TrackingToAll=false
self.TrackingOnOff=true
self.TrackingFrequency=3
self.AlertsToAll=true
self.AlertsHitsOnOff=true
self.AlertsLaunchesOnOff=true
self.DetailsRangeOnOff=true
self.DetailsBearingOnOff=true
self.MenusOnOff=true
self.TrackingMissiles={}
self.TrackingScheduler=SCHEDULER:New(self,self._TrackMissiles,{},0.5,0.05,0)
return self
end
function MISSILETRAINER:InitMessagesOnOff(MessagesOnOff)
self:F(MessagesOnOff)
self.MessagesOnOff=MessagesOnOff
if self.MessagesOnOff==true then
MESSAGE:New("Messages ON",15,"Menu"):ToAll()
else
MESSAGE:New("Messages OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitTrackingToAll(TrackingToAll)
self:F(TrackingToAll)
self.TrackingToAll=TrackingToAll
if self.TrackingToAll==true then
MESSAGE:New("Missile tracking to all players ON",15,"Menu"):ToAll()
else
MESSAGE:New("Missile tracking to all players OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitTrackingOnOff(TrackingOnOff)
self:F(TrackingOnOff)
self.TrackingOnOff=TrackingOnOff
if self.TrackingOnOff==true then
MESSAGE:New("Missile tracking ON",15,"Menu"):ToAll()
else
MESSAGE:New("Missile tracking OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitTrackingFrequency(TrackingFrequency)
self:F(TrackingFrequency)
self.TrackingFrequency=self.TrackingFrequency+TrackingFrequency
if self.TrackingFrequency<0.5 then
self.TrackingFrequency=0.5
end
if self.TrackingFrequency then
MESSAGE:New("Missile tracking frequency is "..self.TrackingFrequency.." seconds.",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitAlertsToAll(AlertsToAll)
self:F(AlertsToAll)
self.AlertsToAll=AlertsToAll
if self.AlertsToAll==true then
MESSAGE:New("Alerts to all players ON",15,"Menu"):ToAll()
else
MESSAGE:New("Alerts to all players OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitAlertsHitsOnOff(AlertsHitsOnOff)
self:F(AlertsHitsOnOff)
self.AlertsHitsOnOff=AlertsHitsOnOff
if self.AlertsHitsOnOff==true then
MESSAGE:New("Alerts Hits ON",15,"Menu"):ToAll()
else
MESSAGE:New("Alerts Hits OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitAlertsLaunchesOnOff(AlertsLaunchesOnOff)
self:F(AlertsLaunchesOnOff)
self.AlertsLaunchesOnOff=AlertsLaunchesOnOff
if self.AlertsLaunchesOnOff==true then
MESSAGE:New("Alerts Launches ON",15,"Menu"):ToAll()
else
MESSAGE:New("Alerts Launches OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitRangeOnOff(DetailsRangeOnOff)
self:F(DetailsRangeOnOff)
self.DetailsRangeOnOff=DetailsRangeOnOff
if self.DetailsRangeOnOff==true then
MESSAGE:New("Range display ON",15,"Menu"):ToAll()
else
MESSAGE:New("Range display OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitBearingOnOff(DetailsBearingOnOff)
self:F(DetailsBearingOnOff)
self.DetailsBearingOnOff=DetailsBearingOnOff
if self.DetailsBearingOnOff==true then
MESSAGE:New("Bearing display OFF",15,"Menu"):ToAll()
else
MESSAGE:New("Bearing display OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitMenusOnOff(MenusOnOff)
self:F(MenusOnOff)
self.MenusOnOff=MenusOnOff
if self.MenusOnOff==true then
MESSAGE:New("Menus are ENABLED (only when a player rejoins a slot)",15,"Menu"):ToAll()
else
MESSAGE:New("Menus are DISABLED",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER._MenuMessages(MenuParameters)
local self=MenuParameters.MenuSelf
if MenuParameters.MessagesOnOff~=nil then
self:InitMessagesOnOff(MenuParameters.MessagesOnOff)
end
if MenuParameters.TrackingToAll~=nil then
self:InitTrackingToAll(MenuParameters.TrackingToAll)
end
if MenuParameters.TrackingOnOff~=nil then
self:InitTrackingOnOff(MenuParameters.TrackingOnOff)
end
if MenuParameters.TrackingFrequency~=nil then
self:InitTrackingFrequency(MenuParameters.TrackingFrequency)
end
if MenuParameters.AlertsToAll~=nil then
self:InitAlertsToAll(MenuParameters.AlertsToAll)
end
if MenuParameters.AlertsHitsOnOff~=nil then
self:InitAlertsHitsOnOff(MenuParameters.AlertsHitsOnOff)
end
if MenuParameters.AlertsLaunchesOnOff~=nil then
self:InitAlertsLaunchesOnOff(MenuParameters.AlertsLaunchesOnOff)
end
if MenuParameters.DetailsRangeOnOff~=nil then
self:InitRangeOnOff(MenuParameters.DetailsRangeOnOff)
end
if MenuParameters.DetailsBearingOnOff~=nil then
self:InitBearingOnOff(MenuParameters.DetailsBearingOnOff)
end
if MenuParameters.Distance~=nil then
self.Distance=MenuParameters.Distance
MESSAGE:New("Hit detection distance set to "..(self.Distance*1000).." meters",15,"Menu"):ToAll()
end
end
function MISSILETRAINER:OnEventShot(EVentData)
self:F({EVentData})
local TrainerSourceDCSUnit=EVentData.IniDCSUnit
local TrainerSourceDCSUnitName=EVentData.IniDCSUnitName
local TrainerWeapon=EVentData.Weapon
local TrainerWeaponName=EVentData.WeaponName
self:T("Missile Launched = "..TrainerWeaponName)
local TrainerTargetDCSUnit=TrainerWeapon:getTarget()
if TrainerTargetDCSUnit then
local TrainerTargetDCSUnitName=Unit.getName(TrainerTargetDCSUnit)
local TrainerTargetSkill=_DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill
self:T(TrainerTargetDCSUnitName)
local Client=self.DBClients:FindClient(TrainerTargetDCSUnitName)
if Client then
local TrainerSourceUnit=UNIT:Find(TrainerSourceDCSUnit)
local TrainerTargetUnit=UNIT:Find(TrainerTargetDCSUnit)
if self.MessagesOnOff==true and self.AlertsLaunchesOnOff==true then
local Message=MESSAGE:New(
string.format("%s launched a %s",
TrainerSourceUnit:GetTypeName(),
TrainerWeaponName
)..self:_AddRange(Client,TrainerWeapon)..self:_AddBearing(Client,TrainerWeapon),5,"Launch Alert")
if self.AlertsToAll then
Message:ToAll()
else
Message:ToClient(Client)
end
end
local ClientID=Client:GetID()
self:T(ClientID)
local MissileData={}
MissileData.TrainerSourceUnit=TrainerSourceUnit
MissileData.TrainerWeapon=TrainerWeapon
MissileData.TrainerTargetUnit=TrainerTargetUnit
MissileData.TrainerWeaponTypeName=TrainerWeapon:getTypeName()
MissileData.TrainerWeaponLaunched=true
table.insert(self.TrackingMissiles[ClientID].MissileData,MissileData)
end
else
if(TrainerWeapon:getTypeName()=="9M311")then
SCHEDULER:New(TrainerWeapon,TrainerWeapon.destroy,{},1)
else
end
end
end
function MISSILETRAINER:_AddRange(Client,TrainerWeapon)
local RangeText=""
if self.DetailsRangeOnOff then
local PositionMissile=TrainerWeapon:getPoint()
local TargetVec3=Client:GetVec3()
local Range=((PositionMissile.x-TargetVec3.x)^2+
(PositionMissile.y-TargetVec3.y)^2+
(PositionMissile.z-TargetVec3.z)^2
)^0.5/1000
RangeText=string.format(", at %4.2fkm",Range)
end
return RangeText
end
function MISSILETRAINER:_AddBearing(Client,TrainerWeapon)
local BearingText=""
if self.DetailsBearingOnOff then
local PositionMissile=TrainerWeapon:getPoint()
local TargetVec3=Client:GetVec3()
self:T2({TargetVec3,PositionMissile})
local DirectionVector={x=PositionMissile.x-TargetVec3.x,y=PositionMissile.y-TargetVec3.y,z=PositionMissile.z-TargetVec3.z}
local DirectionRadians=math.atan2(DirectionVector.z,DirectionVector.x)
if DirectionRadians<0 then
DirectionRadians=DirectionRadians+2*math.pi
end
local DirectionDegrees=DirectionRadians*180/math.pi
BearingText=string.format(", %d degrees",DirectionDegrees)
end
return BearingText
end
function MISSILETRAINER:_TrackMissiles()
self:F2()
local ShowMessages=false
if self.MessagesOnOff and self.MessageLastTime+self.TrackingFrequency<=timer.getTime()then
self.MessageLastTime=timer.getTime()
ShowMessages=true
end
for ClientDataID,ClientData in pairs(self.TrackingMissiles)do
local Client=ClientData.Client
if Client and Client:IsAlive()then
for MissileDataID,MissileData in pairs(ClientData.MissileData)do
self:T3(MissileDataID)
local TrainerSourceUnit=MissileData.TrainerSourceUnit
local TrainerWeapon=MissileData.TrainerWeapon
local TrainerTargetUnit=MissileData.TrainerTargetUnit
local TrainerWeaponTypeName=MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched=MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive()and TrainerSourceUnit and TrainerSourceUnit:IsAlive()and TrainerWeapon and TrainerWeapon:isExist()and TrainerTargetUnit and TrainerTargetUnit:IsAlive()then
local PositionMissile=TrainerWeapon:getPosition().p
local TargetVec3=Client:GetVec3()
local Distance=((PositionMissile.x-TargetVec3.x)^2+
(PositionMissile.y-TargetVec3.y)^2+
(PositionMissile.z-TargetVec3.z)^2
)^0.5/1000
if Distance<=self.Distance then
TrainerWeapon:destroy()
if self.MessagesOnOff==true and self.AlertsHitsOnOff==true then
self:T("killed")
local Message=MESSAGE:New(
string.format("%s launched by %s killed %s",
TrainerWeapon:getTypeName(),
TrainerSourceUnit:GetTypeName(),
TrainerTargetUnit:GetPlayerName()
),15,"Hit Alert")
if self.AlertsToAll==true then
Message:ToAll()
else
Message:ToClient(Client)
end
MissileData=nil
table.remove(ClientData.MissileData,MissileDataID)
self:T(ClientData.MissileData)
end
end
else
if not(TrainerWeapon and TrainerWeapon:isExist())then
if self.MessagesOnOff==true and self.AlertsLaunchesOnOff==true then
local Message=MESSAGE:New(
string.format("%s launched by %s self destructed!",
TrainerWeaponTypeName,
TrainerSourceUnit:GetTypeName()
),5,"Tracking")
if self.AlertsToAll==true then
Message:ToAll()
else
Message:ToClient(Client)
end
end
MissileData=nil
table.remove(ClientData.MissileData,MissileDataID)
self:T(ClientData.MissileData)
end
end
end
else
self.TrackingMissiles[ClientDataID]=nil
end
end
if ShowMessages==true and self.MessagesOnOff==true and self.TrackingOnOff==true then
for ClientDataID,ClientData in pairs(self.TrackingMissiles)do
local Client=ClientData.Client
ClientData.MessageToClient=""
ClientData.MessageToAll=""
for TrackingDataID,TrackingData in pairs(self.TrackingMissiles)do
for MissileDataID,MissileData in pairs(TrackingData.MissileData)do
local TrainerSourceUnit=MissileData.TrainerSourceUnit
local TrainerWeapon=MissileData.TrainerWeapon
local TrainerTargetUnit=MissileData.TrainerTargetUnit
local TrainerWeaponTypeName=MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched=MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive()and TrainerSourceUnit and TrainerSourceUnit:IsAlive()and TrainerWeapon and TrainerWeapon:isExist()and TrainerTargetUnit and TrainerTargetUnit:IsAlive()then
if ShowMessages==true then
local TrackingTo
TrackingTo=string.format("  -> %s",
TrainerWeaponTypeName
)
if ClientDataID==TrackingDataID then
if ClientData.MessageToClient==""then
ClientData.MessageToClient="Missiles to You:\n"
end
ClientData.MessageToClient=ClientData.MessageToClient..TrackingTo..self:_AddRange(ClientData.Client,TrainerWeapon)..self:_AddBearing(ClientData.Client,TrainerWeapon).."\n"
else
if self.TrackingToAll==true then
if ClientData.MessageToAll==""then
ClientData.MessageToAll="Missiles to other Players:\n"
end
ClientData.MessageToAll=ClientData.MessageToAll..TrackingTo..self:_AddRange(ClientData.Client,TrainerWeapon)..self:_AddBearing(ClientData.Client,TrainerWeapon).." ( "..TrainerTargetUnit:GetPlayerName().." )\n"
end
end
end
end
end
end
if ClientData.MessageToClient~=""or ClientData.MessageToAll~=""then
local Message=MESSAGE:New(ClientData.MessageToClient..ClientData.MessageToAll,1,"Tracking"):ToClient(Client)
end
end
end
return true
end
ATC_GROUND={
ClassName="ATC_GROUND",
SetClient=nil,
Airbases=nil,
AirbaseNames=nil,
}
function ATC_GROUND:New(Airbases,AirbaseList)
local self=BASE:Inherit(self,BASE:New())
self:E({self.ClassName,Airbases})
self.Airbases=Airbases
self.AirbaseList=AirbaseList
self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart()
for AirbaseID,Airbase in pairs(self.Airbases)do
if Airbase.ZoneBoundary then
Airbase.ZoneBoundary=ZONE_POLYGON_BASE:New("Boundary "..AirbaseID,Airbase.ZoneBoundary)
else
Airbase.ZoneBoundary=_DATABASE:FindAirbase(AirbaseID):GetZone()
end
Airbase.ZoneRunways={}
if Airbase.PointsRunways then
for PointsRunwayID,PointsRunway in pairs(Airbase.PointsRunways)do
Airbase.ZoneRunways[PointsRunwayID]=ZONE_POLYGON_BASE:New("Runway "..PointsRunwayID,PointsRunway)
end
end
Airbase.Monitor=self.AirbaseList and false or true
end
for AirbaseID,AirbaseName in pairs(self.AirbaseList or{})do
self.Airbases[AirbaseName].Monitor=true
end
self.SetClient:ForEachClient(
function(Client)
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
Client:SetState(self,"Taxi",false)
end
)
SSB=USERFLAG:New("SSB")
SSB:Set(100)
return self
end
function ATC_GROUND:SmokeRunways(SmokeColor)
for AirbaseID,Airbase in pairs(self.Airbases)do
for PointsRunwayID,PointsRunway in pairs(Airbase.PointsRunways)do
Airbase.ZoneRunways[PointsRunwayID]:SmokeZone(SmokeColor)
end
end
end
function ATC_GROUND:SetKickSpeed(KickSpeed,Airbase)
if not Airbase then
self.KickSpeed=KickSpeed
else
self.Airbases[Airbase].KickSpeed=KickSpeed
end
return self
end
function ATC_GROUND:SetKickSpeedKmph(KickSpeed,Airbase)
self:SetKickSpeed(UTILS.KmphToMps(KickSpeed),Airbase)
return self
end
function ATC_GROUND:SetKickSpeedMiph(KickSpeedMiph,Airbase)
self:SetKickSpeed(UTILS.MiphToMps(KickSpeedMiph),Airbase)
return self
end
function ATC_GROUND:SetMaximumKickSpeed(MaximumKickSpeed,Airbase)
if not Airbase then
self.MaximumKickSpeed=MaximumKickSpeed
else
self.Airbases[Airbase].MaximumKickSpeed=MaximumKickSpeed
end
return self
end
function ATC_GROUND:SetMaximumKickSpeedKmph(MaximumKickSpeed,Airbase)
self:SetMaximumKickSpeed(UTILS.KmphToMps(MaximumKickSpeed),Airbase)
return self
end
function ATC_GROUND:SetMaximumKickSpeedMiph(MaximumKickSpeedMiph,Airbase)
self:SetMaximumKickSpeed(UTILS.MiphToMps(MaximumKickSpeedMiph),Airbase)
return self
end
function ATC_GROUND:_AirbaseMonitor()
self.SetClient:ForEachClient(
function(Client)
if Client:IsAlive()then
local IsOnGround=Client:InAir()==false
for AirbaseID,AirbaseMeta in pairs(self.Airbases)do
self:E(AirbaseID,AirbaseMeta.KickSpeed)
if AirbaseMeta.Monitor==true and Client:IsInZone(AirbaseMeta.ZoneBoundary)then
local NotInRunwayZone=true
for ZoneRunwayID,ZoneRunway in pairs(AirbaseMeta.ZoneRunways)do
NotInRunwayZone=(Client:IsNotInZone(ZoneRunway)==true)and NotInRunwayZone or false
end
if NotInRunwayZone then
if IsOnGround then
local Taxi=Client:GetState(self,"Taxi")
self:E(Taxi)
if Taxi==false then
local Velocity=VELOCITY:New(AirbaseMeta.KickSpeed or self.KickSpeed)
Client:Message("Welcome to "..AirbaseID..". The maximum taxiing speed is "..
Velocity:ToString(),20,"ATC")
Client:SetState(self,"Taxi",true)
end
local Velocity=VELOCITY_POSITIONABLE:New(Client)
local IsAboveRunway=Client:IsAboveRunway()
self:T(IsAboveRunway,IsOnGround)
if IsOnGround then
local Speeding=false
if AirbaseMeta.MaximumKickSpeed then
if Velocity:Get()>AirbaseMeta.MaximumKickSpeed then
Speeding=true
end
else
if Velocity:Get()>self.MaximumKickSpeed then
Speeding=true
end
end
if Speeding==true then
MESSAGE:New("Penalty! Player "..Client:GetPlayerName()..
" has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll()
Client:Destroy()
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
end
end
if IsOnGround then
local Speeding=false
if AirbaseMeta.KickSpeed then
if Velocity:Get()>AirbaseMeta.KickSpeed then
Speeding=true
end
else
if Velocity:Get()>self.KickSpeed then
Speeding=true
end
end
if Speeding==true then
local IsSpeeding=Client:GetState(self,"Speeding")
if IsSpeeding==true then
local SpeedingWarnings=Client:GetState(self,"Warnings")
self:T(SpeedingWarnings)
if SpeedingWarnings<=3 then
Client:Message("Warning "..SpeedingWarnings.."/3! Airbase traffic rule violation! Slow down now! Your speed is "..
Velocity:ToString(),5,"ATC")
Client:SetState(self,"Warnings",SpeedingWarnings+1)
else
MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll()
Client:Destroy()
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
end
else
Client:Message("Attention! You are speeding on the taxiway, slow down! Your speed is "..
Velocity:ToString(),5,"ATC")
Client:SetState(self,"Speeding",true)
Client:SetState(self,"Warnings",1)
end
else
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
end
end
if IsOnGround and not IsAboveRunway then
local IsOffRunway=Client:GetState(self,"IsOffRunway")
if IsOffRunway==true then
local OffRunwayWarnings=Client:GetState(self,"OffRunwayWarnings")
self:T(OffRunwayWarnings)
if OffRunwayWarnings<=3 then
Client:Message("Warning "..OffRunwayWarnings.."/3! Airbase traffic rule violation! Get back on the taxi immediately!",5,"ATC")
Client:SetState(self,"OffRunwayWarnings",OffRunwayWarnings+1)
else
MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll()
Client:Destroy()
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
end
else
Client:Message("Attention! You are off the taxiway. Get back on the taxiway immediately!",5,"ATC")
Client:SetState(self,"IsOffRunway",true)
Client:SetState(self,"OffRunwayWarnings",1)
end
else
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
end
end
else
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
local Taxi=Client:GetState(self,"Taxi")
if Taxi==true then
Client:Message("You have progressed to the runway ... Await take-off clearance ...",20,"ATC")
Client:SetState(self,"Taxi",false)
end
end
end
end
else
Client:SetState(self,"Taxi",false)
end
end
)
return true
end
ATC_GROUND_UNIVERSAL={
ClassName="ATC_GROUND_UNIVERSAL",
Version="0.0.1",
SetClient=nil,
Airbases=nil,
AirbaseList=nil,
KickSpeed=nil,
}
function ATC_GROUND_UNIVERSAL:New(AirbaseList)
local self=BASE:Inherit(self,BASE:New())
self:E({self.ClassName})
self.Airbases={}
for _name,_ in pairs(_DATABASE.AIRBASES)do
self.Airbases[_name]={}
end
self.AirbaseList=AirbaseList
self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart()
for AirbaseID,Airbase in pairs(self.Airbases)do
if Airbase.ZoneBoundary then
Airbase.ZoneBoundary=ZONE_POLYGON_BASE:New("Boundary "..AirbaseID,Airbase.ZoneBoundary)
else
Airbase.ZoneBoundary=_DATABASE:FindAirbase(AirbaseID):GetZone()
end
Airbase.ZoneRunways=AIRBASE:FindByName(AirbaseID):GetRunways()
Airbase.Monitor=self.AirbaseList and false or true
end
for AirbaseID,AirbaseName in pairs(self.AirbaseList or{})do
self.Airbases[AirbaseName].Monitor=true
end
self.SetClient:ForEachClient(
function(Client)
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
Client:SetState(self,"Taxi",false)
end
)
SSB=USERFLAG:New("SSB")
SSB:Set(100)
self.KickSpeed=UTILS.KnotsToMps(10)
self:SetMaximumKickSpeedMiph(30)
return self
end
function ATC_GROUND_UNIVERSAL:SetAirbaseBoundaries(Airbase,Zone)
self.Airbases[Airbase].ZoneBoundary=Zone
return self
end
function ATC_GROUND_UNIVERSAL:SmokeRunways(SmokeColor)
local SmokeColor=SmokeColor or SMOKECOLOR.Red
for AirbaseID,Airbase in pairs(self.Airbases)do
if Airbase.ZoneRunways then
for _,_runwaydata in pairs(Airbase.ZoneRunways)do
local runwaydata=_runwaydata
runwaydata.zone:SmokeZone(SmokeColor)
end
end
end
return self
end
function ATC_GROUND_UNIVERSAL:DrawRunways(Color)
local Color=Color or{1,0,0}
for AirbaseID,Airbase in pairs(self.Airbases)do
if Airbase.ZoneRunways then
for _,_runwaydata in pairs(Airbase.ZoneRunways)do
local runwaydata=_runwaydata
runwaydata.zone:DrawZone(-1,Color)
end
end
end
return self
end
function ATC_GROUND_UNIVERSAL:DrawBoundaries(Color)
local Color=Color or{1,0,0}
for AirbaseID,Airbase in pairs(self.Airbases)do
if Airbase.ZoneBoundary then
Airbase.ZoneBoundary:DrawZone(-1,Color)
end
end
return self
end
function ATC_GROUND_UNIVERSAL:SetKickSpeed(KickSpeed,Airbase)
if not Airbase then
self.KickSpeed=KickSpeed
else
self.Airbases[Airbase].KickSpeed=KickSpeed
end
return self
end
function ATC_GROUND_UNIVERSAL:SetKickSpeedKmph(KickSpeed,Airbase)
self:SetKickSpeed(UTILS.KmphToMps(KickSpeed),Airbase)
return self
end
function ATC_GROUND_UNIVERSAL:SetKickSpeedMiph(KickSpeedMiph,Airbase)
self:SetKickSpeed(UTILS.MiphToMps(KickSpeedMiph),Airbase)
return self
end
function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeed(MaximumKickSpeed,Airbase)
if not Airbase then
self.MaximumKickSpeed=MaximumKickSpeed
else
self.Airbases[Airbase].MaximumKickSpeed=MaximumKickSpeed
end
return self
end
function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeedKmph(MaximumKickSpeed,Airbase)
self:SetMaximumKickSpeed(UTILS.KmphToMps(MaximumKickSpeed),Airbase)
return self
end
function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeedMiph(MaximumKickSpeedMiph,Airbase)
self:SetMaximumKickSpeed(UTILS.MiphToMps(MaximumKickSpeedMiph),Airbase)
return self
end
function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
self.SetClient:ForEachClient(
function(Client)
if Client:IsAlive()then
local IsOnGround=Client:InAir()==false
for AirbaseID,AirbaseMeta in pairs(self.Airbases)do
self:E(AirbaseID,AirbaseMeta.KickSpeed)
if AirbaseMeta.Monitor==true and Client:IsInZone(AirbaseMeta.ZoneBoundary)then
local NotInRunwayZone=true
if AirbaseMeta.ZoneRunways then
for _,_runwaydata in pairs(AirbaseMeta.ZoneRunways)do
local runwaydata=_runwaydata
NotInRunwayZone=(Client:IsNotInZone(_runwaydata.zone)==true)and NotInRunwayZone or false
end
end
if NotInRunwayZone then
if IsOnGround then
local Taxi=Client:GetState(self,"Taxi")
self:E(Taxi)
if Taxi==false then
local Velocity=VELOCITY:New(AirbaseMeta.KickSpeed or self.KickSpeed)
Client:Message("Welcome to "..AirbaseID..". The maximum taxiing speed is "..
Velocity:ToString(),20,"ATC")
Client:SetState(self,"Taxi",true)
end
local Velocity=VELOCITY_POSITIONABLE:New(Client)
local IsAboveRunway=Client:IsAboveRunway()
self:T({IsAboveRunway,IsOnGround,Velocity:Get()})
if IsOnGround then
local Speeding=false
if AirbaseMeta.MaximumKickSpeed then
if Velocity:Get()>AirbaseMeta.MaximumKickSpeed then
Speeding=true
end
else
if Velocity:Get()>self.MaximumKickSpeed then
Speeding=true
end
end
if Speeding==true then
MESSAGE:New("Penalty! Player "..Client:GetPlayerName()..
" has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll()
Client:Destroy()
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
end
end
if IsOnGround then
local Speeding=false
if AirbaseMeta.KickSpeed then
if Velocity:Get()>AirbaseMeta.KickSpeed then
Speeding=true
end
else
if Velocity:Get()>self.KickSpeed then
Speeding=true
end
end
if Speeding==true then
local IsSpeeding=Client:GetState(self,"Speeding")
if IsSpeeding==true then
local SpeedingWarnings=Client:GetState(self,"Warnings")
self:T(SpeedingWarnings)
if SpeedingWarnings<=3 then
Client:Message("Warning "..SpeedingWarnings.."/3! Airbase traffic rule violation! Slow down now! Your speed is "..
Velocity:ToString(),5,"ATC")
Client:SetState(self,"Warnings",SpeedingWarnings+1)
else
MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll()
Client:Destroy()
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
end
else
Client:Message("Attention! You are speeding on the taxiway, slow down! Your speed is "..
Velocity:ToString(),5,"ATC")
Client:SetState(self,"Speeding",true)
Client:SetState(self,"Warnings",1)
end
else
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
end
end
if IsOnGround and not IsAboveRunway then
local IsOffRunway=Client:GetState(self,"IsOffRunway")
if IsOffRunway==true then
local OffRunwayWarnings=Client:GetState(self,"OffRunwayWarnings")
self:T(OffRunwayWarnings)
if OffRunwayWarnings<=3 then
Client:Message("Warning "..OffRunwayWarnings.."/3! Airbase traffic rule violation! Get back on the taxi immediately!",5,"ATC")
Client:SetState(self,"OffRunwayWarnings",OffRunwayWarnings+1)
else
MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll()
Client:Destroy()
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
end
else
Client:Message("Attention! You are off the taxiway. Get back on the taxiway immediately!",5,"ATC")
Client:SetState(self,"IsOffRunway",true)
Client:SetState(self,"OffRunwayWarnings",1)
end
else
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
end
end
else
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
local Taxi=Client:GetState(self,"Taxi")
if Taxi==true then
Client:Message("You have progressed to the runway ... Await take-off clearance ...",20,"ATC")
Client:SetState(self,"Taxi",false)
end
end
end
end
else
Client:SetState(self,"Taxi",false)
end
end
)
return true
end
function ATC_GROUND_UNIVERSAL:Start(RepeatScanSeconds)
RepeatScanSeconds=RepeatScanSeconds or 0.05
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
return self
end
ATC_GROUND_CAUCASUS={
ClassName="ATC_GROUND_CAUCASUS",
}
function ATC_GROUND_CAUCASUS:New(AirbaseNames)
local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames))
self:SetKickSpeedKmph(50)
self:SetMaximumKickSpeedKmph(150)
return self
end
function ATC_GROUND_CAUCASUS:Start(RepeatScanSeconds)
RepeatScanSeconds=RepeatScanSeconds or 0.05
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
end
ATC_GROUND_NEVADA={
ClassName="ATC_GROUND_NEVADA",
}
function ATC_GROUND_NEVADA:New(AirbaseNames)
local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames))
self:SetKickSpeedKmph(50)
self:SetMaximumKickSpeedKmph(150)
return self
end
function ATC_GROUND_NEVADA:Start(RepeatScanSeconds)
RepeatScanSeconds=RepeatScanSeconds or 0.05
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
end
ATC_GROUND_NORMANDY={
ClassName="ATC_GROUND_NORMANDY",
}
function ATC_GROUND_NORMANDY:New(AirbaseNames)
local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames))
self:SetKickSpeedKmph(40)
self:SetMaximumKickSpeedKmph(100)
return self
end
function ATC_GROUND_NORMANDY:Start(RepeatScanSeconds)
RepeatScanSeconds=RepeatScanSeconds or 0.05
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
end
ATC_GROUND_PERSIANGULF={
ClassName="ATC_GROUND_PERSIANGULF",
}
function ATC_GROUND_PERSIANGULF:New(AirbaseNames)
local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames))
self:SetKickSpeedKmph(50)
self:SetMaximumKickSpeedKmph(150)
end
function ATC_GROUND_PERSIANGULF:Start(RepeatScanSeconds)
RepeatScanSeconds=RepeatScanSeconds or 0.05
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
end
ATC_GROUND_MARIANAISLANDS={
ClassName="ATC_GROUND_MARIANAISLANDS",
}
function ATC_GROUND_MARIANAISLANDS:New(AirbaseNames)
local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(self.Airbases,AirbaseNames))
self:SetKickSpeedKmph(50)
self:SetMaximumKickSpeedKmph(150)
return self
end
function ATC_GROUND_MARIANAISLANDS:Start(RepeatScanSeconds)
RepeatScanSeconds=RepeatScanSeconds or 0.05
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds)
end
do
DETECTION_BASE={
ClassName="DETECTION_BASE",
DetectionSetGroup=nil,
DetectionRange=nil,
DetectedObjects={},
DetectionRun=0,
DetectedObjectsIdentified={},
DetectedItems={},
DetectedItemsByIndex={},
}
function DETECTION_BASE:New(DetectionSet)
local self=BASE:Inherit(self,FSM:New())
self.DetectedItemCount=0
self.DetectedItemMax=0
self.DetectedItems={}
self.DetectionSet=DetectionSet
self.RefreshTimeInterval=30
self:InitDetectVisual(nil)
self:InitDetectOptical(nil)
self:InitDetectRadar(nil)
self:InitDetectRWR(nil)
self:InitDetectIRST(nil)
self:InitDetectDLINK(nil)
self:FilterCategories({
Unit.Category.AIRPLANE,
Unit.Category.GROUND_UNIT,
Unit.Category.HELICOPTER,
Unit.Category.SHIP,
Unit.Category.STRUCTURE
})
self:SetFriendliesRange(6000)
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Detecting")
self:AddTransition("Detecting","Detect","Detecting")
self:AddTransition("Detecting","Detection","Detecting")
self:AddTransition("Detecting","Detected","Detecting")
self:AddTransition("Detecting","DetectedItem","Detecting")
self:AddTransition("*","Stop","Stopped")
return self
end
do
function DETECTION_BASE:onafterStart(From,Event,To)
self:__Detect(1)
end
function DETECTION_BASE:onafterDetect(From,Event,To)
local DetectDelay=0.1
self.DetectionCount=0
self.DetectionRun=0
self:UnIdentifyAllDetectedObjects()
local DetectionTimeStamp=timer.getTime()
for DetectionObjectName,DetectedObjectData in pairs(self.DetectedObjects)do
self.DetectedObjects[DetectionObjectName].IsDetected=false
self.DetectedObjects[DetectionObjectName].IsVisible=false
self.DetectedObjects[DetectionObjectName].KnowDistance=nil
self.DetectedObjects[DetectionObjectName].LastTime=nil
self.DetectedObjects[DetectionObjectName].LastPos=nil
self.DetectedObjects[DetectionObjectName].LastVelocity=nil
self.DetectedObjects[DetectionObjectName].Distance=10000000
end
self.DetectionCount=self:CountAliveRecce()
local DetectionInterval=self.DetectionCount/(self.RefreshTimeInterval-1)
self:ForEachAliveRecce(function(DetectionGroup)
self:__Detection(DetectDelay,DetectionGroup,DetectionTimeStamp)
DetectDelay=DetectDelay+DetectionInterval
end)
self:__Detect(-self.RefreshTimeInterval)
end
function DETECTION_BASE:CountAliveRecce()
return self.DetectionSet:CountAlive()
end
function DETECTION_BASE:ForEachAliveRecce(IteratorFunction,...)
self:F2(arg)
self.DetectionSet:ForEachGroupAlive(IteratorFunction,arg)
return self
end
function DETECTION_BASE:onafterDetection(From,Event,To,Detection,DetectionTimeStamp)
self.DetectionRun=self.DetectionRun+1
local HasDetectedObjects=false
if Detection and Detection:IsAlive()then
local DetectionGroupName=Detection:GetName()
local DetectionUnit=Detection:GetUnit(1)
local DetectedUnits={}
local DetectedTargets=Detection:GetDetectedTargets(
self.DetectVisual,
self.DetectOptical,
self.DetectRadar,
self.DetectIRST,
self.DetectRWR,
self.DetectDLINK
)
self:F({DetectedTargets=DetectedTargets})
for DetectionObjectID,Detection in pairs(DetectedTargets)do
local DetectedObject=Detection.object
if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then
local DetectedObjectName=DetectedObject:getName()
if not self.DetectedObjects[DetectedObjectName]then
self.DetectedObjects[DetectedObjectName]=self.DetectedObjects[DetectedObjectName]or{}
self.DetectedObjects[DetectedObjectName].Name=DetectedObjectName
self.DetectedObjects[DetectedObjectName].Object=DetectedObject
end
end
end
for DetectionObjectName,DetectedObjectData in pairs(self.DetectedObjects)do
local DetectedObject=DetectedObjectData.Object
if DetectedObject:isExist()then
local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity=DetectionUnit:IsTargetDetected(
DetectedObject,
self.DetectVisual,
self.DetectOptical,
self.DetectRadar,
self.DetectIRST,
self.DetectRWR,
self.DetectDLINK
)
local DetectionAccepted=true
local DetectedObjectName=DetectedObject:getName()
local DetectedObjectType=DetectedObject:getTypeName()
local DetectedObjectVec3=DetectedObject:getPoint()
local DetectedObjectVec2={x=DetectedObjectVec3.x,y=DetectedObjectVec3.z}
local DetectionGroupVec3=Detection:GetVec3()or{x=0,y=0,z=0}
local DetectionGroupVec2={x=DetectionGroupVec3.x,y=DetectionGroupVec3.z}
local Distance=((DetectedObjectVec3.x-DetectionGroupVec3.x)^2+
(DetectedObjectVec3.y-DetectionGroupVec3.y)^2+
(DetectedObjectVec3.z-DetectionGroupVec3.z)^2
)^0.5/1000
local DetectedUnitCategory=DetectedObject:getDesc().category
DetectionAccepted=self._.FilterCategories[DetectedUnitCategory]~=nil and DetectionAccepted or false
if self.AcceptRange and Distance*1000>self.AcceptRange then
DetectionAccepted=false
end
if self.AcceptZones then
local AnyZoneDetection=false
for AcceptZoneID,AcceptZone in pairs(self.AcceptZones)do
local AcceptZone=AcceptZone
if AcceptZone:IsVec2InZone(DetectedObjectVec2)then
AnyZoneDetection=true
end
end
if not AnyZoneDetection then
DetectionAccepted=false
end
end
if self.RejectZones then
for RejectZoneID,RejectZone in pairs(self.RejectZones)do
local RejectZone=RejectZone
if RejectZone:IsVec2InZone(DetectedObjectVec2)==true then
DetectionAccepted=false
end
end
end
if not self.DetectedObjects[DetectedObjectName]and TargetIsVisible and self.DistanceProbability then
local DistanceFactor=Distance/4
local DistanceProbabilityReversed=(1-self.DistanceProbability)*DistanceFactor
local DistanceProbability=1-DistanceProbabilityReversed
DistanceProbability=DistanceProbability*30/300
local Probability=math.random()
if Probability>DistanceProbability then
DetectionAccepted=false
end
end
if not self.DetectedObjects[DetectedObjectName]and TargetIsVisible and self.AlphaAngleProbability then
local NormalVec2={x=DetectedObjectVec2.x-DetectionGroupVec2.x,y=DetectedObjectVec2.y-DetectionGroupVec2.y}
local AlphaAngle=math.atan2(NormalVec2.y,NormalVec2.x)
local Sinus=math.sin(AlphaAngle)
local AlphaAngleProbabilityReversed=(1-self.AlphaAngleProbability)*(1-Sinus)
local AlphaAngleProbability=1-AlphaAngleProbabilityReversed
AlphaAngleProbability=AlphaAngleProbability*30/300
local Probability=math.random()
if Probability>AlphaAngleProbability then
DetectionAccepted=false
end
end
if not self.DetectedObjects[DetectedObjectName]and TargetIsVisible and self.ZoneProbability then
for ZoneDataID,ZoneData in pairs(self.ZoneProbability)do
self:F({ZoneData})
local ZoneObject=ZoneData[1]
local ZoneProbability=ZoneData[2]
ZoneProbability=ZoneProbability*30/300
if ZoneObject:IsVec2InZone(DetectedObjectVec2)==true then
local Probability=math.random()
if Probability>ZoneProbability then
DetectionAccepted=false
break
end
end
end
end
if DetectionAccepted then
HasDetectedObjects=true
self.DetectedObjects[DetectedObjectName]=self.DetectedObjects[DetectedObjectName]or{}
self.DetectedObjects[DetectedObjectName].Name=DetectedObjectName
if TargetIsDetected and TargetIsDetected==true then
self.DetectedObjects[DetectedObjectName].IsDetected=TargetIsDetected
end
if TargetIsDetected and TargetIsVisible and TargetIsVisible==true then
self.DetectedObjects[DetectedObjectName].IsVisible=TargetIsDetected and TargetIsVisible
end
if TargetIsDetected and not self.DetectedObjects[DetectedObjectName].KnowType then
self.DetectedObjects[DetectedObjectName].KnowType=TargetIsDetected and TargetKnowType
end
self.DetectedObjects[DetectedObjectName].KnowDistance=TargetKnowDistance
self.DetectedObjects[DetectedObjectName].LastTime=(TargetIsDetected and TargetIsVisible==false)and TargetLastTime
self.DetectedObjects[DetectedObjectName].LastPos=(TargetIsDetected and TargetIsVisible==false)and TargetLastPos
self.DetectedObjects[DetectedObjectName].LastVelocity=(TargetIsDetected and TargetIsVisible==false)and TargetLastVelocity
if not self.DetectedObjects[DetectedObjectName].Distance or(Distance and self.DetectedObjects[DetectedObjectName].Distance>Distance)then
self.DetectedObjects[DetectedObjectName].Distance=Distance
end
self.DetectedObjects[DetectedObjectName].DetectionTimeStamp=DetectionTimeStamp
self:F({DetectedObject=self.DetectedObjects[DetectedObjectName]})
local DetectedUnit=UNIT:FindByName(DetectedObjectName)
DetectedUnits[DetectedObjectName]=DetectedUnit
else
self:F({DetectedObject="No more detection for "..DetectedObjectName})
if self.DetectedObjects[DetectedObjectName]then
self.DetectedObjects[DetectedObjectName]=nil
end
end
else
self:F("Removing from DetectedObjects: "..DetectionObjectName)
self.DetectedObjects[DetectionObjectName]=nil
end
end
if HasDetectedObjects then
self:__Detected(0.1,DetectedUnits)
end
end
if self.DetectionCount>0 and self.DetectionRun==self.DetectionCount then
for DetectedObjectName,DetectedObject in pairs(self.DetectedObjects)do
if self.DetectedObjects[DetectedObjectName].IsDetected==true and self.DetectedObjects[DetectedObjectName].DetectionTimeStamp+300<=DetectionTimeStamp then
self.DetectedObjects[DetectedObjectName].IsDetected=false
end
end
self:CreateDetectionItems()
for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
self:UpdateDetectedItemDetection(DetectedItem)
self:CleanDetectionItem(DetectedItem,DetectedItemID)
if DetectedItem then
self:__DetectedItem(0.1,DetectedItem)
end
end
end
end
end
do
function DETECTION_BASE:CleanDetectionItem(DetectedItem,DetectedItemID)
local DetectedSet=DetectedItem.Set
if DetectedSet:Count()==0 then
self:RemoveDetectedItem(DetectedItemID)
end
return self
end
function DETECTION_BASE:ForgetDetectedUnit(UnitName)
local DetectedItems=self:GetDetectedItems()
for DetectedItemIndex,DetectedItem in pairs(DetectedItems)do
local DetectedSet=self:GetDetectedItemSet(DetectedItem)
if DetectedSet then
DetectedSet:RemoveUnitsByName(UnitName)
end
end
return self
end
function DETECTION_BASE:CreateDetectionItems()
self:F("Error, in DETECTION_BASE class...")
return self
end
end
do
function DETECTION_BASE:InitDetectVisual(DetectVisual)
self.DetectVisual=DetectVisual
return self
end
function DETECTION_BASE:InitDetectOptical(DetectOptical)
self:F2()
self.DetectOptical=DetectOptical
return self
end
function DETECTION_BASE:InitDetectRadar(DetectRadar)
self:F2()
self.DetectRadar=DetectRadar
return self
end
function DETECTION_BASE:InitDetectIRST(DetectIRST)
self:F2()
self.DetectIRST=DetectIRST
return self
end
function DETECTION_BASE:InitDetectRWR(DetectRWR)
self:F2()
self.DetectRWR=DetectRWR
return self
end
function DETECTION_BASE:InitDetectDLINK(DetectDLINK)
self:F2()
self.DetectDLINK=DetectDLINK
return self
end
end
do
function DETECTION_BASE:FilterCategories(FilterCategories)
self:F2()
self._.FilterCategories={}
if type(FilterCategories)=="table"then
for CategoryID,Category in pairs(FilterCategories)do
self._.FilterCategories[Category]=Category
end
else
self._.FilterCategories[FilterCategories]=FilterCategories
end
return self
end
end
do
function DETECTION_BASE:SetRefreshTimeInterval(RefreshTimeInterval)
self:F2()
self.RefreshTimeInterval=RefreshTimeInterval
return self
end
end
do
function DETECTION_BASE:SetFriendliesRange(FriendliesRange)
self:F2()
self.FriendliesRange=FriendliesRange
return self
end
end
do
function DETECTION_BASE:SetIntercept(Intercept,InterceptDelay)
self:F2()
self.Intercept=Intercept
self.InterceptDelay=InterceptDelay
return self
end
end
do
function DETECTION_BASE:SetAcceptRange(AcceptRange)
self:F2()
self.AcceptRange=AcceptRange
return self
end
function DETECTION_BASE:SetAcceptZones(AcceptZones)
self:F2()
if type(AcceptZones)=="table"then
if AcceptZones.ClassName and AcceptZones:IsInstanceOf(ZONE_BASE)then
self.AcceptZones={AcceptZones}
else
self.AcceptZones=AcceptZones
end
else
self:F({"AcceptZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object",AcceptZones})
error()
end
return self
end
function DETECTION_BASE:SetRejectZones(RejectZones)
self:F2()
if type(RejectZones)=="table"then
if RejectZones.ClassName and RejectZones:IsInstanceOf(ZONE_BASE)then
self.RejectZones={RejectZones}
else
self.RejectZones=RejectZones
end
else
self:F({"RejectZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object",RejectZones})
error()
end
return self
end
end
do
function DETECTION_BASE:SetDistanceProbability(DistanceProbability)
self:F2()
self.DistanceProbability=DistanceProbability
return self
end
function DETECTION_BASE:SetAlphaAngleProbability(AlphaAngleProbability)
self:F2()
self.AlphaAngleProbability=AlphaAngleProbability
return self
end
function DETECTION_BASE:SetZoneProbability(ZoneArray)
self:F2()
self.ZoneProbability=ZoneArray
return self
end
end
do
function DETECTION_BASE:AcceptChanges(DetectedItem)
DetectedItem.Changed=false
DetectedItem.Changes={}
return self
end
function DETECTION_BASE:AddChangeItem(DetectedItem,ChangeCode,ItemUnitType)
DetectedItem.Changed=true
local ID=DetectedItem.ID
DetectedItem.Changes=DetectedItem.Changes or{}
DetectedItem.Changes[ChangeCode]=DetectedItem.Changes[ChangeCode]or{}
DetectedItem.Changes[ChangeCode].ID=ID
DetectedItem.Changes[ChangeCode].ItemUnitType=ItemUnitType
self:F({"Change on Detected Item:",DetectedItemID=DetectedItem.ID,ChangeCode=ChangeCode,ItemUnitType=ItemUnitType})
return self
end
function DETECTION_BASE:AddChangeUnit(DetectedItem,ChangeCode,ChangeUnitType)
DetectedItem.Changed=true
local ID=DetectedItem.ID
DetectedItem.Changes=DetectedItem.Changes or{}
DetectedItem.Changes[ChangeCode]=DetectedItem.Changes[ChangeCode]or{}
DetectedItem.Changes[ChangeCode][ChangeUnitType]=DetectedItem.Changes[ChangeCode][ChangeUnitType]or 0
DetectedItem.Changes[ChangeCode][ChangeUnitType]=DetectedItem.Changes[ChangeCode][ChangeUnitType]+1
DetectedItem.Changes[ChangeCode].ID=ID
self:F({"Change on Detected Unit:",DetectedItemID=DetectedItem.ID,ChangeCode=ChangeCode,ChangeUnitType=ChangeUnitType})
return self
end
end
do
function DETECTION_BASE:SetFriendlyPrefixes(FriendlyPrefixes)
self.FriendlyPrefixes=self.FriendlyPrefixes or{}
if type(FriendlyPrefixes)~="table"then
FriendlyPrefixes={FriendlyPrefixes}
end
for PrefixID,Prefix in pairs(FriendlyPrefixes)do
self:F({FriendlyPrefix=Prefix})
self.FriendlyPrefixes[Prefix]=Prefix
end
return self
end
function DETECTION_BASE:IsFriendliesNearBy(DetectedItem,Category)
return(DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category]~=nil)or false
end
function DETECTION_BASE:GetFriendliesNearBy(DetectedItem,Category)
return DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category]
end
function DETECTION_BASE:IsFriendliesNearIntercept(DetectedItem)
return DetectedItem.FriendliesNearIntercept~=nil or false
end
function DETECTION_BASE:GetFriendliesNearIntercept(DetectedItem)
return DetectedItem.FriendliesNearIntercept
end
function DETECTION_BASE:GetFriendliesDistance(DetectedItem)
return DetectedItem.FriendliesDistance
end
function DETECTION_BASE:IsPlayersNearBy(DetectedItem)
return DetectedItem.PlayersNearBy~=nil
end
function DETECTION_BASE:GetPlayersNearBy(DetectedItem)
return DetectedItem.PlayersNearBy
end
function DETECTION_BASE:ReportFriendliesNearBy(TargetData)
local DetectedItem=TargetData.DetectedItem
local DetectedSet=TargetData.DetectedItem.Set
local DetectedUnit=DetectedSet:GetFirst()
DetectedItem.FriendliesNearBy=nil
if DetectedUnit and DetectedUnit:IsAlive()then
local DetectedUnitCoord=DetectedUnit:GetCoordinate()
local InterceptCoord=TargetData.InterceptCoord or DetectedUnitCoord
local SphereSearch={
id=world.VolumeType.SPHERE,
params={
point=InterceptCoord:GetVec3(),
radius=self.FriendliesRange,
}
}
local FindNearByFriendlies=function(FoundDCSUnit,ReportGroupData)
local DetectedItem=ReportGroupData.DetectedItem
local DetectedSet=ReportGroupData.DetectedItem.Set
local DetectedUnit=DetectedSet:GetFirst()
local DetectedUnitCoord=DetectedUnit:GetCoordinate()
local InterceptCoord=ReportGroupData.InterceptCoord or DetectedUnitCoord
local ReportSetGroup=ReportGroupData.ReportSetGroup
local EnemyCoalition=DetectedUnit:GetCoalition()
local FoundUnitCoalition=FoundDCSUnit:getCoalition()
local FoundUnitCategory=FoundDCSUnit:getDesc().category
local FoundUnitName=FoundDCSUnit:getName()
local FoundUnitGroupName=FoundDCSUnit:getGroup():getName()
local EnemyUnitName=DetectedUnit:GetName()
local FoundUnitInReportSetGroup=ReportSetGroup:FindGroup(FoundUnitGroupName)~=nil
if FoundUnitInReportSetGroup==true then
for PrefixID,Prefix in pairs(self.FriendlyPrefixes or{})do
if string.find(FoundUnitName,Prefix:gsub("-","%%-"),1)then
FoundUnitInReportSetGroup=false
break
end
end
end
if FoundUnitCoalition~=EnemyCoalition and FoundUnitInReportSetGroup==false then
local FriendlyUnit=UNIT:Find(FoundDCSUnit)
local FriendlyUnitName=FriendlyUnit:GetName()
local FriendlyUnitCategory=FriendlyUnit:GetDesc().category
DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{}
DetectedItem.FriendliesNearBy[FoundUnitCategory]=DetectedItem.FriendliesNearBy[FoundUnitCategory]or{}
DetectedItem.FriendliesNearBy[FoundUnitCategory][FriendlyUnitName]=FriendlyUnit
local Distance=DetectedUnitCoord:Get2DDistance(FriendlyUnit:GetCoordinate())
DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{}
DetectedItem.FriendliesDistance[Distance]=FriendlyUnit
return true
end
return true
end
world.searchObjects(Object.Category.UNIT,SphereSearch,FindNearByFriendlies,TargetData)
DetectedItem.PlayersNearBy=nil
_DATABASE:ForEachPlayer(
function(PlayerUnitName)
local PlayerUnit=UNIT:FindByName(PlayerUnitName)
if PlayerUnit and PlayerUnit:IsAlive()then
local coord=PlayerUnit:GetCoordinate()
if coord and coord:IsInRadius(DetectedUnitCoord,self.FriendliesRange)then
local PlayerUnitCategory=PlayerUnit:GetDesc().category
if(not self.FriendliesCategory)or(self.FriendliesCategory and(self.FriendliesCategory==PlayerUnitCategory))then
local PlayerUnitName=PlayerUnit:GetName()
DetectedItem.PlayersNearBy=DetectedItem.PlayersNearBy or{}
DetectedItem.PlayersNearBy[PlayerUnitName]=PlayerUnit
DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{}
DetectedItem.FriendliesNearBy[PlayerUnitCategory]=DetectedItem.FriendliesNearBy[PlayerUnitCategory]or{}
DetectedItem.FriendliesNearBy[PlayerUnitCategory][PlayerUnitName]=PlayerUnit
local Distance=DetectedUnitCoord:Get2DDistance(PlayerUnit:GetCoordinate())
DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{}
DetectedItem.FriendliesDistance[Distance]=PlayerUnit
end
end
end
end)
end
self:F({Friendlies=DetectedItem.FriendliesNearBy,Players=DetectedItem.PlayersNearBy})
end
end
function DETECTION_BASE:IsDetectedObjectIdentified(DetectedObject)
local DetectedObjectName=DetectedObject.Name
if DetectedObjectName then
local DetectedObjectIdentified=self.DetectedObjectsIdentified[DetectedObjectName]==true
return DetectedObjectIdentified
else
return nil
end
end
function DETECTION_BASE:IdentifyDetectedObject(DetectedObject)
local DetectedObjectName=DetectedObject.Name
self.DetectedObjectsIdentified[DetectedObjectName]=true
end
function DETECTION_BASE:UnIdentifyDetectedObject(DetectedObject)
local DetectedObjectName=DetectedObject.Name
self.DetectedObjectsIdentified[DetectedObjectName]=false
end
function DETECTION_BASE:UnIdentifyAllDetectedObjects()
self.DetectedObjectsIdentified={}
end
function DETECTION_BASE:GetDetectedObject(ObjectName)
self:F2({ObjectName=ObjectName})
if ObjectName then
local DetectedObject=self.DetectedObjects[ObjectName]
if DetectedObject then
local DetectedUnit=UNIT:FindByName(ObjectName)
if DetectedUnit and DetectedUnit:IsAlive()then
if self:IsDetectedObjectIdentified(DetectedObject)==false then
return DetectedObject
end
end
end
end
return nil
end
function DETECTION_BASE:GetDetectedUnitTypeName(DetectedUnit)
if DetectedUnit and DetectedUnit:IsAlive()then
local DetectedUnitName=DetectedUnit:GetName()
local DetectedObject=self.DetectedObjects[DetectedUnitName]
if DetectedObject then
if DetectedObject.KnowType then
return DetectedUnit:GetTypeName()
else
return"Unknown"
end
else
return"Unknown"
end
else
return"Dead:"..DetectedUnit:GetName()
end
return"Undetected:"..DetectedUnit:GetName()
end
function DETECTION_BASE:AddDetectedItem(ItemPrefix,DetectedItemKey,Set)
local DetectedItem={}
self.DetectedItemCount=self.DetectedItemCount+1
self.DetectedItemMax=self.DetectedItemMax+1
DetectedItemKey=DetectedItemKey or self.DetectedItemMax
self.DetectedItems[DetectedItemKey]=DetectedItem
self.DetectedItemsByIndex[DetectedItemKey]=DetectedItem
DetectedItem.Index=DetectedItemKey
DetectedItem.Set=Set or SET_UNIT:New():FilterDeads():FilterCrashes()
DetectedItem.ItemID=ItemPrefix.."."..self.DetectedItemMax
DetectedItem.ID=self.DetectedItemMax
DetectedItem.Removed=false
if self.Locking then
self:LockDetectedItem(DetectedItem)
end
return DetectedItem
end
function DETECTION_BASE:AddDetectedItemZone(ItemPrefix,DetectedItemKey,Set,Zone)
self:F({ItemPrefix,DetectedItemKey,Set,Zone})
local DetectedItem=self:AddDetectedItem(ItemPrefix,DetectedItemKey,Set)
DetectedItem.Zone=Zone
return DetectedItem
end
function DETECTION_BASE:RemoveDetectedItem(DetectedItemKey)
local DetectedItem=self.DetectedItems[DetectedItemKey]
if DetectedItem then
self.DetectedItemCount=self.DetectedItemCount-1
local DetectedItemIndex=DetectedItem.Index
self.DetectedItemsByIndex[DetectedItemIndex]=nil
self.DetectedItems[DetectedItemKey]=nil
end
end
function DETECTION_BASE:GetDetectedItems()
return self.DetectedItems
end
function DETECTION_BASE:GetDetectedItemsByIndex()
return self.DetectedItemsByIndex
end
function DETECTION_BASE:GetDetectedItemsCount()
local DetectedCount=self.DetectedItemCount
return DetectedCount
end
function DETECTION_BASE:GetDetectedItemByKey(Key)
self:F({DetectedItems=self.DetectedItems})
local DetectedItem=self.DetectedItems[Key]
if DetectedItem then
return DetectedItem
end
return nil
end
function DETECTION_BASE:GetDetectedItemByIndex(Index)
self:F({self.DetectedItemsByIndex})
local DetectedItem=self.DetectedItemsByIndex[Index]
if DetectedItem then
return DetectedItem
end
return nil
end
function DETECTION_BASE:GetDetectedItemID(DetectedItem)
return DetectedItem and DetectedItem.ItemID or""
end
function DETECTION_BASE:GetDetectedID(Index)
local DetectedItem=self.DetectedItemsByIndex[Index]
if DetectedItem then
return DetectedItem.ID
end
return""
end
function DETECTION_BASE:GetDetectedItemSet(DetectedItem)
local DetectedSetUnit=DetectedItem and DetectedItem.Set
if DetectedSetUnit then
return DetectedSetUnit
end
return nil
end
function DETECTION_BASE:UpdateDetectedItemDetection(DetectedItem)
local IsDetected=false
for UnitName,UnitData in pairs(DetectedItem.Set:GetSet())do
local DetectedObject=self.DetectedObjects[UnitName]
self:F({UnitName=UnitName,IsDetected=DetectedObject.IsDetected})
if DetectedObject.IsDetected then
IsDetected=true
break
end
end
self:F({IsDetected=DetectedItem.IsDetected})
DetectedItem.IsDetected=IsDetected
return IsDetected
end
function DETECTION_BASE:IsDetectedItemDetected(DetectedItem)
return DetectedItem.IsDetected
end
do
function DETECTION_BASE:GetDetectedItemZone(DetectedItem)
local DetectedZone=DetectedItem and DetectedItem.Zone
if DetectedZone then
return DetectedZone
end
local Detected
return nil
end
end
function DETECTION_BASE:LockDetectedItems()
for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
self:LockDetectedItem(DetectedItem)
end
self.Locking=true
return self
end
function DETECTION_BASE:UnlockDetectedItems()
for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
self:UnlockDetectedItem(DetectedItem)
end
self.Locking=nil
return self
end
function DETECTION_BASE:IsDetectedItemLocked(DetectedItem)
return self.Locking and DetectedItem.Locked==true
end
function DETECTION_BASE:LockDetectedItem(DetectedItem)
DetectedItem.Locked=true
return self
end
function DETECTION_BASE:UnlockDetectedItem(DetectedItem)
DetectedItem.Locked=nil
return self
end
function DETECTION_BASE:SetDetectedItemCoordinate(DetectedItem,Coordinate,DetectedItemUnit)
self:F({Coordinate=Coordinate})
if DetectedItem then
if DetectedItemUnit then
DetectedItem.Coordinate=Coordinate
DetectedItem.Coordinate:SetHeading(DetectedItemUnit:GetHeading())
DetectedItem.Coordinate.y=DetectedItemUnit:GetAltitude()
DetectedItem.Coordinate:SetVelocity(DetectedItemUnit:GetVelocityMPS())
end
end
end
function DETECTION_BASE:GetDetectedItemCoordinate(DetectedItem)
self:F({DetectedItem=DetectedItem})
if DetectedItem then
return DetectedItem.Coordinate
end
return nil
end
function DETECTION_BASE:GetDetectedItemCoordinates()
local Coordinates={}
for DetectedItemID,DetectedItem in pairs(self:GetDetectedItems())do
Coordinates[DetectedItem]=self:GetDetectedItemCoordinate(DetectedItem)
end
return Coordinates
end
function DETECTION_BASE:SetDetectedItemThreatLevel(DetectedItem)
local DetectedSet=DetectedItem.Set
if DetectedItem then
DetectedItem.ThreatLevel,DetectedItem.ThreatText=DetectedSet:CalculateThreatLevelA2G()
end
end
function DETECTION_BASE:GetDetectedItemThreatLevel(DetectedItem)
self:F({DetectedItem=DetectedItem})
if DetectedItem then
self:F({ThreatLevel=DetectedItem.ThreatLevel,ThreatText=DetectedItem.ThreatText})
return DetectedItem.ThreatLevel or 0,DetectedItem.ThreatText or""
end
return nil,""
end
function DETECTION_BASE:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings)
self:F()
return nil
end
function DETECTION_BASE:DetectedReportDetailed(AttackGroup)
self:F()
return nil
end
function DETECTION_BASE:GetDetectionSet()
local DetectionSet=self.DetectionSet
return DetectionSet
end
function DETECTION_BASE:NearestRecce(DetectedItem)
local NearestRecce=nil
local DistanceRecce=1000000000
for RecceGroupName,RecceGroup in pairs(self.DetectionSet:GetSet())do
if RecceGroup and RecceGroup:IsAlive()then
for RecceUnit,RecceUnit in pairs(RecceGroup:GetUnits())do
if RecceUnit:IsActive()then
local RecceUnitCoord=RecceUnit:GetCoordinate()
local Distance=RecceUnitCoord:Get2DDistance(self:GetDetectedItemCoordinate(DetectedItem))
if Distance<DistanceRecce then
DistanceRecce=Distance
NearestRecce=RecceUnit
end
end
end
end
end
DetectedItem.NearestFAC=NearestRecce
DetectedItem.DistanceRecce=DistanceRecce
end
function DETECTION_BASE:Schedule(DelayTime,RepeatInterval)
self:F2()
self.ScheduleDelayTime=DelayTime
self.ScheduleRepeatInterval=RepeatInterval
self.DetectionScheduler=SCHEDULER:New(self,self._DetectionScheduler,{self,"Detection"},DelayTime,RepeatInterval)
return self
end
end
do
DETECTION_UNITS={
ClassName="DETECTION_UNITS",
DetectionRange=nil,
}
function DETECTION_UNITS:New(DetectionSetGroup)
local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetGroup))
self._SmokeDetectedUnits=false
self._FlareDetectedUnits=false
self._SmokeDetectedZones=false
self._FlareDetectedZones=false
self._BoundDetectedZones=false
return self
end
function DETECTION_UNITS:GetChangeText(DetectedItem)
self:F(DetectedItem)
local MT={}
for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do
if ChangeCode=="AU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="   New target(s) detected: "..table.concat(MTUT,", ").."."
end
if ChangeCode=="RU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="   Invisible or destroyed target(s): "..table.concat(MTUT,", ").."."
end
end
return table.concat(MT,"\n")
end
function DETECTION_UNITS:CreateDetectionItems()
for DetectedItemKey,_DetectedItem in pairs(self.DetectedItems)do
local DetectedItem=_DetectedItem
local DetectedItemSet=DetectedItem.Set
for DetectedUnitName,DetectedUnitData in pairs(DetectedItemSet:GetSet())do
local DetectedUnit=DetectedUnitData
local DetectedObject=nil
if DetectedUnit:IsAlive()then
DetectedObject=self:GetDetectedObject(DetectedUnit:GetName())
end
if DetectedObject then
self:IdentifyDetectedObject(DetectedObject)
self:F({"**DETECTED**",IsVisible=DetectedObject.IsVisible})
DetectedItem.TypeName=DetectedUnit:GetTypeName()
DetectedItem.CategoryName=DetectedUnit:GetCategoryName()
DetectedItem.Name=DetectedObject.Name
DetectedItem.IsVisible=DetectedObject.IsVisible
DetectedItem.LastTime=DetectedObject.LastTime
DetectedItem.LastPos=DetectedObject.LastPos
DetectedItem.LastVelocity=DetectedObject.LastVelocity
DetectedItem.KnowType=DetectedObject.KnowType
DetectedItem.KnowDistance=DetectedObject.KnowDistance
DetectedItem.Distance=DetectedObject.Distance
else
self:AddChangeUnit(DetectedItem,"RU",DetectedUnitName)
DetectedItemSet:Remove(DetectedUnitName)
end
end
if DetectedItemSet:Count()==0 then
self:RemoveDetectedItem(DetectedItemKey)
end
end
for DetectedUnitName,DetectedObjectData in pairs(self.DetectedObjects)do
local DetectedObject=self:GetDetectedObject(DetectedUnitName)
if DetectedObject then
self:T({"Detected Unit #",DetectedUnitName})
local DetectedUnit=UNIT:FindByName(DetectedUnitName)
if DetectedUnit then
local DetectedTypeName=DetectedUnit:GetTypeName()
local DetectedItem=self:GetDetectedItemByKey(DetectedUnitName)
if not DetectedItem then
self:T("Added new DetectedItem")
DetectedItem=self:AddDetectedItem("UNIT",DetectedUnitName)
DetectedItem.TypeName=DetectedUnit:GetTypeName()
DetectedItem.Name=DetectedObject.Name
DetectedItem.IsVisible=DetectedObject.IsVisible
DetectedItem.LastTime=DetectedObject.LastTime
DetectedItem.LastPos=DetectedObject.LastPos
DetectedItem.LastVelocity=DetectedObject.LastVelocity
DetectedItem.KnowType=DetectedObject.KnowType
DetectedItem.KnowDistance=DetectedObject.KnowDistance
DetectedItem.Distance=DetectedObject.Distance
end
DetectedItem.Set:AddUnit(DetectedUnit)
self:AddChangeUnit(DetectedItem,"AU",DetectedTypeName)
end
end
end
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
local DetectedItem=DetectedItemData
local DetectedSet=DetectedItem.Set
local DetectedFirstUnit=DetectedSet:GetFirst()
local DetectedFirstUnitCoord=DetectedFirstUnit:GetCoordinate()
self:SetDetectedItemCoordinate(DetectedItem,DetectedFirstUnitCoord,DetectedFirstUnit)
self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSet})
self:SetDetectedItemThreatLevel(DetectedItem)
self:NearestRecce(DetectedItem)
end
end
function DETECTION_UNITS:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings,ForceA2GCoordinate)
self:F({DetectedItem=DetectedItem})
local DetectedItemID=self:GetDetectedItemID(DetectedItem)
if DetectedItem then
local ReportSummary=""
local UnitDistanceText=""
local UnitCategoryText=""
if DetectedItem.KnowType then
local UnitCategoryName=DetectedItem.CategoryName
if UnitCategoryName then
UnitCategoryText=UnitCategoryName
end
if DetectedItem.TypeName then
UnitCategoryText=UnitCategoryText.." ("..DetectedItem.TypeName..")"
end
else
UnitCategoryText="Unknown"
end
if DetectedItem.KnowDistance then
if DetectedItem.IsVisible then
UnitDistanceText=" at "..string.format("%.2f",DetectedItem.Distance).." km"
end
else
if DetectedItem.IsVisible then
UnitDistanceText=" at +/- "..string.format("%.0f",DetectedItem.Distance).." km"
end
end
local DetectedItemCoordinate=self:GetDetectedItemCoordinate(DetectedItem)
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
if ForceA2GCoordinate then
DetectedItemCoordText=DetectedItemCoordinate:ToStringA2G(AttackGroup,Settings)
end
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem)
local Report=REPORT:New()
Report:Add(DetectedItemID..", "..DetectedItemCoordText)
Report:Add(string.format("Threat: [%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G)))
Report:Add(string.format("Type: %s%s",UnitCategoryText,UnitDistanceText))
Report:Add(string.format("Visible: %s",DetectedItem.IsVisible and"yes"or"no"))
Report:Add(string.format("Detected: %s",DetectedItem.IsDetected and"yes"or"no"))
Report:Add(string.format("Distance: %s",DetectedItem.KnowDistance and"yes"or"no"))
return Report
end
return nil
end
function DETECTION_UNITS:DetectedReportDetailed(AttackGroup)
self:F()
local Report=REPORT:New()
for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do
local DetectedItem=DetectedItem
local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup)
Report:SetTitle("Detected units:")
Report:Add(ReportSummary:Text())
end
local ReportText=Report:Text()
return ReportText
end
end
do
DETECTION_TYPES={
ClassName="DETECTION_TYPES",
DetectionRange=nil,
}
function DETECTION_TYPES:New(DetectionSetGroup)
local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetGroup))
self._SmokeDetectedUnits=false
self._FlareDetectedUnits=false
self._SmokeDetectedZones=false
self._FlareDetectedZones=false
self._BoundDetectedZones=false
return self
end
function DETECTION_TYPES:GetChangeText(DetectedItem)
self:F(DetectedItem)
local MT={}
for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do
if ChangeCode=="AU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="   New target(s) detected: "..table.concat(MTUT,", ").."."
end
if ChangeCode=="RU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="   Invisible or destroyed target(s): "..table.concat(MTUT,", ").."."
end
end
return table.concat(MT,"\n")
end
function DETECTION_TYPES:CreateDetectionItems()
for DetectedItemKey,DetectedItem in pairs(self.DetectedItems)do
local DetectedItemSet=DetectedItem.Set
local DetectedTypeName=DetectedItem.TypeName
for DetectedUnitName,DetectedUnitData in pairs(DetectedItemSet:GetSet())do
local DetectedUnit=DetectedUnitData
local DetectedObject=nil
if DetectedUnit:IsAlive()then
DetectedObject=self:GetDetectedObject(DetectedUnit:GetName())
end
if DetectedObject then
self:IdentifyDetectedObject(DetectedObject)
else
self:AddChangeUnit(DetectedItem,"RU",DetectedUnitName)
DetectedItemSet:Remove(DetectedUnitName)
end
end
if DetectedItemSet:Count()==0 then
self:RemoveDetectedItem(DetectedItemKey)
end
end
for DetectedUnitName,DetectedObjectData in pairs(self.DetectedObjects)do
local DetectedObject=self:GetDetectedObject(DetectedUnitName)
if DetectedObject then
self:T({"Detected Unit #",DetectedUnitName})
local DetectedUnit=UNIT:FindByName(DetectedUnitName)
if DetectedUnit then
local DetectedTypeName=DetectedUnit:GetTypeName()
local DetectedItem=self:GetDetectedItemByKey(DetectedTypeName)
if not DetectedItem then
DetectedItem=self:AddDetectedItem("TYPE",DetectedTypeName)
DetectedItem.TypeName=DetectedTypeName
end
DetectedItem.Set:AddUnit(DetectedUnit)
self:AddChangeUnit(DetectedItem,"AU",DetectedTypeName)
end
end
end
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
local DetectedItem=DetectedItemData
local DetectedSet=DetectedItem.Set
local DetectedFirstUnit=DetectedSet:GetFirst()
local DetectedUnitCoord=DetectedFirstUnit:GetCoordinate()
self:SetDetectedItemCoordinate(DetectedItem,DetectedUnitCoord,DetectedFirstUnit)
self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSet})
self:SetDetectedItemThreatLevel(DetectedItem)
self:NearestRecce(DetectedItem)
end
end
function DETECTION_TYPES:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings)
self:F({DetectedItem=DetectedItem})
local DetectedSet=self:GetDetectedItemSet(DetectedItem)
local DetectedItemID=self:GetDetectedItemID(DetectedItem)
self:T(DetectedItem)
if DetectedItem then
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem)
local DetectedItemsCount=DetectedSet:Count()
local DetectedItemType=DetectedItem.TypeName
local DetectedItemCoordinate=self:GetDetectedItemCoordinate(DetectedItem)
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
local Report=REPORT:New()
Report:Add(DetectedItemID..", "..DetectedItemCoordText)
Report:Add(string.format("Threat: [%s%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G)))
Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemType))
return Report
end
end
function DETECTION_TYPES:DetectedReportDetailed(AttackGroup)
self:F()
local Report=REPORT:New()
for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do
local DetectedItem=DetectedItem
local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup)
Report:SetTitle("Detected types:")
Report:Add(ReportSummary:Text())
end
local ReportText=Report:Text()
return ReportText
end
end
do
DETECTION_AREAS={
ClassName="DETECTION_AREAS",
DetectionZoneRange=nil,
}
function DETECTION_AREAS:New(DetectionSetGroup,DetectionZoneRange)
local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetGroup))
self.DetectionZoneRange=DetectionZoneRange
self._SmokeDetectedUnits=false
self._FlareDetectedUnits=false
self._SmokeDetectedZones=false
self._FlareDetectedZones=false
self._BoundDetectedZones=false
return self
end
function DETECTION_AREAS:GetDetectionZones()
local zoneset=SET_ZONE:New()
for _ID,_Item in pairs(self.DetectedItems)do
local item=_Item
if item.Zone then
zoneset:AddZone(item.Zone)
end
end
return zoneset
end
function DETECTION_AREAS:GetDetectionZoneByID(ID)
local zone=nil
for _ID,_Item in pairs(self.DetectedItems)do
local item=_Item
if item.ID==ID then
zone=item.Zone
break
end
end
return zone
end
function DETECTION_AREAS:GetDetectionZoneCount()
local zoneset=0
for _ID,_Item in pairs(self.DetectedItems)do
if _Item.Zone then
zoneset=zoneset+1
end
end
return zoneset
end
function DETECTION_AREAS:DetectedItemReportMenu(DetectedItem,AttackGroup,Settings)
self:F({DetectedItem=DetectedItem})
local DetectedItemID=self:GetDetectedItemID(DetectedItem)
if DetectedItem then
local DetectedSet=self:GetDetectedItemSet(DetectedItem)
local ReportSummaryItem
local DetectedZone=self:GetDetectedItemZone(DetectedItem)
local DetectedItemCoordinate=DetectedZone:GetCoordinate()
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem)
local Report=REPORT:New()
Report:Add(DetectedItemID)
Report:Add(string.format("Threat: [%s%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G)))
return Report
end
return nil
end
function DETECTION_AREAS:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings)
self:F({DetectedItem=DetectedItem})
local DetectedItemID=self:GetDetectedItemID(DetectedItem)
if DetectedItem then
local DetectedSet=self:GetDetectedItemSet(DetectedItem)
local ReportSummaryItem
local DetectedItemCoordinate=self:GetDetectedItemCoordinate(DetectedItem)
local DetectedAir=DetectedSet:HasAirUnits()
local DetectedAltitude=self:GetDetectedItemCoordinate(DetectedItem)
local DetectedItemCoordText=""
if DetectedAir>0 then
DetectedItemCoordText=DetectedItemCoordinate:ToStringA2A(AttackGroup,Settings)
else
DetectedItemCoordText=DetectedItemCoordinate:ToStringA2G(AttackGroup,Settings)
end
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem)
local DetectedItemsCount=DetectedSet:Count()
local DetectedItemsTypes=DetectedSet:GetTypeNames()
local Report=REPORT:New()
Report:Add(DetectedItemID..", "..DetectedItemCoordText)
Report:Add(string.format("Threat: [%s%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G)))
Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemsTypes))
return Report
end
return nil
end
function DETECTION_AREAS:DetectedReportDetailed(AttackGroup)
self:F()
local Report=REPORT:New()
for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do
local DetectedItem=DetectedItem
local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup)
Report:SetTitle("Detected areas:")
Report:Add(ReportSummary:Text())
end
local ReportText=Report:Text()
return ReportText
end
function DETECTION_AREAS:CalculateIntercept(DetectedItem)
local DetectedCoord=DetectedItem.Coordinate
local DetectedSpeed=DetectedCoord:GetVelocity()
local DetectedHeading=DetectedCoord:GetHeading()
if self.Intercept then
local DetectedSet=DetectedItem.Set
local TranslateDistance=DetectedSpeed*self.InterceptDelay
local InterceptCoord=DetectedCoord:Translate(TranslateDistance,DetectedHeading)
DetectedItem.InterceptCoord=InterceptCoord
else
DetectedItem.InterceptCoord=DetectedCoord
end
end
function DETECTION_AREAS:SmokeDetectedUnits()
self:F2()
self._SmokeDetectedUnits=true
return self
end
function DETECTION_AREAS:FlareDetectedUnits()
self:F2()
self._FlareDetectedUnits=true
return self
end
function DETECTION_AREAS:SmokeDetectedZones()
self:F2()
self._SmokeDetectedZones=true
return self
end
function DETECTION_AREAS:FlareDetectedZones()
self:F2()
self._FlareDetectedZones=true
return self
end
function DETECTION_AREAS:BoundDetectedZones()
self:F2()
self._BoundDetectedZones=true
return self
end
function DETECTION_AREAS:GetChangeText(DetectedItem)
self:F(DetectedItem)
local MT={}
for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do
if ChangeCode=="AA"then
MT[#MT+1]="Detected new area "..ChangeData.ID..". The center target is a "..ChangeData.ItemUnitType.."."
end
if ChangeCode=="RAU"then
MT[#MT+1]="Changed area "..ChangeData.ID..". Removed the center target."
end
if ChangeCode=="AAU"then
MT[#MT+1]="Changed area "..ChangeData.ID..". The new center target is a "..ChangeData.ItemUnitType.."."
end
if ChangeCode=="RA"then
MT[#MT+1]="Removed old area "..ChangeData.ID..". No more targets in this area."
end
if ChangeCode=="AU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="Detected for area "..ChangeData.ID.." new target(s) "..table.concat(MTUT,", ").."."
end
if ChangeCode=="RU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="Removed for area "..ChangeData.ID.." invisible or destroyed target(s) "..table.concat(MTUT,", ").."."
end
end
return table.concat(MT,"\n")
end
function DETECTION_AREAS:CreateDetectionItems()
self:F("Checking Detected Items for new Detected Units ...")
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
local DetectedItem=DetectedItemData
if DetectedItem then
self:T2({"Detected Item ID: ",DetectedItemID})
local DetectedSet=DetectedItem.Set
local AreaExists=false
self:T3({"Zone Center Unit:",DetectedItem.Zone.ZoneUNIT.UnitName})
local DetectedZoneObject=self:GetDetectedObject(DetectedItem.Zone.ZoneUNIT.UnitName)
self:T3({"Detected Zone Object:",DetectedItem.Zone:GetName(),DetectedZoneObject})
if DetectedZoneObject then
AreaExists=true
else
DetectedSet:RemoveUnitsByName(DetectedItem.Zone.ZoneUNIT.UnitName)
self:AddChangeItem(DetectedItem,'RAU',self:GetDetectedUnitTypeName(DetectedItem.Zone.ZoneUNIT))
for DetectedUnitName,DetectedUnitData in pairs(DetectedSet:GetSet())do
local DetectedUnit=DetectedUnitData
local DetectedObject=self:GetDetectedObject(DetectedUnit.UnitName)
local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit)
if DetectedObject then
self:IdentifyDetectedObject(DetectedObject)
AreaExists=true
DetectedItem.Zone=ZONE_UNIT:New(DetectedUnit:GetName(),DetectedUnit,self.DetectionZoneRange)
self:AddChangeItem(DetectedItem,"AAU",DetectedUnitTypeName)
break
else
DetectedSet:Remove(DetectedUnitName)
self:AddChangeUnit(DetectedItem,"RU",DetectedUnitTypeName)
end
end
end
if AreaExists then
for DetectedUnitName,DetectedUnitData in pairs(DetectedSet:GetSet())do
local DetectedUnit=DetectedUnitData
local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit)
local DetectedObject=nil
if DetectedUnit:IsAlive()then
DetectedObject=self:GetDetectedObject(DetectedUnit:GetName())
end
if DetectedObject then
if DetectedUnit:IsInZone(DetectedItem.Zone)then
self:IdentifyDetectedObject(DetectedObject)
DetectedSet:AddUnit(DetectedUnit)
else
DetectedSet:Remove(DetectedUnitName)
self:AddChangeUnit(DetectedItem,"RU",DetectedUnitTypeName)
end
else
self:AddChangeUnit(DetectedItem,"RU","destroyed target")
DetectedSet:Remove(DetectedUnitName)
end
end
else
self:RemoveDetectedItem(DetectedItemID)
self:AddChangeItem(DetectedItem,"RA")
end
end
end
for DetectedUnitName,DetectedObjectData in pairs(self.DetectedObjects)do
local DetectedObject=self:GetDetectedObject(DetectedUnitName)
if DetectedObject then
local DetectedUnit=UNIT:FindByName(DetectedUnitName)
local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit)
local AddedToDetectionArea=false
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
local DetectedItem=DetectedItemData
if DetectedItem then
local DetectedSet=DetectedItem.Set
if not self:IsDetectedObjectIdentified(DetectedObject)and DetectedUnit:IsInZone(DetectedItem.Zone)then
self:IdentifyDetectedObject(DetectedObject)
DetectedSet:AddUnit(DetectedUnit)
AddedToDetectionArea=true
self:AddChangeUnit(DetectedItem,"AU",DetectedUnitTypeName)
end
end
end
if AddedToDetectionArea==false then
local DetectedItem=self:AddDetectedItemZone("AREA",nil,
SET_UNIT:New():FilterDeads():FilterCrashes(),
ZONE_UNIT:New(DetectedUnitName,DetectedUnit,self.DetectionZoneRange)
)
DetectedItem.Set:AddUnit(DetectedUnit)
self:AddChangeItem(DetectedItem,"AA",DetectedUnitTypeName)
end
end
end
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
local DetectedItem=DetectedItemData
local DetectedSet=DetectedItem.Set
local DetectedFirstUnit=DetectedSet:GetFirst()
local DetectedZone=DetectedItem.Zone
local DetectedZoneCoord=DetectedZone:GetCoordinate()
self:SetDetectedItemCoordinate(DetectedItem,DetectedZoneCoord,DetectedFirstUnit)
self:CalculateIntercept(DetectedItem)
local OldFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT)
self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSet})
local NewFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT)
if OldFriendliesNearbyGround~=NewFriendliesNearbyGround then
DetectedItem.Changed=true
end
self:SetDetectedItemThreatLevel(DetectedItem)
self:NearestRecce(DetectedItem)
if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then
DetectedZone.ZoneUNIT:SmokeRed()
end
DetectedSet:ForEachUnit(
function(DetectedUnit)
if DetectedUnit:IsAlive()then
if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then
DetectedUnit:FlareGreen()
end
if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then
DetectedUnit:SmokeGreen()
end
end
end)
if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then
DetectedZone:FlareZone(SMOKECOLOR.White,30,math.random(0,90))
end
if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then
DetectedZone:SmokeZone(SMOKECOLOR.White,30)
end
if DETECTION_AREAS._BoundDetectedZones or self._BoundDetectedZones then
self.CountryID=DetectedSet:GetFirst():GetCountry()
DetectedZone:BoundZone(12,self.CountryID)
end
end
end
end
do
DETECTION_ZONES={
ClassName="DETECTION_ZONES",
DetectionZoneRange=nil,
}
function DETECTION_ZONES:New(DetectionSetZone,DetectionCoalition)
local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetZone))
self.DetectionSetZone=DetectionSetZone
self.DetectionCoalition=DetectionCoalition
self._SmokeDetectedUnits=false
self._FlareDetectedUnits=false
self._SmokeDetectedZones=false
self._FlareDetectedZones=false
self._BoundDetectedZones=false
return self
end
function DETECTION_ZONES:CountAliveRecce()
return self.DetectionSetZone:Count()
end
function DETECTION_ZONES:ForEachAliveRecce(IteratorFunction,...)
self:F2(arg)
self.DetectionSetZone:ForEachZone(IteratorFunction,arg)
return self
end
function DETECTION_ZONES:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings)
self:F({DetectedItem=DetectedItem})
local DetectedItemID=self:GetDetectedItemID(DetectedItem)
if DetectedItem then
local DetectedSet=self:GetDetectedItemSet(DetectedItem)
local ReportSummaryItem
local DetectedZone=self:GetDetectedItemZone(DetectedItem)
local DetectedItemCoordinate=DetectedZone:GetCoordinate()
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem)
local DetectedItemsCount=DetectedSet:Count()
local DetectedItemsTypes=DetectedSet:GetTypeNames()
local Report=REPORT:New()
Report:Add(DetectedItemID..", "..DetectedItemCoordText)
Report:Add(string.format("Threat: [%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G)))
Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemsTypes))
Report:Add(string.format("Detected: %s",DetectedItem.IsDetected and"yes"or"no"))
return Report
end
return nil
end
function DETECTION_ZONES:DetectedReportDetailed(AttackGroup)
self:F()
local Report=REPORT:New()
for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do
local DetectedItem=DetectedItem
local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup)
Report:SetTitle("Detected areas:")
Report:Add(ReportSummary:Text())
end
local ReportText=Report:Text()
return ReportText
end
function DETECTION_ZONES:CalculateIntercept(DetectedItem)
local DetectedCoord=DetectedItem.Coordinate
DetectedItem.InterceptCoord=DetectedCoord
end
function DETECTION_ZONES:SmokeDetectedUnits()
self:F2()
self._SmokeDetectedUnits=true
return self
end
function DETECTION_ZONES:FlareDetectedUnits()
self:F2()
self._FlareDetectedUnits=true
return self
end
function DETECTION_ZONES:SmokeDetectedZones()
self:F2()
self._SmokeDetectedZones=true
return self
end
function DETECTION_ZONES:FlareDetectedZones()
self:F2()
self._FlareDetectedZones=true
return self
end
function DETECTION_ZONES:BoundDetectedZones()
self:F2()
self._BoundDetectedZones=true
return self
end
function DETECTION_ZONES:GetChangeText(DetectedItem)
self:F(DetectedItem)
local MT={}
for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do
if ChangeCode=="AA"then
MT[#MT+1]="Detected new area "..ChangeData.ID..". The center target is a "..ChangeData.ItemUnitType.."."
end
if ChangeCode=="RAU"then
MT[#MT+1]="Changed area "..ChangeData.ID..". Removed the center target."
end
if ChangeCode=="AAU"then
MT[#MT+1]="Changed area "..ChangeData.ID..". The new center target is a "..ChangeData.ItemUnitType.."."
end
if ChangeCode=="RA"then
MT[#MT+1]="Removed old area "..ChangeData.ID..". No more targets in this area."
end
if ChangeCode=="AU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="Detected for area "..ChangeData.ID.." new target(s) "..table.concat(MTUT,", ").."."
end
if ChangeCode=="RU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="Removed for area "..ChangeData.ID.." invisible or destroyed target(s) "..table.concat(MTUT,", ").."."
end
end
return table.concat(MT,"\n")
end
function DETECTION_ZONES:CreateDetectionItems()
self:F("Checking Detected Items for new Detected Units ...")
local DetectedUnits=SET_UNIT:New()
for ZoneName,DetectionZone in pairs(self.DetectionSetZone:GetSet())do
local DetectedItem=self:GetDetectedItemByKey(ZoneName)
if DetectedItem==nil then
DetectedItem=self:AddDetectedItemZone("ZONE",ZoneName,nil,DetectionZone)
end
local DetectedItemSetUnit=self:GetDetectedItemSet(DetectedItem)
DetectionZone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
local ZoneUnits=DetectionZone:GetScannedUnits()
for DCSUnitID,DCSUnit in pairs(ZoneUnits)do
local UnitName=DCSUnit:getName()
local ZoneUnit=UNIT:FindByName(UnitName)
local ZoneUnitCoalition=ZoneUnit:GetCoalition()
if ZoneUnitCoalition==self.DetectionCoalition then
if DetectedItemSetUnit:FindUnit(UnitName)==nil and DetectedUnits:FindUnit(UnitName)==nil then
self:F("Adding "..UnitName)
DetectedItemSetUnit:AddUnit(ZoneUnit)
DetectedUnits:AddUnit(ZoneUnit)
end
end
end
end
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
local DetectedItem=DetectedItemData
local DetectedSet=self:GetDetectedItemSet(DetectedItem)
local DetectedFirstUnit=DetectedSet:GetFirst()
local DetectedZone=self:GetDetectedItemZone(DetectedItem)
local DetectedZoneCoord=DetectedZone:GetCoordinate()
self:SetDetectedItemCoordinate(DetectedItem,DetectedZoneCoord,DetectedFirstUnit)
self:CalculateIntercept(DetectedItem)
local OldFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT)
self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSetGroup})
local NewFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT)
if OldFriendliesNearbyGround~=NewFriendliesNearbyGround then
DetectedItem.Changed=true
end
self:SetDetectedItemThreatLevel(DetectedItem)
if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then
DetectedZone:SmokeZone(SMOKECOLOR.Red,30)
end
DetectedSet:ForEachUnit(
function(DetectedUnit)
if DetectedUnit:IsAlive()then
if DETECTION_ZONES._FlareDetectedUnits or self._FlareDetectedUnits then
DetectedUnit:FlareGreen()
end
if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then
DetectedUnit:SmokeGreen()
end
end
end
)
if DETECTION_ZONES._FlareDetectedZones or self._FlareDetectedZones then
DetectedZone:FlareZone(SMOKECOLOR.White,30,math.random(0,90))
end
if DETECTION_ZONES._SmokeDetectedZones or self._SmokeDetectedZones then
DetectedZone:SmokeZone(SMOKECOLOR.White,30)
end
if DETECTION_ZONES._BoundDetectedZones or self._BoundDetectedZones then
self.CountryID=DetectedSet:GetFirst():GetCountry()
DetectedZone:BoundZone(12,self.CountryID)
end
end
end
function DETECTION_ZONES:onafterDetection(From,Event,To,Detection,DetectionTimeStamp)
self.DetectionRun=self.DetectionRun+1
if self.DetectionCount>0 and self.DetectionRun==self.DetectionCount then
self:CreateDetectionItems()
for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
self:UpdateDetectedItemDetection(DetectedItem)
self:CleanDetectionItem(DetectedItem,DetectedItemID)
if DetectedItem then
self:__DetectedItem(0.1,DetectedItem)
end
end
self:__Detect(-self.RefreshTimeInterval)
end
end
function DETECTION_ZONES:UpdateDetectedItemDetection(DetectedItem)
local IsDetected=true
DetectedItem.IsDetected=true
return IsDetected
end
end
do
DESIGNATE={
ClassName="DESIGNATE",
}
function DESIGNATE:New(CC,Detection,AttackSet,Mission)
local self=BASE:Inherit(self,FSM:New())
self:F({Detection})
self:SetStartState("Designating")
self:AddTransition("*","Detect","*")
self:AddTransition("*","LaseOn","Lasing")
self:AddTransition("Lasing","Lasing","Lasing")
self:AddTransition("*","LaseOff","Designate")
self:AddTransition("*","Smoke","*")
self:AddTransition("*","Illuminate","*")
self:AddTransition("*","DoneSmoking","*")
self:AddTransition("*","DoneIlluminating","*")
self:AddTransition("*","Status","*")
self.CC=CC
self.Detection=Detection
self.AttackSet=AttackSet
self.RecceSet=Detection:GetDetectionSet()
self.Recces={}
self.Designating={}
self:SetDesignateName()
self:SetLaseDuration()
self:SetFlashStatusMenu(false)
self:SetFlashDetectionMessages(true)
self:SetMission(Mission)
self:SetLaserCodes({1688,1130,4785,6547,1465,4578})
self:SetAutoLase(false,false)
self:SetThreatLevelPrioritization(false)
self:SetMaximumDesignations(5)
self:SetMaximumDistanceDesignations(8000)
self:SetMaximumMarkings(2)
self:SetDesignateMenu()
self.LaserCodesUsed={}
self.MenuLaserCodes={}
self.Detection:__Start(2)
self:__Detect(-15)
self.MarkScheduler=SCHEDULER:New(self)
return self
end
function DESIGNATE:SetFlashStatusMenu(FlashMenu)
self.FlashStatusMenu={}
self.AttackSet:ForEachGroupAlive(
function(AttackGroup)
self.FlashStatusMenu[AttackGroup]=FlashMenu
end
)
return self
end
function DESIGNATE:SetFlashDetectionMessages(FlashDetectionMessage)
self.FlashDetectionMessage={}
self.AttackSet:ForEachGroupAlive(
function(AttackGroup)
self.FlashDetectionMessage[AttackGroup]=FlashDetectionMessage
end
)
return self
end
function DESIGNATE:SetMaximumDesignations(MaximumDesignations)
self.MaximumDesignations=MaximumDesignations
return self
end
function DESIGNATE:SetMaximumDistanceGroundDesignation(MaximumDistanceGroundDesignation)
self.MaximumDistanceGroundDesignation=MaximumDistanceGroundDesignation
return self
end
function DESIGNATE:SetMaximumDistanceAirDesignation(MaximumDistanceAirDesignation)
self.MaximumDistanceAirDesignation=MaximumDistanceAirDesignation
return self
end
function DESIGNATE:SetMaximumDistanceDesignations(MaximumDistanceDesignations)
self.MaximumDistanceDesignations=MaximumDistanceDesignations
return self
end
function DESIGNATE:SetMaximumMarkings(MaximumMarkings)
self.MaximumMarkings=MaximumMarkings
return self
end
function DESIGNATE:SetLaserCodes(LaserCodes)
self.LaserCodes=(type(LaserCodes)=="table")and LaserCodes or{LaserCodes}
self:F({LaserCodes=self.LaserCodes})
self.LaserCodesUsed={}
return self
end
function DESIGNATE:AddMenuLaserCode(LaserCode,MenuText)
self.MenuLaserCodes[LaserCode]=MenuText
self:SetDesignateMenu()
return self
end
function DESIGNATE:RemoveMenuLaserCode(LaserCode)
self.MenuLaserCodes[LaserCode]=nil
self:SetDesignateMenu()
return self
end
function DESIGNATE:SetDesignateName(DesignateName)
self.DesignateName="Designation"..(DesignateName and(" for "..DesignateName)or"")
return self
end
function DESIGNATE:SetLaseDuration(LaseDuration)
self.LaseDuration=LaseDuration or 120
return self
end
function DESIGNATE:GenerateLaserCodes()
self.LaserCodes={}
local function containsDigit(_number,_numberToFind)
local _thisNumber=_number
local _thisDigit=0
while _thisNumber~=0 do
_thisDigit=_thisNumber%10
_thisNumber=math.floor(_thisNumber/10)
if _thisDigit==_numberToFind then
return true
end
end
return false
end
local _code=1111
local _count=1
while _code<1777 and _count<30 do
while true do
_code=_code+1
if not containsDigit(_code,8)
and not containsDigit(_code,9)
and not containsDigit(_code,0)then
self:T(_code)
table.insert(self.LaserCodes,_code)
break
end
end
_count=_count+1
end
self.LaserCodesUsed={}
return self
end
function DESIGNATE:SetAutoLase(AutoLase,Message)
self.AutoLase=AutoLase or false
if Message then
local AutoLaseOnOff=(self.AutoLase==true)and"On"or"Off"
local CC=self.CC:GetPositionable()
if CC then
CC:MessageToSetGroup(self.DesignateName..": Auto Lase "..AutoLaseOnOff..".",15,self.AttackSet)
end
end
self:CoordinateLase()
self:SetDesignateMenu()
return self
end
function DESIGNATE:SetThreatLevelPrioritization(Prioritize)
self.ThreatLevelPrioritization=Prioritize
return self
end
function DESIGNATE:SetMission(Mission)
self.Mission=Mission
return self
end
function DESIGNATE:onafterDetect()
self:__Detect(-math.random(60))
self:DesignationScope()
self:CoordinateLase()
self:SendStatus()
self:SetDesignateMenu()
return self
end
function DESIGNATE:DesignationScope()
local DetectedItems=self.Detection:GetDetectedItemsByIndex()
local DetectedItemCount=0
for DesignateIndex,Designating in pairs(self.Designating)do
local DetectedItem=self.Detection:GetDetectedItemByIndex(DesignateIndex)
if DetectedItem then
local IsDetected=self.Detection:IsDetectedItemDetected(DetectedItem)
self:F({IsDetected=IsDetected})
if IsDetected==false then
self:F("Removing")
self.Designating[DesignateIndex]=nil
self.AttackSet:ForEachGroupAlive(
function(AttackGroup)
if AttackGroup:IsAlive()==true then
local DetectionText=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup):Text(", ")
self.CC:GetPositionable():MessageToGroup("Targets out of LOS\n"..DetectionText,10,AttackGroup,self.DesignateName)
end
end
)
else
DetectedItemCount=DetectedItemCount+1
end
else
self.Designating[DesignateIndex]=nil
end
end
if DetectedItemCount<5 then
for DesignateIndex,DetectedItem in pairs(DetectedItems)do
local IsDetected=self.Detection:IsDetectedItemDetected(DetectedItem)
if IsDetected==true then
self:F({DistanceRecce=DetectedItem.DistanceRecce})
if DetectedItem.DistanceRecce<=self.MaximumDistanceDesignations then
if self.Designating[DesignateIndex]==nil then
self.AttackSet:ForEachGroupAlive(
function(AttackGroup)
if self.FlashDetectionMessage[AttackGroup]==true then
local DetectionText=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup):Text(", ")
self.CC:GetPositionable():MessageToGroup("Targets detected at \n"..DetectionText,10,AttackGroup,self.DesignateName)
end
end
)
self.Designating[DesignateIndex]=""
break
end
end
end
end
end
return self
end
function DESIGNATE:CoordinateLase()
local DetectedItems=self.Detection:GetDetectedItemsByIndex()
for DesignateIndex,Designating in pairs(self.Designating)do
local DetectedItem=DetectedItems[DesignateIndex]
if DetectedItem then
if self.AutoLase then
self:LaseOn(DesignateIndex,self.LaseDuration)
end
end
end
return self
end
function DESIGNATE:SendStatus(MenuAttackGroup)
self.AttackSet:ForEachGroupAlive(
function(AttackGroup)
if self.FlashStatusMenu[AttackGroup]or(MenuAttackGroup and(AttackGroup:GetName()==MenuAttackGroup:GetName()))then
local DetectedReport=REPORT:New("Targets ready for Designation:")
local DetectedItems=self.Detection:GetDetectedItemsByIndex()
for DesignateIndex,Designating in pairs(self.Designating)do
local DetectedItem=DetectedItems[DesignateIndex]
if DetectedItem then
local Report=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup,nil,true):Text(", ")
DetectedReport:Add(string.rep("-",40))
DetectedReport:Add(" - "..Report)
if string.find(Designating,"L")then
DetectedReport:Add(" - ".."Lasing Targets")
end
if string.find(Designating,"S")then
DetectedReport:Add(" - ".."Smoking Targets")
end
if string.find(Designating,"I")then
DetectedReport:Add(" - ".."Illuminating Area")
end
end
end
local CC=self.CC:GetPositionable()
CC:MessageTypeToGroup(DetectedReport:Text("\n"),MESSAGE.Type.Information,AttackGroup,self.DesignateName)
local DesignationReport=REPORT:New("Marking Targets:")
self.RecceSet:ForEachGroupAlive(
function(RecceGroup)
local RecceUnits=RecceGroup:GetUnits()
for UnitID,RecceData in pairs(RecceUnits)do
local Recce=RecceData
if Recce:IsLasing()then
DesignationReport:Add(" - "..Recce:GetMessageText("Marking "..Recce:GetSpot().Target:GetTypeName().." with laser "..Recce:GetSpot().LaserCode.."."))
end
end
end
)
CC:MessageTypeToGroup(DesignationReport:Text(),MESSAGE.Type.Information,AttackGroup,self.DesignateName)
end
end
)
return self
end
function DESIGNATE:SetMenu(AttackGroup)
self.MenuDesignate=self.MenuDesignate or{}
local MissionMenu=nil
if self.Mission then
MissionMenu=self.Mission:GetMenu(AttackGroup)
end
local MenuTime=timer.getTime()
self.MenuDesignate[AttackGroup]=MENU_GROUP_DELAYED:New(AttackGroup,self.DesignateName,MissionMenu):SetTime(MenuTime):SetTag(self.DesignateName)
local MenuDesignate=self.MenuDesignate[AttackGroup]
if self.AutoLase then
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Auto Lase Off",MenuDesignate,self.MenuAutoLase,self,false):SetTime(MenuTime):SetTag(self.DesignateName)
else
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Auto Lase On",MenuDesignate,self.MenuAutoLase,self,true):SetTime(MenuTime):SetTag(self.DesignateName)
end
local StatusMenu=MENU_GROUP_DELAYED:New(AttackGroup,"Status",MenuDesignate):SetTime(MenuTime):SetTag(self.DesignateName)
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Report Status",StatusMenu,self.MenuStatus,self,AttackGroup):SetTime(MenuTime):SetTag(self.DesignateName)
if self.FlashStatusMenu[AttackGroup]then
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Flash Status Report Off",StatusMenu,self.MenuFlashStatus,self,AttackGroup,false):SetTime(MenuTime):SetTag(self.DesignateName)
else
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Flash Status Report On",StatusMenu,self.MenuFlashStatus,self,AttackGroup,true):SetTime(MenuTime):SetTag(self.DesignateName)
end
local DesignateCount=0
for DesignateIndex,Designating in pairs(self.Designating)do
local DetectedItem=self.Detection:GetDetectedItemByIndex(DesignateIndex)
if DetectedItem then
local Coord=self.Detection:GetDetectedItemCoordinate(DetectedItem)
local ID=self.Detection:GetDetectedItemID(DetectedItem)
local MenuText=ID
if DetectedItem.DesignateMenuName then
MenuText=string.format("(%3s) %s",Designating,DetectedItem.DesignateMenuName)
else
MenuText=string.format("(%3s) %s",Designating,MenuText)
end
local DetectedMenu=MENU_GROUP_DELAYED:New(AttackGroup,MenuText,MenuDesignate):SetTime(MenuTime):SetTag(self.DesignateName)
if string.find(Designating,"L",1,true)==nil then
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Search other target",DetectedMenu,self.MenuForget,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName)
for LaserCode,MenuText in pairs(self.MenuLaserCodes)do
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,string.format(MenuText,LaserCode),DetectedMenu,self.MenuLaseCode,self,DesignateIndex,self.LaseDuration,LaserCode):SetTime(MenuTime):SetTag(self.DesignateName)
end
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Lase with random laser code(s)",DetectedMenu,self.MenuLaseOn,self,DesignateIndex,self.LaseDuration):SetTime(MenuTime):SetTag(self.DesignateName)
else
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Stop lasing",DetectedMenu,self.MenuLaseOff,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName)
end
if string.find(Designating,"S",1,true)==nil then
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke red",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Red):SetTime(MenuTime):SetTag(self.DesignateName)
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke blue",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Blue):SetTime(MenuTime):SetTag(self.DesignateName)
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke green",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Green):SetTime(MenuTime):SetTag(self.DesignateName)
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke white",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.White):SetTime(MenuTime):SetTag(self.DesignateName)
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke orange",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Orange):SetTime(MenuTime):SetTag(self.DesignateName)
end
if string.find(Designating,"I",1,true)==nil then
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Illuminate",DetectedMenu,self.MenuIlluminate,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName)
end
end
DesignateCount=DesignateCount+1
if DesignateCount>10 then
break
end
end
MenuDesignate:Remove(MenuTime,self.DesignateName)
MenuDesignate:Set()
end
function DESIGNATE:SetDesignateMenu()
self.AttackSet:Flush(self)
local Delay=1
self.AttackSet:ForEachGroupAlive(
function(AttackGroup)
self:ScheduleOnce(Delay,self.SetMenu,self,AttackGroup)
Delay=Delay+1
end
)
return self
end
function DESIGNATE:MenuStatus(AttackGroup)
self:F("Status")
self:SendStatus(AttackGroup)
end
function DESIGNATE:MenuFlashStatus(AttackGroup,Flash)
self:F("Flash Status")
self.FlashStatusMenu[AttackGroup]=Flash
self:SetDesignateMenu()
end
function DESIGNATE:MenuForget(Index)
self:F("Forget")
self.Designating[Index]=""
self:SetDesignateMenu()
end
function DESIGNATE:MenuAutoLase(AutoLase)
self:F("AutoLase")
self:SetAutoLase(AutoLase,true)
end
function DESIGNATE:MenuSmoke(Index,Color)
self:F("Designate through Smoke")
if string.find(self.Designating[Index],"S")==nil then
self.Designating[Index]=self.Designating[Index].."S"
end
self:Smoke(Index,Color)
self:SetDesignateMenu()
end
function DESIGNATE:MenuIlluminate(Index)
self:F("Designate through Illumination")
if string.find(self.Designating[Index],"I",1,true)==nil then
self.Designating[Index]=self.Designating[Index].."I"
end
self:__Illuminate(1,Index)
self:SetDesignateMenu()
end
function DESIGNATE:MenuLaseOn(Index,Duration)
self:F("Designate through Lase")
self:__LaseOn(1,Index,Duration)
self:SetDesignateMenu()
end
function DESIGNATE:MenuLaseCode(Index,Duration,LaserCode)
self:F("Designate through Lase using "..LaserCode)
self:__LaseOn(1,Index,Duration,LaserCode)
self:SetDesignateMenu()
end
function DESIGNATE:MenuLaseOff(Index,Duration)
self:F("Lasing off")
self.Designating[Index]=string.gsub(self.Designating[Index],"L","")
self:__LaseOff(1,Index)
self:SetDesignateMenu()
end
function DESIGNATE:onafterLaseOn(From,Event,To,Index,Duration,LaserCode)
if string.find(self.Designating[Index],"L",1,true)==nil then
self.Designating[Index]=self.Designating[Index].."L"
self.LaseStart=timer.getTime()
self.LaseDuration=Duration
self:Lasing(Index,Duration,LaserCode)
end
end
function DESIGNATE:onafterLasing(From,Event,To,Index,Duration,LaserCodeRequested)
local DetectedItem=self.Detection:GetDetectedItemByIndex(Index)
local TargetSetUnit=self.Detection:GetDetectedItemSet(DetectedItem)
local MarkingCount=0
local MarkedTypes={}
TargetSetUnit:Flush(self)
for TargetUnit,RecceData in pairs(self.Recces)do
local Recce=RecceData
self:F({TargetUnit=TargetUnit,Recce=Recce:GetName()})
if not Recce:IsLasing()then
local LaserCode=Recce:GetLaserCode()
self:F({ClearingLaserCode=LaserCode})
self.LaserCodesUsed[LaserCode]=nil
self.Recces[TargetUnit]=nil
end
end
if LaserCodeRequested then
for TargetUnit,RecceData in pairs(self.Recces)do
local Recce=RecceData
self:F({TargetUnit=TargetUnit,Recce=Recce:GetName()})
if Recce:IsLasing()then
Recce:LaseOff()
local LaserCode=Recce:GetLaserCode()
self:F({ClearingLaserCode=LaserCode})
self.LaserCodesUsed[LaserCode]=nil
self.Recces[TargetUnit]=nil
break
end
end
end
if self.AutoLase or(not self.AutoLase and(self.LaseStart+Duration>=timer.getTime()))then
TargetSetUnit:ForEachUnitPerThreatLevel(10,0,
function(TargetUnit)
self:F({TargetUnit=TargetUnit:GetName()})
if MarkingCount<self.MaximumMarkings then
if TargetUnit:IsAlive()then
local Recce=self.Recces[TargetUnit]
if not Recce then
self:F("Lasing...")
for RecceGroupID,RecceGroup in pairs(self.RecceSet:GetSet())do
for UnitID,UnitData in pairs(RecceGroup:GetUnits()or{})do
local RecceUnit=UnitData
local RecceUnitDesc=RecceUnit:GetDesc()
if RecceUnit:IsLasing()==false then
if RecceUnit:IsDetected(TargetUnit)and RecceUnit:IsLOS(TargetUnit)then
local LaserCodeIndex=math.random(1,#self.LaserCodes)
local LaserCode=self.LaserCodes[LaserCodeIndex]
if LaserCodeRequested and LaserCodeRequested~=LaserCode then
LaserCode=LaserCodeRequested
LaserCodeRequested=nil
end
if not self.LaserCodesUsed[LaserCode]then
self.LaserCodesUsed[LaserCode]=LaserCodeIndex
local Spot=RecceUnit:LaseUnit(TargetUnit,LaserCode,Duration)
local AttackSet=self.AttackSet
local DesignateName=self.DesignateName
function Spot:OnAfterDestroyed(From,Event,To)
self.Recce:MessageToSetGroup("Target "..TargetUnit:GetTypeName().." destroyed. "..TargetSetUnit:Count().." targets left.",
5,AttackSet,self.DesignateName)
end
self.Recces[TargetUnit]=RecceUnit
MarkingCount=MarkingCount+1
local TargetUnitType=TargetUnit:GetTypeName()
RecceUnit:MessageToSetGroup("Marking "..TargetUnit:GetTypeName().." with laser "..RecceUnit:GetSpot().LaserCode.." for "..Duration.."s.",
10,self.AttackSet,DesignateName)
if not MarkedTypes[TargetUnitType]then
MarkedTypes[TargetUnitType]=true
end
return
end
else
end
else
if not RecceUnit:IsDetected(TargetUnit)or not RecceUnit:IsLOS(TargetUnit)then
local Recce=self.Recces[TargetUnit]
if Recce then
Recce:LaseOff()
Recce:MessageToSetGroup("Target "..TargetUnit:GetTypeName()"out of LOS. Cancelling lase!",10,self.AttackSet,self.DesignateName)
end
else
local TargetUnitType=TargetUnit:GetTypeName()
if not MarkedTypes[TargetUnitType]then
MarkedTypes[TargetUnitType]=true
end
end
end
end
end
else
MarkingCount=MarkingCount+1
local TargetUnitType=TargetUnit:GetTypeName()
if not MarkedTypes[TargetUnitType]then
MarkedTypes[TargetUnitType]=true
end
Recce:MessageToSetGroup(self.DesignateName..": Marking "..TargetUnit:GetTypeName().." with laser "..Recce.LaserCode..".",10,self.AttackSet)
end
end
end
end
)
self:__Lasing(-self.LaseDuration,Index,Duration,LaserCodeRequested)
self:SetDesignateMenu()
else
self:LaseOff(Index)
end
end
function DESIGNATE:onafterLaseOff(From,Event,To,Index)
local CC=self.CC:GetPositionable()
if CC then
CC:MessageToSetGroup("Stopped lasing.",5,self.AttackSet,self.DesignateName)
end
local DetectedItem=self.Detection:GetDetectedItemByIndex(Index)
local TargetSetUnit=self.Detection:GetDetectedItemSet(DetectedItem)
local Recces=self.Recces
for TargetID,RecceData in pairs(Recces)do
local Recce=RecceData
Recce:MessageToSetGroup("Stopped lasing "..Recce:GetSpot().Target:GetTypeName()..".",5,self.AttackSet,self.DesignateName)
Recce:LaseOff()
end
Recces=nil
self.Recces={}
self.LaserCodesUsed={}
self.Designating[Index]=string.gsub(self.Designating[Index],"L","")
self:SetDesignateMenu()
end
function DESIGNATE:onafterSmoke(From,Event,To,Index,Color)
local DetectedItem=self.Detection:GetDetectedItemByIndex(Index)
local TargetSetUnit=self.Detection:GetDetectedItemSet(DetectedItem)
local TargetSetUnitCount=TargetSetUnit:Count()
local MarkedCount=0
TargetSetUnit:ForEachUnitPerThreatLevel(10,0,
function(SmokeUnit)
if MarkedCount<self.MaximumMarkings then
MarkedCount=MarkedCount+1
self:F("Smoking ...")
local RecceGroup=self.RecceSet:FindNearestGroupFromPointVec2(SmokeUnit:GetPointVec2())
local RecceUnit=RecceGroup:GetUnit(1)
if RecceUnit then
RecceUnit:MessageToSetGroup("Smoking "..SmokeUnit:GetTypeName()..".",5,self.AttackSet,self.DesignateName)
if SmokeUnit:IsAlive()then
SmokeUnit:Smoke(Color,50,2)
end
self.MarkScheduler:Schedule(self,
function()
self:DoneSmoking(Index)
end,{},math.random(180,240)
)
end
end
end
)
end
function DESIGNATE:onafterIlluminate(From,Event,To,Index)
local DetectedItem=self.Detection:GetDetectedItemByIndex(Index)
local TargetSetUnit=self.Detection:GetDetectedItemSet(DetectedItem)
local TargetUnit=TargetSetUnit:GetFirst()
if TargetUnit then
local RecceGroup=self.RecceSet:FindNearestGroupFromPointVec2(TargetUnit:GetPointVec2())
local RecceUnit=RecceGroup:GetUnit(1)
if RecceUnit then
RecceUnit:MessageToSetGroup("Illuminating "..TargetUnit:GetTypeName()..".",5,self.AttackSet,self.DesignateName)
if TargetUnit:IsAlive()then
TargetUnit:GetPointVec3():AddY(math.random(350,500)):AddX(math.random(-50,50)):AddZ(math.random(-50,50)):IlluminationBomb()
TargetUnit:GetPointVec3():AddY(math.random(350,500)):AddX(math.random(-50,50)):AddZ(math.random(-50,50)):IlluminationBomb()
end
self.MarkScheduler:Schedule(self,
function()
self:DoneIlluminating(Index)
end,{},math.random(60,90)
)
end
end
end
function DESIGNATE:onafterDoneSmoking(From,Event,To,Index)
self.Designating[Index]=string.gsub(self.Designating[Index],"S","")
self:SetDesignateMenu()
end
function DESIGNATE:onafterDoneIlluminating(From,Event,To,Index)
self.Designating[Index]=string.gsub(self.Designating[Index],"I","")
self:SetDesignateMenu()
end
end
RAT={
ClassName="RAT",
Debug=false,
templategroup=nil,
alias=nil,
spawninitialized=false,
spawndelay=5,
spawninterval=5,
coalition=nil,
country=nil,
category=nil,
groupsize=nil,
friendly="same",
ctable={},
aircraft={},
Vcruisemax=nil,
Vclimb=1500,
AlphaDescent=3.6,
roe="hold",
rot="noreaction",
takeoff=0,
landing=9,
mindist=5000,
maxdist=5000000,
airports_map={},
airports={},
random_departure=true,
random_destination=true,
departure_ports={},
destination_ports={},
Ndestination_Airports=0,
Ndestination_Zones=0,
Ndeparture_Airports=0,
Ndeparture_Zones=0,
destinationzone=false,
return_zones={},
returnzone=false,
excluded_ports={},
departure_Azone=nil,
destination_Azone=nil,
addfriendlydepartures=false,
addfriendlydestinations=false,
ratcraft={},
Tinactive=600,
reportstatus=false,
statusinterval=30,
placemarkers=false,
FLcruise=nil,
FLminuser=nil,
FLmaxuser=nil,
FLuser=nil,
commute=false,
starshape=false,
homebase=nil,
continuejourney=false,
alive=0,
ngroups=nil,
f10menu=false,
Menu={},
SubMenuName=nil,
respawn_at_landing=false,
norespawn=false,
respawn_after_takeoff=false,
respawn_after_crash=true,
respawn_inair=true,
respawn_delay=0,
markerids={},
waypointdescriptions={},
waypointstatus={},
livery=nil,
skill="High",
ATCswitch=true,
radio=nil,
frequency=nil,
modulation=nil,
actype=nil,
uncontrolled=false,
invisible=false,
immortal=false,
activate_uncontrolled=false,
activate_delay=5,
activate_delta=5,
activate_frand=0,
activate_max=1,
onboardnum=nil,
onboardnum0=1,
checkonrunway=true,
onrunwayradius=75,
onrunwaymaxretry=3,
checkontop=false,
ontopradius=2,
termtype=nil,
parkingscanradius=40,
parkingscanscenery=false,
parkingverysafe=false,
despawnair=true,
eplrs=false,
}
RAT.cat={
plane="plane",
heli="heli",
}
RAT.wp={
coldorhot=0,
air=1,
runway=2,
hot=3,
cold=4,
climb=5,
cruise=6,
descent=7,
holding=8,
landing=9,
finalwp=10,
}
RAT.status={
Departure="At departure point",
Climb="Climbing",
Cruise="Cruising",
Uturn="Flying back home",
Descent="Descending",
DescentHolding="Descend to holding point",
Holding="Holding",
Destination="Arrived at destination",
Uncontrolled="Uncontrolled",
Spawned="Spawned",
EventBirthAir="Born in air",
EventBirth="Ready and starting engines",
EventEngineStartAir="On journey",
EventEngineStart="Started engines and taxiing",
EventTakeoff="Airborne after take-off",
EventLand="Landed and taxiing",
EventEngineShutdown="Engines off",
EventDead="Dead",
EventCrash="Crashed",
}
RAT.coal={
same="same",
sameonly="sameonly",
neutral="neutral",
}
RAT.unit={
ft2meter=0.305,
kmh2ms=0.278,
FL2m=30.48,
nm2km=1.852,
nm2m=1852,
}
RAT.ROE={
weaponhold="hold",
weaponfree="free",
returnfire="return",
}
RAT.ROT={
evade="evade",
passive="passive",
noreaction="noreaction",
}
RAT.ATC={
init=false,
flight={},
airport={},
unregistered=-1,
onfinal=-100,
Nclearance=2,
delay=240,
messages=true,
}
RAT.markerid=0
RAT.MenuF10=nil
RAT.id="RAT | "
RAT.version={
version="2.3.9",
print=true,
}
function RAT:New(groupname,alias)
BASE:F({groupname=groupname,alias=alias})
self=BASE:Inherit(self,SPAWN:NewWithAlias(groupname,alias))
if RAT.version.print then
env.info(RAT.id.."Version "..RAT.version.version)
RAT.version.print=false
end
self:F(RAT.id..string.format("Creating new RAT object from template: %s.",groupname))
alias=alias or groupname
self.alias=alias
local DCSgroup=Group.getByName(groupname)
if DCSgroup==nil then
self:E(RAT.id..string.format("ERROR: Group with name %s does not exist in the mission editor!",groupname))
return nil
end
self.templategroup=GROUP:FindByName(groupname)
self.groupsize=self.templategroup:GetSize()
self.coalition=DCSgroup:getCoalition()
self:_InitAircraft(DCSgroup)
self:_GetAirportsOfMap()
return self
end
function RAT:Spawn(naircraft)
if self.spawninitialized==true then
self:E("ERROR: Spawn function should only be called once per RAT object! Exiting and returning nil.")
return nil
else
self.spawninitialized=true
end
self.ngroups=naircraft or 1
if self.ATCswitch and not RAT.ATC.init then
self:_ATCInit(self.airports_map)
end
if self.f10menu and not RAT.MenuF10 then
RAT.MenuF10=MENU_MISSION:New("RAT")
end
self:_SetCoalitionTable()
self:_GetAirportsOfCoalition()
if not self.SubMenuName then
self.SubMenuName=self.alias
end
if self.departure_Azone~=nil then
self.departure_ports=self:_GetAirportsInZone(self.departure_Azone)
end
if self.destination_Azone~=nil then
self.destination_ports=self:_GetAirportsInZone(self.destination_Azone)
end
if self.addfriendlydepartures then
self:_AddFriendlyAirports(self.departure_ports)
end
if self.addfriendlydestinations then
self:_AddFriendlyAirports(self.destination_ports)
end
if self.FLcruise==nil then
if self.category==RAT.cat.plane then
self.FLcruise=200*RAT.unit.FL2m
else
self.FLcruise=005*RAT.unit.FL2m
end
end
if self.category==RAT.cat.heli then
self.mindist=50
end
self:_CheckConsistency()
local text=string.format("\n******************************************************\n")
text=text..string.format("Spawning %i aircraft from template %s of type %s.\n",self.ngroups,self.SpawnTemplatePrefix,self.aircraft.type)
text=text..string.format("Alias: %s\n",self.alias)
text=text..string.format("Category: %s\n",self.category)
text=text..string.format("Friendly coalitions: %s\n",self.friendly)
text=text..string.format("Number of airports on map  : %i\n",#self.airports_map)
text=text..string.format("Number of friendly airports: %i\n",#self.airports)
text=text..string.format("Totally random departure: %s\n",tostring(self.random_departure))
if not self.random_departure then
text=text..string.format("Number of departure airports: %d\n",self.Ndeparture_Airports)
text=text..string.format("Number of departure zones   : %d\n",self.Ndeparture_Zones)
end
text=text..string.format("Totally random destination: %s\n",tostring(self.random_destination))
if not self.random_destination then
text=text..string.format("Number of destination airports: %d\n",self.Ndestination_Airports)
text=text..string.format("Number of destination zones   : %d\n",self.Ndestination_Zones)
end
text=text..string.format("Min dist to destination: %4.1f\n",self.mindist)
text=text..string.format("Max dist to destination: %4.1f\n",self.maxdist)
text=text..string.format("Terminal type: %s\n",tostring(self.termtype))
text=text..string.format("Takeoff type: %i\n",self.takeoff)
text=text..string.format("Landing type: %i\n",self.landing)
text=text..string.format("Commute: %s\n",tostring(self.commute))
text=text..string.format("Journey: %s\n",tostring(self.continuejourney))
text=text..string.format("Destination Zone: %s\n",tostring(self.destinationzone))
text=text..string.format("Return Zone: %s\n",tostring(self.returnzone))
text=text..string.format("Spawn delay: %4.1f\n",self.spawndelay)
text=text..string.format("Spawn interval: %4.1f\n",self.spawninterval)
text=text..string.format("Respawn delay: %s\n",tostring(self.respawn_delay))
text=text..string.format("Respawn off: %s\n",tostring(self.norespawn))
text=text..string.format("Respawn after landing: %s\n",tostring(self.respawn_at_landing))
text=text..string.format("Respawn after take-off: %s\n",tostring(self.respawn_after_takeoff))
text=text..string.format("Respawn after crash: %s\n",tostring(self.respawn_after_crash))
text=text..string.format("Respawn in air: %s\n",tostring(self.respawn_inair))
text=text..string.format("ROE: %s\n",tostring(self.roe))
text=text..string.format("ROT: %s\n",tostring(self.rot))
text=text..string.format("Immortal: %s\n",tostring(self.immortal))
text=text..string.format("Invisible: %s\n",tostring(self.invisible))
text=text..string.format("Vclimb: %4.1f\n",self.Vclimb)
text=text..string.format("AlphaDescent: %4.2f\n",self.AlphaDescent)
text=text..string.format("Vcruisemax: %s\n",tostring(self.Vcruisemax))
text=text..string.format("FLcruise =  %6.1f km = FL%3.0f\n",self.FLcruise/1000,self.FLcruise/RAT.unit.FL2m)
text=text..string.format("FLuser: %s\n",tostring(self.Fluser))
text=text..string.format("FLminuser: %s\n",tostring(self.FLminuser))
text=text..string.format("FLmaxuser: %s\n",tostring(self.FLmaxuser))
text=text..string.format("Place markers: %s\n",tostring(self.placemarkers))
text=text..string.format("Report status: %s\n",tostring(self.reportstatus))
text=text..string.format("Status interval: %4.1f\n",self.statusinterval)
text=text..string.format("Time inactive: %4.1f\n",self.Tinactive)
text=text..string.format("Create F10 menu : %s\n",tostring(self.f10menu))
text=text..string.format("F10 submenu name: %s\n",self.SubMenuName)
text=text..string.format("ATC enabled : %s\n",tostring(self.ATCswitch))
text=text..string.format("Radio comms      : %s\n",tostring(self.radio))
text=text..string.format("Radio frequency  : %s\n",tostring(self.frequency))
text=text..string.format("Radio modulation : %s\n",tostring(self.frequency))
text=text..string.format("Tail # prefix    : %s\n",tostring(self.onboardnum))
text=text..string.format("Check on runway: %s\n",tostring(self.checkonrunway))
text=text..string.format("Max respawn attempts: %s\n",tostring(self.onrunwaymaxretry))
text=text..string.format("Check on top: %s\n",tostring(self.checkontop))
text=text..string.format("Uncontrolled: %s\n",tostring(self.uncontrolled))
if self.uncontrolled and self.activate_uncontrolled then
text=text..string.format("Uncontrolled max  : %4.1f\n",self.activate_max)
text=text..string.format("Uncontrolled delay: %4.1f\n",self.activate_delay)
text=text..string.format("Uncontrolled delta: %4.1f\n",self.activate_delta)
text=text..string.format("Uncontrolled frand: %4.1f\n",self.activate_frand)
end
if self.livery then
text=text..string.format("Available liveries:\n")
for _,livery in pairs(self.livery)do
text=text..string.format("- %s\n",livery)
end
end
text=text..string.format("******************************************************\n")
self:T(RAT.id..text)
if self.f10menu then
self.Menu[self.SubMenuName]=MENU_MISSION:New(self.SubMenuName,RAT.MenuF10)
self.Menu[self.SubMenuName]["groups"]=MENU_MISSION:New("Groups",self.Menu[self.SubMenuName])
MENU_MISSION_COMMAND:New("Spawn new group",self.Menu[self.SubMenuName],self._SpawnWithRoute,self)
MENU_MISSION_COMMAND:New("Delete markers",self.Menu[self.SubMenuName],self._DeleteMarkers,self)
MENU_MISSION_COMMAND:New("Status report",self.Menu[self.SubMenuName],self.Status,self,true)
end
local Tstart=self.spawndelay
local dt=self.spawninterval
if self.takeoff==RAT.wp.runway and not self.random_departure then
dt=math.max(dt,180)
end
local Tstop=Tstart+dt*(self.ngroups-1)
SCHEDULER:New(nil,self.Status,{self},Tstart+1,self.statusinterval)
self:HandleEvent(EVENTS.Birth,self._OnBirth)
self:HandleEvent(EVENTS.EngineStartup,self._OnEngineStartup)
self:HandleEvent(EVENTS.Takeoff,self._OnTakeoff)
self:HandleEvent(EVENTS.Land,self._OnLand)
self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutdown)
self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash)
self:HandleEvent(EVENTS.Hit,self._OnHit)
if self.ngroups==0 then
return nil
end
SCHEDULER:New(nil,self._SpawnWithRoute,{self},Tstart,dt,0.0,Tstop)
if self.uncontrolled and self.activate_uncontrolled then
SCHEDULER:New(nil,self._ActivateUncontrolled,{self},self.activate_delay,self.activate_delta,self.activate_frand)
end
return true
end
function RAT:_CheckConsistency()
self:F2()
if not self.random_departure then
for _,name in pairs(self.departure_ports)do
if self:_AirportExists(name)then
self.Ndeparture_Airports=self.Ndeparture_Airports+1
elseif self:_ZoneExists(name)then
self.Ndeparture_Zones=self.Ndeparture_Zones+1
end
end
if self.Ndeparture_Zones>0 and self.takeoff~=RAT.wp.air then
self.takeoff=RAT.wp.air
self:E(RAT.id..string.format("ERROR: At least one zone defined as departure and takeoff is NOT set to air. Enabling air start for RAT group %s!",self.alias))
end
if self.Ndeparture_Airports==0 and self.Ndeparture_Zone==0 then
self.random_departure=true
local text=string.format("No airports or zones found given in SetDeparture(). Enabling random departure airports for RAT group %s!",self.alias)
self:E(RAT.id.."ERROR: "..text)
MESSAGE:New(text,30):ToAll()
end
end
if not self.random_destination then
for _,name in pairs(self.destination_ports)do
if self:_AirportExists(name)then
self.Ndestination_Airports=self.Ndestination_Airports+1
elseif self:_ZoneExists(name)then
self.Ndestination_Zones=self.Ndestination_Zones+1
end
end
if self.Ndestination_Zones>0 and self.landing~=RAT.wp.air and not self.returnzone then
self.landing=RAT.wp.air
self.destinationzone=true
self:E(RAT.id.."ERROR: At least one zone defined as destination and landing is NOT set to air. Enabling destination zone!")
end
if self.Ndestination_Airports==0 and self.Ndestination_Zones==0 then
self.random_destination=true
local text="No airports or zones found given in SetDestination(). Enabling random destination airports!"
self:E(RAT.id.."ERROR: "..text)
MESSAGE:New(text,30):ToAll()
end
end
if self.destinationzone and self.returnzone then
self:E(RAT.id.."ERROR: Destination zone _and_ return to zone not possible! Disabling return to zone.")
self.returnzone=false
end
if self.returnzone and self.takeoff==RAT.wp.air then
self.landing=RAT.wp.air
end
if self.FLminuser then
self.FLminuser=math.min(self.FLminuser,self.aircraft.ceiling)
end
if self.FLmaxuser then
self.FLmaxuser=math.min(self.FLmaxuser,self.aircraft.ceiling)
end
if self.FLcruise then
self.FLcruise=math.min(self.FLcruise,self.aircraft.ceiling)
end
if self.FLminuser and self.FLmaxuser then
if self.FLminuser>self.FLmaxuser then
local min=self.FLminuser
local max=self.FLmaxuser
self.FLminuser=max
self.FLmaxuser=min
end
end
if self.FLminuser and self.FLcruise<self.FLminuser then
self.FLcruise=self.FLminuser
end
if self.FLmaxuser and self.FLcruise>self.FLmaxuser then
self.FLcruise=self.FLmaxuser
end
if self.uncontrolled then
self.takeoff=RAT.wp.cold
end
end
function RAT:SetCoalition(friendly)
self:F2(friendly)
if friendly:lower()=="sameonly"then
self.friendly=RAT.coal.sameonly
elseif friendly:lower()=="neutral"then
self.friendly=RAT.coal.neutral
else
self.friendly=RAT.coal.same
end
return self
end
function RAT:SetCoalitionAircraft(color)
self:F2(color)
if color:lower()=="blue"then
self.coalition=coalition.side.BLUE
if not self.country then
self.country=country.id.USA
end
elseif color:lower()=="red"then
self.coalition=coalition.side.RED
if not self.country then
self.country=country.id.RUSSIA
end
elseif color:lower()=="neutral"then
self.coalition=coalition.side.NEUTRAL
if not self.country then
self.country=country.id.SWITZERLAND
end
end
return self
end
function RAT:SetCountry(id)
self:F2(id)
self.country=id
return self
end
function RAT:SetTerminalType(termtype)
self:F2(termtype)
self.termtype=termtype
return self
end
function RAT:SetParkingScanRadius(radius)
self:F2(radius)
self.parkingscanradius=radius or 50
return self
end
function RAT:SetParkingScanSceneryON()
self:F2()
self.parkingscanscenery=true
return self
end
function RAT:SetParkingScanSceneryOFF()
self:F2()
self.parkingscanscenery=false
return self
end
function RAT:SetParkingSpotSafeON()
self:F2()
self.parkingverysafe=true
return self
end
function RAT:SetParkingSpotSafeOFF()
self:F2()
self.parkingverysafe=false
return self
end
function RAT:SetDespawnAirOFF()
self.despawnair=false
return self
end
function RAT:SetTakeoff(type)
self:F2(type)
local _Type
if type:lower()=="takeoff-cold"or type:lower()=="cold"then
_Type=RAT.wp.cold
elseif type:lower()=="takeoff-hot"or type:lower()=="hot"then
_Type=RAT.wp.hot
elseif type:lower()=="takeoff-runway"or type:lower()=="runway"then
_Type=RAT.wp.runway
elseif type:lower()=="air"then
_Type=RAT.wp.air
else
_Type=RAT.wp.coldorhot
end
self.takeoff=_Type
return self
end
function RAT:SetTakeoffCold()
self.takeoff=RAT.wp.cold
return self
end
function RAT:SetTakeoffHot()
self.takeoff=RAT.wp.hot
return self
end
function RAT:SetTakeoffRunway()
self.takeoff=RAT.wp.runway
return self
end
function RAT:SetTakeoffColdOrHot()
self.takeoff=RAT.wp.coldorhot
return self
end
function RAT:SetTakeoffAir()
self.takeoff=RAT.wp.air
return self
end
function RAT:SetDeparture(departurenames)
self:F2(departurenames)
self.random_departure=false
local names
if type(departurenames)=="table"then
names=departurenames
elseif type(departurenames)=="string"then
names={departurenames}
else
self:E(RAT.id.."ERROR: Input parameter must be a string or a table in SetDeparture()!")
end
for _,name in pairs(names)do
if self:_AirportExists(name)then
table.insert(self.departure_ports,name)
elseif self:_ZoneExists(name)then
table.insert(self.departure_ports,name)
else
self:E(RAT.id.."ERROR: No departure airport or zone found with name "..name)
end
end
return self
end
function RAT:SetDestination(destinationnames)
self:F2(destinationnames)
self.random_destination=false
local names
if type(destinationnames)=="table"then
names=destinationnames
elseif type(destinationnames)=="string"then
names={destinationnames}
else
self:E(RAT.id.."ERROR: Input parameter must be a string or a table in SetDestination()!")
end
for _,name in pairs(names)do
if self:_AirportExists(name)then
table.insert(self.destination_ports,name)
elseif self:_ZoneExists(name)then
table.insert(self.destination_ports,name)
else
self:E(RAT.id.."ERROR: No destination airport or zone found with name "..name)
end
end
return self
end
function RAT:DestinationZone()
self:F2()
self.destinationzone=true
self.landing=RAT.wp.air
return self
end
function RAT:ReturnZone()
self:F2()
self.returnzone=true
return self
end
function RAT:SetDestinationsFromZone(zone)
self:F2(zone)
self.random_destination=false
self.destination_Azone=zone
return self
end
function RAT:SetDeparturesFromZone(zone)
self:F2(zone)
self.random_departure=false
self.departure_Azone=zone
return self
end
function RAT:AddFriendlyAirportsToDepartures()
self:F2()
self.addfriendlydepartures=true
return self
end
function RAT:AddFriendlyAirportsToDestinations()
self:F2()
self.addfriendlydestinations=true
return self
end
function RAT:ExcludedAirports(ports)
self:F2(ports)
if type(ports)=="string"then
self.excluded_ports={ports}
else
self.excluded_ports=ports
end
return self
end
function RAT:SetAISkill(skill)
self:F2(skill)
if skill:lower()=="average"then
self.skill="Average"
elseif skill:lower()=="good"then
self.skill="Good"
elseif skill:lower()=="excellent"then
self.skill="Excellent"
elseif skill:lower()=="random"then
self.skill="Random"
else
self.skill="High"
end
return self
end
function RAT:Livery(skins)
self:F2(skins)
if type(skins)=="string"then
self.livery={skins}
else
self.livery=skins
end
return self
end
function RAT:ChangeAircraft(actype)
self:F2(actype)
self.actype=actype
return self
end
function RAT:ContinueJourney()
self:F2()
self.continuejourney=true
self.commute=false
return self
end
function RAT:Commute(starshape)
self:F2()
self.commute=true
self.continuejourney=false
if starshape then
self.starshape=starshape
else
self.starshape=false
end
return self
end
function RAT:SetSpawnDelay(delay)
self:F2(delay)
delay=delay or 5
self.spawndelay=math.max(0.5,delay)
return self
end
function RAT:SetSpawnInterval(interval)
self:F2(interval)
interval=interval or 5
self.spawninterval=math.max(0.5,interval)
return self
end
function RAT:RespawnAfterLanding(delay)
self:F2(delay)
delay=delay or 180
self.respawn_at_landing=true
delay=math.max(1.0,delay)
self.respawn_delay=delay
return self
end
function RAT:SetRespawnDelay(delay)
self:F2(delay)
delay=delay or 1.0
delay=math.max(1.0,delay)
self.respawn_delay=delay
return self
end
function RAT:NoRespawn()
self:F2()
self.norespawn=true
return self
end
function RAT:SetMaxRespawnTriedWhenSpawnedOnRunway(n)
self:F2(n)
n=n or 3
self.onrunwaymaxretry=n
return self
end
function RAT:RespawnAfterTakeoff()
self:F2()
self.respawn_after_takeoff=true
return self
end
function RAT:RespawnAfterCrashON()
self:F2()
self.respawn_after_crash=true
return self
end
function RAT:RespawnAfterCrashOFF()
self:F2()
self.respawn_after_crash=false
return self
end
function RAT:RespawnInAirAllowed()
self:F2()
self.respawn_inair=true
return self
end
function RAT:RespawnInAirNotAllowed()
self:F2()
self.respawn_inair=false
return self
end
function RAT:CheckOnRunway(switch,distance)
self:F2(switch)
if switch==nil then
switch=true
end
self.checkonrunway=switch
self.onrunwayradius=distance or 75
return self
end
function RAT:CheckOnTop(switch,radius)
self:F2(switch)
if switch==nil then
switch=true
end
self.checkontop=switch
self.ontopradius=radius or 2
return self
end
function RAT:ParkingSpotDB(switch)
self:E("RAT ParkingSpotDB function is obsolete and will be removed soon!")
return self
end
function RAT:RadioON()
self:F2()
self.radio=true
return self
end
function RAT:RadioOFF()
self:F2()
self.radio=false
return self
end
function RAT:RadioFrequency(frequency)
self:F2(frequency)
self.frequency=frequency
return self
end
function RAT:RadioModulation(modulation)
self:F2(modulation)
if modulation=="AM"then
self.modulation=radio.modulation.AM
elseif modulation=="FM"then
self.modulation=radio.modulation.FM
else
self.modulation=radio.modulation.AM
end
return self
end
function RAT:RadioMenuON()
self:F2()
self.f10menu=true
return self
end
function RAT:RadioMenuOFF()
self:F2()
self.f10menu=false
return self
end
function RAT:Invisible()
self:F2()
self.invisible=true
return self
end
function RAT:SetEPLRS(switch)
if switch==nil or switch==true then
self.eplrs=true
else
self.eplrs=false
end
return self
end
function RAT:Immortal()
self:F2()
self.immortal=true
return self
end
function RAT:Uncontrolled()
self:F2()
self.uncontrolled=true
return self
end
function RAT:ActivateUncontrolled(maxactivated,delay,delta,frand)
self:F2({max=maxactivated,delay=delay,delta=delta,rand=frand})
self.activate_uncontrolled=true
self.activate_max=maxactivated or 1
self.activate_delay=delay or 1
self.activate_delta=delta or 1
self.activate_frand=frand or 0
self.activate_delay=math.max(self.activate_delay,1)
self.activate_delta=math.max(self.activate_delta,0)
self.activate_frand=math.max(self.activate_frand,0)
self.activate_frand=math.min(self.activate_frand,1)
return self
end
function RAT:TimeDestroyInactive(time)
self:F2(time)
time=time or self.Tinactive
time=math.max(time,60)
self.Tinactive=time
return self
end
function RAT:SetMaxCruiseSpeed(speed)
self:F2(speed)
self.Vcruisemax=speed/3.6
return self
end
function RAT:SetClimbRate(rate)
self:F2(rate)
rate=rate or self.Vclimb
rate=math.max(rate,100)
rate=math.min(rate,15000)
self.Vclimb=rate
return self
end
function RAT:SetDescentAngle(angle)
self:F2(angle)
angle=angle or self.AlphaDescent
angle=math.max(angle,0.5)
angle=math.min(angle,50)
self.AlphaDescent=angle
return self
end
function RAT:SetROE(roe)
self:F2(roe)
if roe=="return"then
self.roe=RAT.ROE.returnfire
elseif roe=="free"then
self.roe=RAT.ROE.weaponfree
else
self.roe=RAT.ROE.weaponhold
end
return self
end
function RAT:SetROT(rot)
self:F2(rot)
if rot=="passive"then
self.rot=RAT.ROT.passive
elseif rot=="evade"then
self.rot=RAT.ROT.evade
else
self.rot=RAT.ROT.noreaction
end
return self
end
function RAT:MenuName(name)
self:F2(name)
self.SubMenuName=tostring(name)
return self
end
function RAT:EnableATC(switch)
self:F2(switch)
if switch==nil then
switch=true
end
self.ATCswitch=switch
return self
end
function RAT:ATC_Messages(switch)
self:F2(switch)
if switch==nil then
switch=true
end
RAT.ATC.messages=switch
return self
end
function RAT:ATC_Clearance(n)
self:F2(n)
RAT.ATC.Nclearance=n or 2
return self
end
function RAT:ATC_Delay(time)
self:F2(time)
RAT.ATC.delay=time or 240
return self
end
function RAT:SetMinDistance(dist)
self:F2(dist)
self.mindist=math.max(100,dist*1000)
return self
end
function RAT:SetMaxDistance(dist)
self:F2(dist)
self.maxdist=dist*1000
return self
end
function RAT:_Debug(switch)
self:F2(switch)
if switch==nil then
switch=true
end
self.Debug=switch
return self
end
function RAT:Debugmode()
self:F2()
self.Debug=true
return self
end
function RAT:StatusReports(switch)
self:F2(switch)
if switch==nil then
switch=true
end
self.reportstatus=switch
return self
end
function RAT:PlaceMarkers(switch)
self:F2(switch)
if switch==nil then
switch=true
end
self.placemarkers=switch
return self
end
function RAT:SetFL(FL)
self:F2(FL)
FL=FL or self.FLcruise
FL=math.max(FL,0)
self.FLuser=FL*RAT.unit.FL2m
return self
end
function RAT:SetFLmax(FL)
self:F2(FL)
self.FLmaxuser=FL*RAT.unit.FL2m
return self
end
function RAT:SetMaxCruiseAltitude(alt)
self:F2(alt)
self.FLmaxuser=alt
return self
end
function RAT:SetFLmin(FL)
self:F2(FL)
self.FLminuser=FL*RAT.unit.FL2m
return self
end
function RAT:SetMinCruiseAltitude(alt)
self:F2(alt)
self.FLminuser=alt
return self
end
function RAT:SetFLcruise(FL)
self:F2(FL)
self.FLcruise=FL*RAT.unit.FL2m
return self
end
function RAT:SetCruiseAltitude(alt)
self:F2(alt)
self.FLcruise=alt
return self
end
function RAT:SetOnboardNum(tailnumprefix,zero)
self:F2({tailnumprefix=tailnumprefix,zero=zero})
self.onboardnum=tailnumprefix
if zero~=nil then
self.onboardnum0=zero
end
return self
end
function RAT:_InitAircraft(DCSgroup)
self:F2(DCSgroup)
local DCSunit=DCSgroup:getUnit(1)
local DCSdesc=DCSunit:getDesc()
local DCScategory=DCSgroup:getCategory()
local DCStype=DCSunit:getTypeName()
if DCScategory==Group.Category.AIRPLANE then
self.category=RAT.cat.plane
elseif DCScategory==Group.Category.HELICOPTER then
self.category=RAT.cat.heli
else
self.category="other"
self:E(RAT.id.."ERROR: Group of RAT is neither airplane nor helicopter!")
end
self.aircraft.type=DCStype
self.aircraft.fuel=DCSunit:getFuel()
self.aircraft.Rmax=DCSdesc.range*RAT.unit.nm2m
self.aircraft.Reff=self.aircraft.Rmax*self.aircraft.fuel*0.95
self.aircraft.Vmax=DCSdesc.speedMax
self.aircraft.Vymax=DCSdesc.VyMax
self.aircraft.ceiling=DCSdesc.Hmax
if DCSdesc.box then
self.aircraft.length=DCSdesc.box.max.x
self.aircraft.height=DCSdesc.box.max.y
self.aircraft.width=DCSdesc.box.max.z
elseif DCStype=="Mirage-F1CE"then
self.aircraft.length=16
self.aircraft.height=5
self.aircraft.width=9
end
self.aircraft.box=math.max(self.aircraft.length,self.aircraft.width)
local text=string.format("\n******************************************************\n")
text=text..string.format("Aircraft parameters:\n")
text=text..string.format("Template group  =  %s\n",self.SpawnTemplatePrefix)
text=text..string.format("Alias           =  %s\n",self.alias)
text=text..string.format("Category        =  %s\n",self.category)
text=text..string.format("Type            =  %s\n",self.aircraft.type)
text=text..string.format("Length (x)      = %6.1f m\n",self.aircraft.length)
text=text..string.format("Width  (z)      = %6.1f m\n",self.aircraft.width)
text=text..string.format("Height (y)      = %6.1f m\n",self.aircraft.height)
text=text..string.format("Max air speed   = %6.1f m/s\n",self.aircraft.Vmax)
text=text..string.format("Max climb speed = %6.1f m/s\n",self.aircraft.Vymax)
text=text..string.format("Initial Fuel    = %6.1f\n",self.aircraft.fuel*100)
text=text..string.format("Max range       = %6.1f km\n",self.aircraft.Rmax/1000)
text=text..string.format("Eff range       = %6.1f km (with 95 percent initial fuel amount)\n",self.aircraft.Reff/1000)
text=text..string.format("Ceiling         = %6.1f km = FL%3.0f\n",self.aircraft.ceiling/1000,self.aircraft.ceiling/RAT.unit.FL2m)
text=text..string.format("******************************************************\n")
self:T(RAT.id..text)
end
function RAT:_SpawnWithRoute(_departure,_destination,_takeoff,_landing,_livery,_waypoint,_lastpos,_nrespawn,parkingdata)
self:F({rat=RAT.id,departure=_departure,destination=_destination,takeoff=_takeoff,landing=_landing,livery=_livery,waypoint=_waypoint,lastpos=_lastpos,nrespawn=_nrespawn})
local takeoff=self.takeoff
local landing=self.landing
if _takeoff then
takeoff=_takeoff
end
if _landing then
landing=_landing
end
if takeoff==RAT.wp.coldorhot then
local temp={RAT.wp.cold,RAT.wp.hot}
takeoff=temp[math.random(2)]
end
local nrespawn=0
if _nrespawn then
nrespawn=_nrespawn
end
local departure,destination,waypoints,WPholding,WPfinal=self:_SetRoute(takeoff,landing,_departure,_destination,_waypoint)
if not(departure and destination and waypoints)then
return nil
end
local livery
if _livery then
livery=_livery
elseif self.livery then
livery=self.livery[math.random(#self.livery)]
local text=string.format("Chosen livery for group %s: %s",self:_AnticipatedGroupName(),livery)
self:T(RAT.id..text)
else
livery=nil
end
local successful=self:_ModifySpawnTemplate(waypoints,livery,_lastpos,departure,takeoff,parkingdata)
if not successful then
return nil
end
local group=self:SpawnWithIndex(self.SpawnIndex)
self.alive=self.alive+1
self:T(RAT.id..string.format("Alive groups counter now = %d.",self.alive))
if self.ATCswitch and landing==RAT.wp.landing then
if self.returnzone then
self:_ATCAddFlight(group:GetName(),departure:GetName())
else
self:_ATCAddFlight(group:GetName(),destination:GetName())
end
end
if self.placemarkers then
self:_PlaceMarkers(waypoints,self.SpawnIndex)
end
if self.invisible then
self:_CommandInvisible(group,true)
end
if self.immortal then
self:_CommandImmortal(group,true)
end
if self.eplrs then
group:CommandEPLRS(true,1)
end
self:_SetROE(group,self.roe)
self:_SetROT(group,self.rot)
self.ratcraft[self.SpawnIndex]={}
self.ratcraft[self.SpawnIndex]["group"]=group
self.ratcraft[self.SpawnIndex]["destination"]=destination
self.ratcraft[self.SpawnIndex]["departure"]=departure
self.ratcraft[self.SpawnIndex]["waypoints"]=waypoints
self.ratcraft[self.SpawnIndex]["airborne"]=group:InAir()
self.ratcraft[self.SpawnIndex]["nunits"]=group:GetInitialSize()
if group:InAir()then
self.ratcraft[self.SpawnIndex]["Tground"]=nil
self.ratcraft[self.SpawnIndex]["Pground"]=nil
self.ratcraft[self.SpawnIndex]["Uground"]=nil
self.ratcraft[self.SpawnIndex]["Tlastcheck"]=nil
else
self.ratcraft[self.SpawnIndex]["Tground"]=timer.getTime()
self.ratcraft[self.SpawnIndex]["Pground"]=group:GetCoordinate()
self.ratcraft[self.SpawnIndex]["Uground"]={}
for _,_unit in pairs(group:GetUnits())do
local _unitname=_unit:GetName()
self.ratcraft[self.SpawnIndex]["Uground"][_unitname]=_unit:GetCoordinate()
end
self.ratcraft[self.SpawnIndex]["Tlastcheck"]=timer.getTime()
end
self.ratcraft[self.SpawnIndex]["P0"]=group:GetCoordinate()
self.ratcraft[self.SpawnIndex]["Pnow"]=group:GetCoordinate()
self.ratcraft[self.SpawnIndex]["Distance"]=0
self.ratcraft[self.SpawnIndex].takeoff=takeoff
self.ratcraft[self.SpawnIndex].landing=landing
self.ratcraft[self.SpawnIndex].wpholding=WPholding
self.ratcraft[self.SpawnIndex].wpfinal=WPfinal
self.ratcraft[self.SpawnIndex].active=not self.uncontrolled
self.ratcraft[self.SpawnIndex]["status"]=RAT.status.Spawned
self.ratcraft[self.SpawnIndex].livery=livery
self.ratcraft[self.SpawnIndex].despawnme=false
self.ratcraft[self.SpawnIndex].nrespawn=nrespawn
if self.f10menu then
local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex)
self.Menu[self.SubMenuName].groups[self.SpawnIndex]=MENU_MISSION:New(name,self.Menu[self.SubMenuName].groups)
self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"]=MENU_MISSION:New("Set ROE",self.Menu[self.SubMenuName].groups[self.SpawnIndex])
MENU_MISSION_COMMAND:New("Weapons hold",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,group,RAT.ROE.weaponhold)
MENU_MISSION_COMMAND:New("Weapons free",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,group,RAT.ROE.weaponfree)
MENU_MISSION_COMMAND:New("Return fire",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,group,RAT.ROE.returnfire)
self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"]=MENU_MISSION:New("Set ROT",self.Menu[self.SubMenuName].groups[self.SpawnIndex])
MENU_MISSION_COMMAND:New("No reaction",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,group,RAT.ROT.noreaction)
MENU_MISSION_COMMAND:New("Passive defense",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,group,RAT.ROT.passive)
MENU_MISSION_COMMAND:New("Evade on fire",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,group,RAT.ROT.evade)
MENU_MISSION_COMMAND:New("Despawn group",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self._Despawn,self,group)
MENU_MISSION_COMMAND:New("Place markers",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self._PlaceMarkers,self,waypoints,self.SpawnIndex)
MENU_MISSION_COMMAND:New("Status report",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self.Status,self,true,self.SpawnIndex)
end
return self.SpawnIndex
end
function RAT:ClearForLanding(name)
trigger.action.setUserFlag(name,1)
local flagvalue=trigger.misc.getUserFlag(name)
self:T(RAT.id.."ATC: User flag value (landing) for "..name.." set to "..flagvalue)
end
function RAT:_Respawn(index,lastpos,delay)
local departure=self.ratcraft[index].departure
local destination=self.ratcraft[index].destination
local takeoff=self.ratcraft[index].takeoff
local landing=self.ratcraft[index].landing
local livery=self.ratcraft[index].livery
local lastwp=self.ratcraft[index].waypoints[#self.ratcraft[index].waypoints]
local _departure=nil
local _destination=nil
local _takeoff=nil
local _landing=nil
local _livery=nil
local _lastwp=nil
local _lastpos=nil
if self.continuejourney then
_departure=destination:GetName()
_livery=livery
if landing==RAT.wp.landing and lastpos and not(self.respawn_at_landing or self.respawn_after_takeoff)then
if destination:GetCategory()==4 then
_lastpos=lastpos
end
end
if self.destinationzone then
_takeoff=RAT.wp.air
_landing=RAT.wp.air
elseif self.returnzone then
_takeoff=self.takeoff
if self.takeoff==RAT.wp.air then
_landing=RAT.wp.air
else
_landing=RAT.wp.landing
end
_departure=departure:GetName()
else
_takeoff=self.takeoff
_landing=self.landing
end
elseif self.commute then
if self.starshape==true then
if destination:GetName()==self.homebase then
_departure=self.homebase
_destination=nil
else
_departure=destination:GetName()
_destination=self.homebase
end
else
_departure=destination:GetName()
_destination=departure:GetName()
end
_livery=livery
if landing==RAT.wp.landing and lastpos and not(self.respawn_at_landing or self.respawn_after_takeoff)then
if destination:GetCategory()==4 then
_lastpos=lastpos
end
end
if self.destinationzone then
if self.takeoff==RAT.wp.air then
_takeoff=RAT.wp.air
_landing=RAT.wp.air
else
if takeoff==RAT.wp.air then
_takeoff=self.takeoff
_landing=RAT.wp.air
else
_takeoff=RAT.wp.air
_landing=RAT.wp.landing
end
end
elseif self.returnzone then
_departure=departure:GetName()
_destination=destination:GetName()
_takeoff=self.takeoff
_landing=self.landing
end
end
if _takeoff==RAT.wp.air and(self.continuejourney or self.commute)then
_lastwp=lastwp
end
self:T2({departure=_departure,destination=_destination,takeoff=_takeoff,landing=_landing,livery=_livery,lastwp=_lastwp})
local respawndelay
if delay then
respawndelay=delay
elseif self.respawn_delay then
respawndelay=self.respawn_delay+3
else
respawndelay=3
end
local arg={}
arg.self=self
arg.departure=_departure
arg.destination=_destination
arg.takeoff=_takeoff
arg.landing=_landing
arg.livery=_livery
arg.lastwp=_lastwp
arg.lastpos=_lastpos
self:T(RAT.id..string.format("%s delayed respawn in %.1f seconds.",self.alias,respawndelay))
SCHEDULER:New(nil,self._SpawnWithRouteTimer,{arg},respawndelay)
end
function RAT._SpawnWithRouteTimer(arg)
RAT._SpawnWithRoute(arg.self,arg.departure,arg.destination,arg.takeoff,arg.landing,arg.livery,arg.lastwp,arg.lastpos)
end
function RAT:_SetRoute(takeoff,landing,_departure,_destination,_waypoint)
local VxCruiseMax
if self.Vcruisemax then
VxCruiseMax=math.min(self.Vcruisemax,self.aircraft.Vmax)
else
VxCruiseMax=math.min(self.aircraft.Vmax*0.90,250)
end
local VxCruiseMin=math.min(VxCruiseMax*0.70,166)
local VxCruise=UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin,(VxCruiseMax-VxCruiseMax)/4,VxCruiseMin,VxCruiseMax)
local VxClimb=math.min(self.aircraft.Vmax*0.90,200)
local VxDescent=math.min(self.aircraft.Vmax*0.60,140)
local VxHolding=VxDescent*0.9
local VxFinal=VxHolding*0.9
local VyClimb=math.min(self.Vclimb*RAT.unit.ft2meter/60,self.aircraft.Vymax)
local AlphaClimb=math.asin(VyClimb/VxClimb)
local AlphaDescent=math.rad(self.AlphaDescent)
local FLcruise_expect=self.FLcruise
local departure=nil
if _departure then
if self:_AirportExists(_departure)then
departure=AIRBASE:FindByName(_departure)
if takeoff==RAT.wp.air then
departure=departure:GetZone()
end
elseif self:_ZoneExists(_departure)then
departure=ZONE:New(_departure)
else
local text=string.format("ERROR! Specified departure airport %s does not exist for %s.",_departure,self.alias)
self:E(RAT.id..text)
end
else
departure=self:_PickDeparture(takeoff)
if self.commute and self.starshape==true and self.homebase==nil then
self.homebase=departure:GetName()
end
end
if not departure then
local text=string.format("ERROR! No valid departure airport could be found for %s.",self.alias)
self:E(RAT.id..text)
return nil
end
local Pdeparture
if takeoff==RAT.wp.air then
if _waypoint then
Pdeparture=COORDINATE:New(_waypoint.x,_waypoint.alt,_waypoint.y)
else
local vec2=departure:GetRandomVec2()
Pdeparture=COORDINATE:NewFromVec2(vec2)
end
else
Pdeparture=departure:GetCoordinate()
end
local H_departure
if takeoff==RAT.wp.air then
local Hmin
if self.category==RAT.cat.plane then
Hmin=1000
else
Hmin=50
end
H_departure=self:_Randomize(FLcruise_expect*0.7,0.3,Pdeparture.y+Hmin,FLcruise_expect)
if self.FLminuser then
H_departure=math.max(H_departure,self.FLminuser)
end
if _waypoint then
H_departure=_waypoint.alt
end
else
H_departure=Pdeparture.y
end
local mindist=self.mindist
if self.FLminuser then
local hclimb=self.FLminuser-H_departure
local hdescent=self.FLminuser-H_departure
local Dclimb,Ddescent,Dtot=self:_MinDistance(AlphaClimb,AlphaDescent,hclimb,hdescent)
if takeoff==RAT.wp.air and landing==RAT.wpair then
mindist=0
elseif takeoff==RAT.wp.air then
mindist=Ddescent
elseif landing==RAT.wp.air then
mindist=Dclimb
else
mindist=Dtot
end
mindist=math.max(self.mindist,mindist)
local text=string.format("Adjusting min distance to %d km (for given min FL%03d)",mindist/1000,self.FLminuser/RAT.unit.FL2m)
self:T(RAT.id..text)
end
local destination=nil
if _destination then
if self:_AirportExists(_destination)then
destination=AIRBASE:FindByName(_destination)
if landing==RAT.wp.air or self.returnzone then
destination=destination:GetZone()
end
elseif self:_ZoneExists(_destination)then
destination=ZONE:New(_destination)
else
local text=string.format("ERROR: Specified destination airport/zone %s does not exist for %s!",_destination,self.alias)
self:E(RAT.id.."ERROR: "..text)
end
else
local random=self.random_destination
if self.continuejourney and _departure and#self.destination_ports<3 then
random=true
end
local mylanding=landing
local acrange=self.aircraft.Reff
if self.returnzone then
mylanding=RAT.wp.air
acrange=self.aircraft.Reff/2
end
destination=self:_PickDestination(departure,Pdeparture,mindist,math.min(acrange,self.maxdist),random,mylanding)
end
if not destination then
local text=string.format("No valid destination airport could be found for %s!",self.alias)
MESSAGE:New(text,60):ToAll()
self:E(RAT.id.."ERROR: "..text)
return nil
end
if destination:GetName()==departure:GetName()then
local text=string.format("%s: Destination and departure are identical. Airport/zone %s.",self.alias,destination:GetName())
MESSAGE:New(text,30):ToAll()
self:E(RAT.id.."ERROR: "..text)
end
local Preturn
local destination_returnzone
if self.returnzone then
local vec2=destination:GetRandomVec2()
Preturn=COORDINATE:NewFromVec2(vec2)
destination_returnzone=destination
destination=departure
end
local Pdestination
if landing==RAT.wp.air then
local vec2=destination:GetRandomVec2()
Pdestination=COORDINATE:NewFromVec2(vec2)
else
Pdestination=destination:GetCoordinate()
end
local H_destination=Pdestination.y
local Rhmin=8000
local Rhmax=20000
if self.category==RAT.cat.heli then
Rhmin=500
Rhmax=1000
end
local Vholding=Pdestination:GetRandomVec2InRadius(Rhmax,Rhmin)
local Pholding=COORDINATE:NewFromVec2(Vholding)
local H_holding=Pholding.y
local h_holding
if self.category==RAT.cat.plane then
h_holding=1200
else
h_holding=150
end
h_holding=self:_Randomize(h_holding,0.2)
local Hh_holding=H_holding+h_holding
if landing==RAT.wp.air then
Hh_holding=H_departure
end
local d_holding=Pholding:Get2DDistance(Pdestination)
local heading
local d_total
if self.returnzone then
heading=self:_Course(Pdeparture,Preturn)
d_total=Pdeparture:Get2DDistance(Preturn)+Preturn:Get2DDistance(Pholding)
else
heading=self:_Course(Pdeparture,Pholding)
d_total=Pdeparture:Get2DDistance(Pholding)
end
if takeoff==RAT.wp.air then
local H_departure_max
if landing==RAT.wp.air then
H_departure_max=H_departure
else
H_departure_max=d_total*math.tan(AlphaDescent)+Hh_holding
end
H_departure=math.min(H_departure,H_departure_max)
end
local deltaH=math.abs(H_departure-Hh_holding)
local phi=math.atan(deltaH/d_total)
local phi_climb
local phi_descent
if(H_departure>Hh_holding)then
phi_climb=AlphaClimb+phi
phi_descent=AlphaDescent-phi
else
phi_climb=AlphaClimb-phi
phi_descent=AlphaDescent+phi
end
local D_total
if self.returnzone then
D_total=math.sqrt(deltaH*deltaH+d_total/2*d_total/2)
else
D_total=math.sqrt(deltaH*deltaH+d_total*d_total)
end
local gamma=math.rad(180)-phi_climb-phi_descent
local a=D_total*math.sin(phi_climb)/math.sin(gamma)
local b=D_total*math.sin(phi_descent)/math.sin(gamma)
local hphi_max=b*math.sin(phi_climb)
local hphi_max2=a*math.sin(phi_descent)
local h_max1=b*math.sin(AlphaClimb)
local h_max2=a*math.sin(AlphaDescent)
local h_max
if(H_departure>Hh_holding)then
h_max=math.min(h_max1,h_max2)
else
h_max=math.max(h_max1,h_max2)
end
local FLmax=h_max+H_departure
local FLmin=math.max(H_departure,Hh_holding)
if self.category==RAT.cat.heli then
FLmin=math.max(H_departure,H_destination)+50
FLmax=math.max(H_departure,H_destination)+1000
end
FLmax=math.min(FLmax,self.aircraft.ceiling)
if self.FLminuser then
FLmin=math.max(self.FLminuser,FLmin)
end
if self.FLmaxuser then
FLmax=math.min(self.FLmaxuser,FLmax)
end
if FLmin>FLmax then
FLmin=FLmax
end
if FLcruise_expect<FLmin then
FLcruise_expect=FLmin
end
if FLcruise_expect>FLmax then
FLcruise_expect=FLmax
end
local FLcruise=UTILS.RandomGaussian(FLcruise_expect,math.abs(FLmax-FLmin)/4,FLmin,FLmax)
if self.FLuser then
FLcruise=self.FLuser
FLcruise=math.max(FLcruise,FLmin)
FLcruise=math.min(FLcruise,FLmax)
end
local h_climb=FLcruise-H_departure
local h_descent=FLcruise-Hh_holding
local d_climb=h_climb/math.tan(AlphaClimb)
local d_descent=h_descent/math.tan(AlphaDescent)
local d_cruise=d_total-d_climb-d_descent
local text=string.format("\n******************************************************\n")
text=text..string.format("Template      =  %s\n",self.SpawnTemplatePrefix)
text=text..string.format("Alias         =  %s\n",self.alias)
text=text..string.format("Group name    =  %s\n\n",self:_AnticipatedGroupName())
text=text..string.format("Speeds:\n")
text=text..string.format("VxCruiseMin   = %6.1f m/s = %5.1f km/h\n",VxCruiseMin,VxCruiseMin*3.6)
text=text..string.format("VxCruiseMax   = %6.1f m/s = %5.1f km/h\n",VxCruiseMax,VxCruiseMax*3.6)
text=text..string.format("VxCruise      = %6.1f m/s = %5.1f km/h\n",VxCruise,VxCruise*3.6)
text=text..string.format("VxClimb       = %6.1f m/s = %5.1f km/h\n",VxClimb,VxClimb*3.6)
text=text..string.format("VxDescent     = %6.1f m/s = %5.1f km/h\n",VxDescent,VxDescent*3.6)
text=text..string.format("VxHolding     = %6.1f m/s = %5.1f km/h\n",VxHolding,VxHolding*3.6)
text=text..string.format("VxFinal       = %6.1f m/s = %5.1f km/h\n",VxFinal,VxFinal*3.6)
text=text..string.format("VyClimb       = %6.1f m/s\n",VyClimb)
text=text..string.format("\nDistances:\n")
text=text..string.format("d_climb       = %6.1f km\n",d_climb/1000)
text=text..string.format("d_cruise      = %6.1f km\n",d_cruise/1000)
text=text..string.format("d_descent     = %6.1f km\n",d_descent/1000)
text=text..string.format("d_holding     = %6.1f km\n",d_holding/1000)
text=text..string.format("d_total       = %6.1f km\n",d_total/1000)
text=text..string.format("\nHeights:\n")
text=text..string.format("H_departure   = %6.1f m ASL\n",H_departure)
text=text..string.format("H_destination = %6.1f m ASL\n",H_destination)
text=text..string.format("H_holding     = %6.1f m ASL\n",H_holding)
text=text..string.format("h_climb       = %6.1f m\n",h_climb)
text=text..string.format("h_descent     = %6.1f m\n",h_descent)
text=text..string.format("h_holding     = %6.1f m\n",h_holding)
text=text..string.format("delta H       = %6.1f m\n",deltaH)
text=text..string.format("FLmin         = %6.1f m ASL = FL%03d\n",FLmin,FLmin/RAT.unit.FL2m)
text=text..string.format("FLcruise      = %6.1f m ASL = FL%03d\n",FLcruise,FLcruise/RAT.unit.FL2m)
text=text..string.format("FLmax         = %6.1f m ASL = FL%03d\n",FLmax,FLmax/RAT.unit.FL2m)
text=text..string.format("\nAngles:\n")
text=text..string.format("Alpha climb   = %6.2f Deg\n",math.deg(AlphaClimb))
text=text..string.format("Alpha descent = %6.2f Deg\n",math.deg(AlphaDescent))
text=text..string.format("Phi (slope)   = %6.2f Deg\n",math.deg(phi))
text=text..string.format("Phi climb     = %6.2f Deg\n",math.deg(phi_climb))
text=text..string.format("Phi descent   = %6.2f Deg\n",math.deg(phi_descent))
if self.Debug then
local h_climb_max=FLmax-H_departure
local h_descent_max=FLmax-Hh_holding
local d_climb_max=h_climb_max/math.tan(AlphaClimb)
local d_descent_max=h_descent_max/math.tan(AlphaDescent)
local d_cruise_max=d_total-d_climb_max-d_descent_max
text=text..string.format("Heading       = %6.1f Deg\n",heading)
text=text..string.format("\nSSA triangle:\n")
text=text..string.format("D_total       = %6.1f km\n",D_total/1000)
text=text..string.format("gamma         = %6.1f Deg\n",math.deg(gamma))
text=text..string.format("a             = %6.1f m\n",a)
text=text..string.format("b             = %6.1f m\n",b)
text=text..string.format("hphi_max      = %6.1f m\n",hphi_max)
text=text..string.format("hphi_max2     = %6.1f m\n",hphi_max2)
text=text..string.format("h_max1        = %6.1f m\n",h_max1)
text=text..string.format("h_max2        = %6.1f m\n",h_max2)
text=text..string.format("h_max         = %6.1f m\n",h_max)
text=text..string.format("\nMax heights and distances:\n")
text=text..string.format("d_climb_max   = %6.1f km\n",d_climb_max/1000)
text=text..string.format("d_cruise_max  = %6.1f km\n",d_cruise_max/1000)
text=text..string.format("d_descent_max = %6.1f km\n",d_descent_max/1000)
text=text..string.format("h_climb_max   = %6.1f m\n",h_climb_max)
text=text..string.format("h_descent_max = %6.1f m\n",h_descent_max)
end
text=text..string.format("******************************************************\n")
self:T2(RAT.id..text)
if d_cruise<0 then
d_cruise=100
end
local wp={}
local c={}
local wpholding=nil
local wpfinal=nil
c[#c+1]=Pdeparture
wp[#wp+1]=self:_Waypoint(#wp+1,"Departure",takeoff,c[#wp+1],VxClimb,H_departure,departure)
self.waypointdescriptions[#wp]="Departure"
self.waypointstatus[#wp]=RAT.status.Departure
if takeoff==RAT.wp.air then
if d_climb<5000 or d_cruise<5000 then
d_cruise=d_cruise+d_climb
else
c[#c+1]=c[#c]:Translate(d_climb,heading)
wp[#wp+1]=self:_Waypoint(#wp+1,"Begin of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise)
self.waypointdescriptions[#wp]="Begin of Cruise"
self.waypointstatus[#wp]=RAT.status.Cruise
end
else
c[#c+1]=c[#c]:Translate(d_climb/2,heading)
c[#c+1]=c[#c]:Translate(d_climb/2,heading)
wp[#wp+1]=self:_Waypoint(#wp+1,"Climb",RAT.wp.climb,c[#wp+1],VxClimb,H_departure+(FLcruise-H_departure)/2)
self.waypointdescriptions[#wp]="Climb"
self.waypointstatus[#wp]=RAT.status.Climb
wp[#wp+1]=self:_Waypoint(#wp+1,"Begin of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise)
self.waypointdescriptions[#wp]="Begin of Cruise"
self.waypointstatus[#wp]=RAT.status.Cruise
end
if self.returnzone then
c[#c+1]=Preturn
wp[#wp+1]=self:_Waypoint(#wp+1,"Return Zone",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise)
self.waypointdescriptions[#wp]="Return Zone"
self.waypointstatus[#wp]=RAT.status.Uturn
end
if landing==RAT.wp.air then
c[#c+1]=Pdestination
wp[#wp+1]=self:_Waypoint(#wp+1,"Final Destination",RAT.wp.finalwp,c[#wp+1],VxCruise,FLcruise)
self.waypointdescriptions[#wp]="Final Destination"
self.waypointstatus[#wp]=RAT.status.Destination
elseif self.returnzone then
c[#c+1]=c[#c]:Translate(d_cruise/2,heading-180)
wp[#wp+1]=self:_Waypoint(#wp+1,"End of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise)
self.waypointdescriptions[#wp]="End of Cruise"
self.waypointstatus[#wp]=RAT.status.Descent
else
c[#c+1]=c[#c]:Translate(d_cruise,heading)
wp[#wp+1]=self:_Waypoint(#wp+1,"End of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise)
self.waypointdescriptions[#wp]="End of Cruise"
self.waypointstatus[#wp]=RAT.status.Descent
end
if landing==RAT.wp.landing then
if self.returnzone then
c[#c+1]=c[#c]:Translate(d_descent/2,heading-180)
wp[#wp+1]=self:_Waypoint(#wp+1,"Descent",RAT.wp.descent,c[#wp+1],VxDescent,FLcruise-(FLcruise-(h_holding+H_holding))/2)
self.waypointdescriptions[#wp]="Descent"
self.waypointstatus[#wp]=RAT.status.DescentHolding
else
c[#c+1]=c[#c]:Translate(d_descent/2,heading)
wp[#wp+1]=self:_Waypoint(#wp+1,"Descent",RAT.wp.descent,c[#wp+1],VxDescent,FLcruise-(FLcruise-(h_holding+H_holding))/2)
self.waypointdescriptions[#wp]="Descent"
self.waypointstatus[#wp]=RAT.status.DescentHolding
end
end
if landing==RAT.wp.landing then
c[#c+1]=Pholding
wp[#wp+1]=self:_Waypoint(#wp+1,"Holding Point",RAT.wp.holding,c[#wp+1],VxHolding,H_holding+h_holding)
self.waypointdescriptions[#wp]="Holding Point"
self.waypointstatus[#wp]=RAT.status.Holding
wpholding=#wp
c[#c+1]=Pdestination
wp[#wp+1]=self:_Waypoint(#wp+1,"Final Destination",landing,c[#wp+1],VxFinal,H_destination,destination)
self.waypointdescriptions[#wp]="Final Destination"
self.waypointstatus[#wp]=RAT.status.Destination
end
wpfinal=#wp
local waypoints={}
for _,p in ipairs(wp)do
table.insert(waypoints,p)
end
self:_Routeinfo(waypoints,"Waypoint info in set_route:")
if self.returnzone then
return departure,destination_returnzone,waypoints,wpholding,wpfinal
else
return departure,destination,waypoints,wpholding,wpfinal
end
end
function RAT:_PickDeparture(takeoff)
local departures={}
if self.random_departure then
for _,_airport in pairs(self.airports)do
local airport=_airport
local name=airport:GetName()
if not self:_Excluded(name)then
if takeoff==RAT.wp.air then
table.insert(departures,airport:GetZone())
else
local nspots=1
if self.termtype~=nil then
nspots=airport:GetParkingSpotsNumber(self.termtype)
end
if nspots>0 then
table.insert(departures,airport)
end
end
end
end
else
for _,name in pairs(self.departure_ports)do
local dep=nil
if self:_AirportExists(name)then
if takeoff==RAT.wp.air then
dep=AIRBASE:FindByName(name):GetZone()
else
dep=AIRBASE:FindByName(name)
if self.termtype~=nil and dep~=nil then
local _dep=dep
local nspots=_dep:GetParkingSpotsNumber(self.termtype)
if nspots==0 then
dep=nil
end
end
end
elseif self:_ZoneExists(name)then
if takeoff==RAT.wp.air then
dep=ZONE:New(name)
else
self:E(RAT.id..string.format("ERROR! Takeoff is not in air. Cannot use %s as departure.",name))
end
else
self:E(RAT.id..string.format("ERROR: No airport or zone found with name %s.",name))
end
if dep then
table.insert(departures,dep)
end
end
end
self:T(RAT.id..string.format("Number of possible departures for %s= %d",self.alias,#departures))
local departure=departures[math.random(#departures)]
local text
if departure and departure:GetName()then
if takeoff==RAT.wp.air then
text=string.format("%s: Chosen departure zone: %s",self.alias,departure:GetName())
else
text=string.format("%s: Chosen departure airport: %s (ID %d)",self.alias,departure:GetName(),departure:GetID())
end
self:T(RAT.id..text)
else
self:E(RAT.id..string.format("ERROR! No departure airport or zone found for %s.",self.alias))
departure=nil
end
return departure
end
function RAT:_PickDestination(departure,q,minrange,maxrange,random,landing)
minrange=minrange or self.mindist
maxrange=maxrange or self.maxdist
local destinations={}
if random then
for _,_airport in pairs(self.airports)do
local airport=_airport
local name=airport:GetName()
if self:_IsFriendly(name)and not self:_Excluded(name)and name~=departure:GetName()then
local distance=q:Get2DDistance(airport:GetCoordinate())
if distance>=minrange and distance<=maxrange then
if landing==RAT.wp.air then
table.insert(destinations,airport:GetZone())
else
local nspot=1
if self.termtype then
nspot=airport:GetParkingSpotsNumber(self.termtype)
end
if nspot>0 then
table.insert(destinations,airport)
end
end
end
end
end
else
for _,name in pairs(self.destination_ports)do
if name~=departure:GetName()then
local dest=nil
if self:_AirportExists(name)then
if landing==RAT.wp.air then
dest=AIRBASE:FindByName(name):GetZone()
else
dest=AIRBASE:FindByName(name)
local nspot=1
if self.termtype then
nspot=dest:GetParkingSpotsNumber(self.termtype)
end
if nspot==0 then
dest=nil
end
end
elseif self:_ZoneExists(name)then
if landing==RAT.wp.air then
dest=ZONE:New(name)
else
self:E(RAT.id..string.format("ERROR! Landing is not in air. Cannot use zone %s as destination!",name))
end
else
self:E(RAT.id..string.format("ERROR! No airport or zone found with name %s",name))
end
if dest then
local distance=q:Get2DDistance(dest:GetCoordinate())
if distance>=minrange and distance<=maxrange then
table.insert(destinations,dest)
else
local text=string.format("Destination %s is ouside range. Distance = %5.1f km, min = %5.1f km, max = %5.1f km.",name,distance,minrange,maxrange)
self:T(RAT.id..text)
end
end
end
end
end
self:T(RAT.id..string.format("Number of possible destinations = %s.",#destinations))
if#destinations>0 then
local function compare(a,b)
local qa=q:Get2DDistance(a:GetCoordinate())
local qb=q:Get2DDistance(b:GetCoordinate())
return qa<qb
end
table.sort(destinations,compare)
else
destinations=nil
end
local destination
if destinations and#destinations>0 then
destination=destinations[math.random(#destinations)]
local text
if landing==RAT.wp.air then
text=string.format("%s: Chosen destination zone: %s.",self.alias,destination:GetName())
else
text=string.format("%s Chosen destination airport: %s (ID %d).",self.alias,destination:GetName(),destination:GetID())
end
self:T(RAT.id..text)
else
self:E(RAT.id.."ERROR! No destination airport or zone found.")
destination=nil
end
return destination
end
function RAT:_GetAirportsInZone(zone)
local airports={}
for _,airport in pairs(self.airports)do
local name=airport:GetName()
local coord=airport:GetCoordinate()
if zone:IsPointVec3InZone(coord)then
table.insert(airports,name)
end
end
return airports
end
function RAT:_Excluded(port)
for _,name in pairs(self.excluded_ports)do
if name==port then
return true
end
end
return false
end
function RAT:_IsFriendly(port)
for _,airport in pairs(self.airports)do
local name=airport:GetName()
if name==port then
return true
end
end
return false
end
function RAT:_GetAirportsOfMap()
local _coalition
for i=0,2 do
if i==0 then
_coalition=coalition.side.NEUTRAL
elseif i==1 then
_coalition=coalition.side.RED
elseif i==2 then
_coalition=coalition.side.BLUE
end
local ab=coalition.getAirbases(i)
for _,airbase in pairs(ab)do
local _id=airbase:getID()
local _p=airbase:getPosition().p
local _name=airbase:getName()
local _myab=AIRBASE:FindByName(_name)
if _myab then
table.insert(self.airports_map,_myab)
local text="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
self:T(RAT.id..text)
else
self:E(RAT.id..string.format("WARNING: Airbase %s does not exsist as MOOSE object!",tostring(_name)))
end
end
end
end
function RAT:_GetAirportsOfCoalition()
for _,coalition in pairs(self.ctable)do
for _,_airport in pairs(self.airports_map)do
local airport=_airport
local category=airport:GetAirbaseCategory()
if airport:GetCoalition()==coalition then
local condition1=self.category==RAT.cat.plane and category==Airbase.Category.HELIPAD
local condition2=self.category==RAT.cat.plane and category==Airbase.Category.SHIP
if not(condition1 or condition2)then
table.insert(self.airports,airport)
end
end
end
end
if#self.airports==0 then
local text=string.format("No possible departure/destination airports found for RAT %s.",tostring(self.alias))
MESSAGE:New(text,10):ToAll()
self:E(RAT.id..text)
end
end
function RAT:Status(message,forID)
if message==nil then
message=false
end
if forID==nil then
forID=false
end
local Tnow=timer.getTime()
local nalive=0
for spawnindex,ratcraft in ipairs(self.ratcraft)do
local group=ratcraft.group
if group and group:IsAlive()then
nalive=nalive+1
local prefix=self:_GetPrefixFromGroup(group)
local life=self:_GetLife(group)
local fuel=group:GetFuel()*100.0
local airborne=group:InAir()
local coords=group:GetCoordinate()
local alt=coords.y or 1000
local departure=ratcraft.departure:GetName()
local destination=ratcraft.destination:GetName()
local type=self.aircraft.type
local status=ratcraft.status
local active=ratcraft.active
local Nunits=ratcraft.nunits
local N0units=group:GetInitialSize()
local Tg=0
local Dg=0
local dTlast=0
local stationary=false
if airborne then
ratcraft["Tground"]=nil
ratcraft["Pground"]=nil
ratcraft["Uground"]=nil
ratcraft["Tlastcheck"]=nil
else
if ratcraft["Tground"]then
Tg=Tnow-ratcraft["Tground"]
Dg=coords:Get2DDistance(ratcraft["Pground"])
dTlast=Tnow-ratcraft["Tlastcheck"]
if dTlast>self.Tinactive then
for _,_unit in pairs(group:GetUnits())do
if _unit and _unit:IsAlive()then
local unitname=_unit:GetName()
local unitcoord=_unit:GetCoordinate()
local Ug=unitcoord:Get2DDistance(ratcraft.Uground[unitname])
self:T2(RAT.id..string.format("Unit %s travelled distance on ground %.1f m since %d seconds.",unitname,Ug,dTlast))
if Ug<50 and active and status~=RAT.status.EventBirth then
stationary=true
end
ratcraft["Uground"][unitname]=unitcoord
end
end
ratcraft["Tlastcheck"]=Tnow
ratcraft["Pground"]=coords
end
else
ratcraft["Tground"]=Tnow
ratcraft["Tlastcheck"]=Tnow
ratcraft["Pground"]=coords
ratcraft["Uground"]={}
for _,_unit in pairs(group:GetUnits())do
local unitname=_unit:GetName()
ratcraft.Uground[unitname]=_unit:GetCoordinate()
end
end
end
local Pn=coords
local Dtravel=Pn:Get2DDistance(ratcraft["Pnow"])
ratcraft["Pnow"]=Pn
ratcraft["Distance"]=ratcraft["Distance"]+Dtravel
local Ddestination=Pn:Get2DDistance(ratcraft.destination:GetCoordinate())
if(forID and spawnindex==forID)or(not forID)then
local text=string.format("ID %i of flight %s",spawnindex,prefix)
if N0units>1 then
text=text..string.format(" (%d/%d)\n",Nunits,N0units)
else
text=text.."\n"
end
if self.commute then
text=text..string.format("%s commuting between %s and %s\n",type,departure,destination)
elseif self.continuejourney then
text=text..string.format("%s travelling from %s to %s (and continueing form there)\n",type,departure,destination)
else
text=text..string.format("%s travelling from %s to %s\n",type,departure,destination)
end
text=text..string.format("Status: %s",status)
if airborne then
text=text.." [airborne]\n"
else
text=text.." [on ground]\n"
end
text=text..string.format("Fuel = %3.0f %%\n",fuel)
text=text..string.format("Life  = %3.0f %%\n",life)
text=text..string.format("FL%03d = %i m ASL\n",alt/RAT.unit.FL2m,alt)
text=text..string.format("Distance travelled        = %6.1f km\n",ratcraft["Distance"]/1000)
text=text..string.format("Distance to destination = %6.1f km",Ddestination/1000)
if not airborne then
text=text..string.format("\nTime on ground  = %6.0f seconds\n",Tg)
text=text..string.format("Position change = %8.1f m since %3.0f seconds.",Dg,dTlast)
end
self:T(RAT.id..text)
if message then
MESSAGE:New(text,20):ToAll()
end
end
if not airborne then
if stationary then
local text=string.format("Group %s is despawned after being %d seconds inaktive on ground.",self.alias,dTlast)
self:T(RAT.id..text)
self:_Despawn(group)
end
if life<10 and Dtravel<100 then
local text=string.format("Damaged group %s is despawned. Life = %3.0f",self.alias,life)
self:T(RAT.id..text)
self:_Despawn(group)
end
end
if ratcraft.despawnme then
local text=string.format("Flight %s will be despawned NOW!",self.alias)
self:T(RAT.id..text)
if(not self.norespawn)and(not self.respawn_after_takeoff)then
local idx=self:GetSpawnIndexFromGroup(group)
local coord=group:GetCoordinate()
self:_Respawn(idx,coord,0)
end
if self.despawnair then
self:_Despawn(group,0)
end
end
else
local text=string.format("Group does not exist in loop ratcraft status.")
self:T2(RAT.id..text)
end
end
local text=string.format("Alive groups of %s: %d, nalive=%d/%d",self.alias,self.alive,nalive,self.ngroups)
self:T(RAT.id..text)
MESSAGE:New(text,20):ToAllIf(message and not forID)
end
function RAT:_GetLife(group)
local life=0.0
if group and group:IsAlive()then
local unit=group:GetUnit(1)
if unit then
life=unit:GetLife()/unit:GetLife0()*100
else
self:T2(RAT.id.."ERROR! Unit does not exist in RAT_Getlife(). Returning zero.")
end
else
self:T2(RAT.id.."ERROR! Group does not exist in RAT_Getlife(). Returning zero.")
end
return life
end
function RAT:_SetStatus(group,status)
if group and group:IsAlive()then
local index=self:GetSpawnIndexFromGroup(group)
if self.ratcraft[index]then
self.ratcraft[index].status=status
local no1=status==RAT.status.Departure
local no2=status==RAT.status.EventBirthAir
local no3=status==RAT.status.Holding
local text=string.format("Flight %s: %s.",group:GetName(),status)
self:T(RAT.id..text)
if not(no1 or no2 or no3)then
MESSAGE:New(text,10):ToAllIf(self.reportstatus)
end
end
end
end
function RAT:GetStatus(group)
if group and group:IsAlive()then
local index=self:GetSpawnIndexFromGroup(group)
if self.ratcraft[index]then
return self.ratcraft[index].status
end
end
return"nonexistant"
end
function RAT:_OnBirth(EventData)
self:F3(EventData)
self:T3(RAT.id.."Captured event birth!")
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
if EventPrefix==self.alias then
local text="Event: Group "..SpawnGroup:GetName().." was born."
self:T(RAT.id..text)
local status="unknown in birth"
if SpawnGroup:InAir()then
status=RAT.status.EventBirthAir
elseif self.uncontrolled then
status=RAT.status.Uncontrolled
else
status=RAT.status.EventBirth
end
self:_SetStatus(SpawnGroup,status)
local i=self:GetSpawnIndexFromGroup(SpawnGroup)
local _departure=self.ratcraft[i].departure:GetName()
local _destination=self.ratcraft[i].destination:GetName()
local _nrespawn=self.ratcraft[i].nrespawn
local _takeoff=self.ratcraft[i].takeoff
local _landing=self.ratcraft[i].landing
local _livery=self.ratcraft[i].livery
local _airbase=AIRBASE:FindByName(_departure)
local onrunway=false
if _airbase then
if self.checkonrunway and _takeoff~=RAT.wp.runway and _takeoff~=RAT.wp.air then
onrunway=_airbase:CheckOnRunWay(SpawnGroup,self.onrunwayradius,false)
end
end
if onrunway then
local text=string.format("ERROR: RAT group of %s was spawned on runway. Group #%d will be despawned immediately!",self.alias,i)
MESSAGE:New(text,30):ToAllIf(self.Debug)
self:E(RAT.id..text)
if self.Debug then
SpawnGroup:FlareRed()
end
self:_Despawn(SpawnGroup)
if(self.Ndeparture_Airports>=2 or self.random_departure)and _nrespawn<self.onrunwaymaxretry then
_nrespawn=_nrespawn+1
text=string.format("Try spawning new aircraft of group %s at another location. Attempt %d of max %d.",self.alias,_nrespawn,self.onrunwaymaxretry)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:T(RAT.id..text)
self:_SpawnWithRoute(nil,nil,nil,nil,nil,nil,nil,_nrespawn)
else
if self.respawn_inair and not self.uncontrolled then
text=string.format("Spawning new aircraft of group %s in air since no parking slot is available at %s.",self.alias,_departure)
MESSAGE:New(text,10):ToAll()
self:T(RAT.id..text)
self:_SpawnWithRoute(_departure,_destination,RAT.wp.air,_landing,_livery)
end
end
end
local ontop=false
if self.checkontop and(_airbase and _airbase:GetAirbaseCategory()==Airbase.Category.AIRDROME)then
ontop=self:_CheckOnTop(SpawnGroup,self.ontopradius)
end
if ontop then
local text=string.format("ERROR: Group of %s was spawned on top of another unit. Group #%d will be despawned immediately!",self.alias,i)
MESSAGE:New(text,30):ToAllIf(self.Debug)
self:T(RAT.id..text)
if self.Debug then
SpawnGroup:FlareYellow()
end
self:_Despawn(SpawnGroup)
end
end
end
else
self:T2(RAT.id.."ERROR: Group does not exist in RAT:_OnBirth().")
end
end
function RAT:_OnEngineStartup(EventData)
self:F3(EventData)
self:T3(RAT.id.."Captured event EngineStartup!")
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
if EventPrefix==self.alias then
local text="Event: Group "..SpawnGroup:GetName().." started engines."
self:T(RAT.id..text)
local status
if SpawnGroup:InAir()then
status=RAT.status.EventEngineStartAir
else
status=RAT.status.EventEngineStart
end
self:_SetStatus(SpawnGroup,status)
end
end
else
self:T2(RAT.id.."ERROR: Group does not exist in RAT:_EngineStartup().")
end
end
function RAT:_OnTakeoff(EventData)
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
if EventPrefix==self.alias then
local text="Event: Group "..SpawnGroup:GetName().." is airborne."
self:T(RAT.id..text)
local status=RAT.status.EventTakeoff
self:_SetStatus(SpawnGroup,status)
if self.respawn_after_takeoff then
text="Event: Group "..SpawnGroup:GetName().." will be respawned after takeoff."
self:T(RAT.id..text)
self:_SpawnWithRoute(nil,nil,nil,nil,nil,nil,nil,nil)
end
end
end
else
self:T2(RAT.id.."ERROR: Group does not exist in RAT:_OnTakeoff().")
end
end
function RAT:_OnLand(EventData)
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
if EventPrefix==self.alias then
local text="Event: Group "..SpawnGroup:GetName().." landed."
self:T(RAT.id..text)
local status=RAT.status.EventLand
self:_SetStatus(SpawnGroup,status)
if self.ATCswitch then
RAT:_ATCFlightLanded(SpawnGroup:GetName())
end
if self.respawn_at_landing and not self.norespawn then
text="Event: Group "..SpawnGroup:GetName().." will be respawned."
self:T(RAT.id..text)
local idx=self:GetSpawnIndexFromGroup(SpawnGroup)
local coord=SpawnGroup:GetCoordinate()
self:_Respawn(idx,coord)
end
end
end
else
self:T2(RAT.id.."ERROR: Group does not exist in RAT:_OnLand().")
end
end
function RAT:_OnEngineShutdown(EventData)
self:F3(EventData)
self:T3(RAT.id.."Captured event EngineShutdown!")
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix and EventPrefix==self.alias then
if not SpawnGroup:InAir()then
local currentstate=self:GetStatus(SpawnGroup)
local text=string.format("Event: Unit %s of group %s shut down its engines. Current state %s.",EventData.IniUnitName,SpawnGroup:GetName(),currentstate)
self:T(RAT.id..text)
if currentstate~=RAT.status.EventEngineShutdown and currentstate~="Dead"then
local status=RAT.status.EventEngineShutdown
self:_SetStatus(SpawnGroup,status)
if not self.respawn_at_landing and not self.norespawn then
text=string.format("Event: Group %s will be respawned. Current state %s => new state %s.",SpawnGroup:GetName(),currentstate,status)
self:T(RAT.id..text)
local idx=self:GetSpawnIndexFromGroup(SpawnGroup)
local coord=SpawnGroup:GetCoordinate()
self:_Respawn(idx,coord)
end
text="Event: Group "..SpawnGroup:GetName().." will be destroyed now."
self:T(RAT.id..text)
self:_Despawn(SpawnGroup)
end
end
end
else
self:T2(RAT.id.."ERROR: Group does not exist in RAT:_OnEngineShutdown().")
end
end
function RAT:_OnHit(EventData)
self:F3(EventData)
self:T(RAT.id..string.format("Captured event Hit by %s! Initiator %s. Target %s",self.alias,tostring(EventData.IniUnitName),tostring(EventData.TgtUnitName)))
local SpawnGroup=EventData.TgtGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix and EventPrefix==self.alias then
self:T(RAT.id..string.format("Event: Group %s was hit. Unit %s.",SpawnGroup:GetName(),tostring(EventData.TgtUnitName)))
local text=string.format("%s, unit %s was hit!",self.alias,EventData.TgtUnitName)
MESSAGE:New(text,10):ToAllIf(self.reportstatus or self.Debug)
end
end
end
function RAT:_OnDeadOrCrash(EventData)
self:F3(EventData)
self:T3(RAT.id.."Captured event DeadOrCrash!")
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
if EventPrefix==self.alias then
self.alive=self.alive-1
local text=string.format("Event: Group %s crashed or died. Alive counter = %d.",SpawnGroup:GetName(),self.alive)
self:T(RAT.id..text)
if EventData.id==world.event.S_EVENT_CRASH then
self:_OnCrash(EventData)
elseif EventData.id==world.event.S_EVENT_DEAD then
self:_OnDead(EventData)
end
end
end
end
end
function RAT:_OnDead(EventData)
self:F3(EventData)
self:T3(RAT.id.."Captured event Dead!")
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
if EventPrefix==self.alias then
local text=string.format("Event: Group %s died. Unit %s.",SpawnGroup:GetName(),EventData.IniUnitName)
self:T(RAT.id..text)
local status=RAT.status.EventDead
self:_SetStatus(SpawnGroup,status)
end
end
else
self:T2(RAT.id.."ERROR: Group does not exist in RAT:_OnDead().")
end
end
function RAT:_OnCrash(EventData)
self:F3(EventData)
self:T3(RAT.id.."Captured event Crash!")
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix and EventPrefix==self.alias then
local _i=self:GetSpawnIndexFromGroup(SpawnGroup)
self.ratcraft[_i].nunits=self.ratcraft[_i].nunits-1
local _n=self.ratcraft[_i].nunits
local _n0=SpawnGroup:GetInitialSize()
local text=string.format("Event: Group %s crashed. Unit %s. Units still alive %d of %d.",SpawnGroup:GetName(),EventData.IniUnitName,_n,_n0)
self:T(RAT.id..text)
local status=RAT.status.EventCrash
self:_SetStatus(SpawnGroup,status)
if _n==0 and self.respawn_after_crash and not self.norespawn then
local text=string.format("No units left of group %s. Group will be respawned now.",SpawnGroup:GetName())
self:T(RAT.id..text)
local idx=self:GetSpawnIndexFromGroup(SpawnGroup)
local coord=SpawnGroup:GetCoordinate()
self:_Respawn(idx,coord)
end
end
else
if self.Debug then
self:E(RAT.id.."ERROR: Group does not exist in RAT:_OnCrash().")
end
end
end
function RAT:_Despawn(group,delay)
if group~=nil then
local index=self:GetSpawnIndexFromGroup(group)
if index~=nil then
self.ratcraft[index].group=nil
self.ratcraft[index]["status"]="Dead"
local despawndelay=0
if delay then
despawndelay=delay
elseif self.respawn_delay then
despawndelay=self.respawn_delay
end
self:T(RAT.id..string.format("%s delayed despawn in %.1f seconds.",self.alias,despawndelay))
SCHEDULER:New(nil,self._Destroy,{self,group},despawndelay)
if self.f10menu and self.SubMenuName~=nil then
self.Menu[self.SubMenuName]["groups"][index]:Remove()
end
end
end
end
function RAT:_Destroy(group)
self:F2(group)
local DCSGroup=group:GetDCSObject()
if DCSGroup and DCSGroup:isExist()then
local triggerdead=true
for _,DCSUnit in pairs(DCSGroup:getUnits())do
if DCSUnit then
if triggerdead then
self:_CreateEventDead(timer.getTime(),DCSUnit)
triggerdead=false
end
_DATABASE:DeleteUnit(DCSUnit:getName())
end
end
DCSGroup:destroy()
DCSGroup=nil
end
return nil
end
function RAT:_CreateEventDead(EventTime,Initiator)
self:F({EventTime,Initiator})
local Event={
id=world.event.S_EVENT_DEAD,
time=EventTime,
initiator=Initiator,
}
world.onEvent(Event)
end
function RAT:_Waypoint(index,description,Type,Coord,Speed,Altitude,Airport)
local _Altitude=Altitude or Coord.y
local Hland=Coord:GetLandHeight()
local _Type=nil
local _Action=nil
local _alttype="RADIO"
if Type==RAT.wp.cold then
_Type="TakeOffParking"
_Action="From Parking Area"
_Altitude=10
_alttype="RADIO"
elseif Type==RAT.wp.hot then
_Type="TakeOffParkingHot"
_Action="From Parking Area Hot"
_Altitude=10
_alttype="RADIO"
elseif Type==RAT.wp.runway then
_Type="TakeOff"
_Action="From Parking Area"
_Altitude=10
_alttype="RADIO"
elseif Type==RAT.wp.air then
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
elseif Type==RAT.wp.climb then
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
elseif Type==RAT.wp.cruise then
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
elseif Type==RAT.wp.descent then
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
elseif Type==RAT.wp.holding then
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
elseif Type==RAT.wp.landing then
_Type="Land"
_Action="Landing"
_Altitude=10
_alttype="RADIO"
elseif Type==RAT.wp.finalwp then
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
else
self:E(RAT.id.."ERROR: Unknown waypoint type in RAT:Waypoint() function!")
_Type="Turning Point"
_Action="Turning Point"
_alttype="RADIO"
end
local text=string.format("\n******************************************************\n")
text=text..string.format("Waypoint =  %d\n",index)
text=text..string.format("Template =  %s\n",self.SpawnTemplatePrefix)
text=text..string.format("Alias    =  %s\n",self.alias)
text=text..string.format("Type: %i - %s\n",Type,_Type)
text=text..string.format("Action: %s\n",_Action)
text=text..string.format("Coord: x = %6.1f km, y = %6.1f km, alt = %6.1f m\n",Coord.x/1000,Coord.z/1000,Coord.y)
text=text..string.format("Speed = %6.1f m/s = %6.1f km/h = %6.1f knots\n",Speed,Speed*3.6,Speed*1.94384)
text=text..string.format("Land     = %6.1f m ASL\n",Hland)
text=text..string.format("Altitude = %6.1f m (%s)\n",_Altitude,_alttype)
if Airport then
if Type==RAT.wp.air then
text=text..string.format("Zone = %s\n",Airport:GetName())
else
text=text..string.format("Airport = %s\n",Airport:GetName())
end
else
text=text..string.format("No airport/zone specified\n")
end
text=text.."******************************************************\n"
self:T2(RAT.id..text)
local RoutePoint={}
RoutePoint.x=Coord.x
RoutePoint.y=Coord.z
RoutePoint.alt=_Altitude
RoutePoint.alt_type=_alttype
RoutePoint.type=_Type
RoutePoint.action=_Action
RoutePoint.speed=Speed
RoutePoint.speed_locked=true
RoutePoint.ETA=nil
RoutePoint.ETA_locked=false
RoutePoint.name=description
if(Airport~=nil)and(Type~=RAT.wp.air)then
local AirbaseID=Airport:GetID()
local AirbaseCategory=Airport:GetAirbaseCategory()
if AirbaseCategory==Airbase.Category.SHIP then
RoutePoint.linkUnit=AirbaseID
RoutePoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.HELIPAD then
RoutePoint.linkUnit=AirbaseID
RoutePoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.AIRDROME then
RoutePoint.airdromeId=AirbaseID
else
self:T(RAT.id.."Unknown Airport category in _Waypoint()!")
end
end
RoutePoint.properties={
["vnav"]=1,
["scale"]=0,
["angle"]=0,
["vangle"]=0,
["steer"]=2,
}
local TaskCombo={}
local TaskHolding=self:_TaskHolding({x=Coord.x,y=Coord.z},Altitude,Speed,self:_Randomize(90,0.9))
local TaskWaypoint=self:_TaskFunction("RAT._WaypointFunction",self,index)
RoutePoint.task={}
RoutePoint.task.id="ComboTask"
RoutePoint.task.params={}
TaskCombo[#TaskCombo+1]=TaskWaypoint
if Type==RAT.wp.holding then
TaskCombo[#TaskCombo+1]=TaskHolding
end
RoutePoint.task.params.tasks=TaskCombo
return RoutePoint
end
function RAT:_Routeinfo(waypoints,comment)
local text=string.format("\n******************************************************\n")
text=text..string.format("Template =  %s\n",self.SpawnTemplatePrefix)
if comment then
text=text..comment.."\n"
end
text=text..string.format("Number of waypoints = %i\n",#waypoints)
for i=1,#waypoints do
local p=waypoints[i]
text=text..string.format("WP #%i: x = %6.1f km, y = %6.1f km, alt = %6.1f m  %s\n",i-1,p.x/1000,p.y/1000,p.alt,self.waypointdescriptions[i])
end
local total=0.0
for i=1,#waypoints-1 do
local point1=waypoints[i]
local point2=waypoints[i+1]
local x1=point1.x
local y1=point1.y
local x2=point2.x
local y2=point2.y
local d=math.sqrt((x1-x2)^2+(y1-y2)^2)
local heading=self:_Course(point1,point2)
total=total+d
text=text..string.format("Distance from WP %i-->%i = %6.1f km. Heading = %03d :  %s - %s\n",i-1,i,d/1000,heading,self.waypointdescriptions[i],self.waypointdescriptions[i+1])
end
text=text..string.format("Total distance = %6.1f km\n",total/1000)
text=text..string.format("******************************************************\n")
self:T2(RAT.id..text)
return total
end
function RAT:_TaskHolding(P1,Altitude,Speed,Duration)
local dx=3000
local dy=0
if self.category==RAT.cat.heli then
dx=200
dy=0
end
local P2={}
P2.x=P1.x+dx
P2.y=P1.y+dy
local Task={
id='Orbit',
params={
pattern=AI.Task.OrbitPattern.RACE_TRACK,
point=P1,
point2=P2,
speed=Speed,
altitude=Altitude
}
}
local DCSTask={}
DCSTask.id="ControlledTask"
DCSTask.params={}
DCSTask.params.task=Task
if self.ATCswitch then
local userflagname=string.format("%s#%03d",self.alias,self.SpawnIndex+1)
local maxholdingduration=60*120
DCSTask.params.stopCondition={userFlag=userflagname,userFlagValue=1,duration=maxholdingduration}
else
DCSTask.params.stopCondition={duration=Duration}
end
return DCSTask
end
function RAT._WaypointFunction(group,rat,wp)
local Tnow=timer.getTime()
local sdx=rat:GetSpawnIndexFromGroup(group)
local departure=rat.ratcraft[sdx].departure:GetName()
local destination=rat.ratcraft[sdx].destination:GetName()
local landing=rat.ratcraft[sdx].landing
local WPholding=rat.ratcraft[sdx].wpholding
local WPfinal=rat.ratcraft[sdx].wpfinal
local text
text=string.format("Flight %s passing waypoint #%d %s.",group:GetName(),wp,rat.waypointdescriptions[wp])
BASE.T(rat,RAT.id..text)
local status=rat.waypointstatus[wp]
rat:_SetStatus(group,status)
if wp==WPholding then
text=string.format("Flight %s to %s ATC: Holding and awaiting landing clearance.",group:GetName(),destination)
MESSAGE:New(text,10):ToAllIf(rat.reportstatus)
if rat.ATCswitch then
if rat.f10menu then
MENU_MISSION_COMMAND:New("Clear for landing",rat.Menu[rat.SubMenuName].groups[sdx],rat.ClearForLanding,rat,group:GetName())
end
rat._ATCRegisterFlight(rat,group:GetName(),Tnow)
end
end
if wp==WPfinal then
text=string.format("Flight %s arrived at final destination %s.",group:GetName(),destination)
MESSAGE:New(text,10):ToAllIf(rat.reportstatus)
BASE.T(rat,RAT.id..text)
if landing==RAT.wp.air then
text=string.format("Activating despawn switch for flight %s! Group will be detroyed soon.",group:GetName())
MESSAGE:New(text,10):ToAllIf(rat.Debug)
BASE.T(rat,RAT.id..text)
rat.ratcraft[sdx].despawnme=true
end
end
end
function RAT:_TaskFunction(FunctionString,...)
self:F2({FunctionString,arg})
local DCSTask
local ArgumentKey
local templatename=self.templategroup:GetName()
local groupname=self:_AnticipatedGroupName()
local DCSScript={}
DCSScript[#DCSScript+1]="local MissionControllable = GROUP:FindByName(\""..groupname.."\") "
DCSScript[#DCSScript+1]="local RATtemplateControllable = GROUP:FindByName(\""..templatename.."\") "
if arg and arg.n>0 then
ArgumentKey='_'..tostring(arg):match("table: (.*)")
self.templategroup:SetState(self.templategroup,ArgumentKey,arg)
DCSScript[#DCSScript+1]="local Arguments = RATtemplateControllable:GetState(RATtemplateControllable, '"..ArgumentKey.."' ) "
DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable, unpack( Arguments ) )"
else
DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable )"
end
DCSTask=self.templategroup:TaskWrappedAction(self.templategroup:CommandDoScript(table.concat(DCSScript)))
return DCSTask
end
function RAT:_AnticipatedGroupName(index)
local index=index or self.SpawnIndex+1
return string.format("%s#%03d",self.alias,index)
end
function RAT:_ActivateUncontrolled()
self:F()
local idx={}
local rat={}
local nactive=0
for spawnindex,ratcraft in pairs(self.ratcraft)do
local group=ratcraft.group
if group and group:IsAlive()then
local text=string.format("Uncontrolled: Group = %s (spawnindex = %d), active = %s.",ratcraft.group:GetName(),spawnindex,tostring(ratcraft.active))
self:T2(RAT.id..text)
if ratcraft.active then
nactive=nactive+1
else
table.insert(idx,spawnindex)
end
end
end
local text=string.format("Uncontrolled: Ninactive = %d,  Nactive = %d (of max %d).",#idx,nactive,self.activate_max)
self:T(RAT.id..text)
if#idx>0 and nactive<self.activate_max then
local index=idx[math.random(#idx)]
local group=self.ratcraft[index].group
self:_CommandStartUncontrolled(group)
end
end
function RAT:_CommandStartUncontrolled(group)
self:F(group)
local StartCommand={id='Start',params={}}
local text=string.format("Uncontrolled: Activating group %s.",group:GetName())
self:T(RAT.id..text)
group:SetCommand(StartCommand)
local index=self:GetSpawnIndexFromGroup(group)
self.ratcraft[index].active=true
self:_SetStatus(group,RAT.status.EventBirth)
end
function RAT:_CommandInvisible(group,switch)
local SetInvisible={id='SetInvisible',params={value=switch}}
group:SetCommand(SetInvisible)
end
function RAT:_CommandImmortal(group,switch)
local SetImmortal={id='SetImmortal',params={value=switch}}
group:SetCommand(SetImmortal)
end
function RAT:_GetObjectSize(unit)
local DCSunit=unit:GetDCSObject()
if DCSunit then
local DCSdesc=DCSunit:getDesc()
local length=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x)
local height=DCSdesc.box.max.y+math.abs(DCSdesc.box.max.y)
local width=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z)
return math.max(length,width)
end
return nil
end
function RAT:_CheckOnTop(group,distmin)
distmin=distmin or 2
for i,uniti in pairs(group:GetUnits())do
local uniti=uniti
if uniti then
local namei=uniti:GetName()
for j,unitj in pairs(_DATABASE.UNITS)do
if unitj then
local unitj=unitj
local namej=unitj:GetName()
if namei~=namej then
local DCSuniti=uniti:GetDCSObject()
local DCSunitj=unitj:GetDCSObject()
if DCSuniti and DCSuniti:isExist()and DCSunitj and DCSunitj:isExist()then
local _dist=uniti:GetCoordinate():Get3DDistance(unitj:GetCoordinate())
if _dist<distmin then
if(not uniti:InAir())and(not unitj:InAir())then
self:T(RAT.id..string.format("Unit %s is on top of unit %s. Distance %.2f m.",namei,namej,_dist))
return true
end
end
end
end
end
end
end
end
return false
end
function RAT:_MinDistance(alpha,beta,ha,hb)
local d1=ha/math.tan(alpha)
local d2=hb/math.tan(beta)
return d1,d2,d1+d2
end
function RAT:_AddFriendlyAirports(ports)
for _,airport in pairs(self.airports)do
if not self:_NameInList(ports,airport:GetName())then
table.insert(ports,airport:GetName())
end
end
end
function RAT:_NameInList(liste,name)
for _,item in pairs(liste)do
if item==name then
return true
end
end
return false
end
function RAT:_AirportExists(name)
for _,airport in pairs(self.airports_map)do
if airport:GetName()==name then
return true
end
end
return false
end
function RAT:_ZoneExists(name)
local z=trigger.misc.getZone(name)
if z then
return true
end
return false
end
function RAT:_SetROE(group,roe)
self:T(RAT.id.."Setting ROE to "..roe.." for group "..group:GetName())
if self.roe==RAT.ROE.returnfire then
group:OptionROEReturnFire()
elseif self.roe==RAT.ROE.weaponfree then
group:OptionROEWeaponFree()
else
group:OptionROEHoldFire()
end
end
function RAT:_SetROT(group,rot)
self:T(RAT.id.."Setting ROT to "..rot.." for group "..group:GetName())
if self.rot==RAT.ROT.passive then
group:OptionROTPassiveDefense()
elseif self.rot==RAT.ROT.evade then
group:OptionROTEvadeFire()
else
group:OptionROTNoReaction()
end
end
function RAT:_SetCoalitionTable()
if self.friendly==RAT.coal.neutral then
self.ctable={coalition.side.NEUTRAL}
elseif self.friendly==RAT.coal.same then
self.ctable={self.coalition,coalition.side.NEUTRAL}
elseif self.friendly==RAT.coal.sameonly then
self.ctable={self.coalition}
else
self:E(RAT.id.."ERROR: Unknown friendly coalition in _SetCoalitionTable(). Defaulting to NEUTRAL.")
self.ctable={self.coalition,coalition.side.NEUTRAL}
end
end
function RAT:_Course(a,b)
local dx=b.x-a.x
local ay
if a.alt then
ay=a.y
else
ay=a.z
end
local by
if b.alt then
by=b.y
else
by=b.z
end
local dy=by-ay
local angle=math.deg(math.atan2(dy,dx))
if angle<0 then
angle=360+angle
end
return angle
end
function RAT:_Heading(course)
local h
if course<=180 then
h=math.rad(course)
else
h=-math.rad(360-course)
end
return h
end
function RAT:_Randomize(value,fac,lower,upper)
local min
if lower then
min=math.max(value-value*fac,lower)
else
min=value-value*fac
end
local max
if upper then
max=math.min(value+value*fac,upper)
else
max=value+value*fac
end
local r=math.random(min,max)
if self.Debug then
local text=string.format("Random: value = %6.2f, fac = %4.2f, min = %6.2f, max = %6.2f, r = %6.2f",value,fac,min,max,r)
self:T3(RAT.id..text)
end
return r
end
function RAT:_PlaceMarkers(waypoints,index)
for i=1,#waypoints do
self:_SetMarker(self.waypointdescriptions[i],waypoints[i],index)
if self.Debug then
local text=string.format("Marker at waypoint #%d: %s for flight #%d",i,self.waypointdescriptions[i],index)
self:T2(RAT.id..text)
end
end
end
function RAT:_SetMarker(text,wp,index)
RAT.markerid=RAT.markerid+1
self.markerids[#self.markerids+1]=RAT.markerid
if self.Debug then
local text2=string.format("%s: placing marker with ID %d and text %s",self.alias,RAT.markerid,text)
self:T2(RAT.id..text2)
end
local vec={x=wp.x,y=wp.alt,z=wp.y}
local flight=self:GetGroupFromIndex(index):GetName()
local text1=string.format("%s:\n%s",flight,text)
trigger.action.markToAll(RAT.markerid,text1,vec,false,"")
end
function RAT:_DeleteMarkers()
for k,v in ipairs(self.markerids)do
trigger.action.removeMark(v)
end
for k,v in ipairs(self.markerids)do
self.markerids[k]=nil
end
end
function RAT:_ModifySpawnTemplate(waypoints,livery,spawnplace,departure,takeoff,parkingdata)
self:F2({waypoints=waypoints,livery=livery,spawnplace=spawnplace,departure=departure,takeoff=takeoff,parking=parkingdata})
local PointVec3=COORDINATE:New(waypoints[1].x,waypoints[1].alt,waypoints[1].y)
if spawnplace then
PointVec3=COORDINATE:NewFromCoordinate(spawnplace)
end
local TemplateGroup=GROUP:FindByName(self.SpawnTemplatePrefix)
local TemplateUnit=TemplateGroup:GetUnit(1)
local spawnonground=takeoff==RAT.wp.cold or takeoff==RAT.wp.hot or takeoff==RAT.wp.runway
local spawnonship=false
local spawnonfarp=false
local spawnonrunway=false
local spawnonairport=false
if spawnonground then
local AirbaseCategory=departure:GetAirbaseCategory()
if AirbaseCategory==Airbase.Category.SHIP then
spawnonship=true
elseif AirbaseCategory==Airbase.Category.HELIPAD then
spawnonfarp=true
elseif AirbaseCategory==Airbase.Category.AIRDROME then
spawnonairport=true
end
spawnonrunway=takeoff==RAT.wp.runway
end
local automatic=false
if automatic and spawnonground then
PointVec3=PointVec3:GetClosestParkingSpot(true,departure)
end
local course=self:_Course(waypoints[1],waypoints[2])
local heading=self:_Heading(course)
if self:_GetSpawnIndex(self.SpawnIndex+1)then
local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
if SpawnTemplate then
self:T(SpawnTemplate)
if self.uncontrolled then
self.SpawnUnControlled=true
SpawnTemplate.uncontrolled=true
end
local nunits=#SpawnTemplate.units
local parkingspots={}
local parkingindex={}
local spots
if spawnonground then
local nfree=0
local termtype=self.termtype
if spawnonrunway then
termtype=AIRBASE.TerminalType.Runway
end
local scanradius=self.parkingscanradius
local scanunits=true
local scanstatics=true
local scanscenery=self.parkingscanscenery
local verysafe=self.parkingverysafe
if spawnonship or spawnonfarp or spawnonrunway then
self:T(RAT.id..string.format("Group %s is spawned on farp/ship/runway %s.",self.alias,departure:GetName()))
nfree=departure:GetFreeParkingSpotsNumber(termtype,true)
spots=departure:GetFreeParkingSpotsTable(termtype,true)
elseif parkingdata~=nil then
nfree=#parkingdata
spots=parkingdata
else
if self.category==RAT.cat.heli then
if termtype==nil then
self:T(RAT.id..string.format("Helo group %s is spawned at %s using terminal type %d.",self.alias,departure:GetName(),AIRBASE.TerminalType.HelicopterOnly))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
if nfree<nunits then
self:T(RAT.id..string.format("Helo group %s is spawned at %s using terminal type %d.",self.alias,departure:GetName(),AIRBASE.TerminalType.HelicopterUsable))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterUsable,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
end
else
self:T(RAT.id..string.format("Helo group %s is at %s using terminal type %d.",self.alias,departure:GetName(),termtype))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,termtype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
end
else
if termtype==nil then
local bomber=TemplateUnit:HasAttribute("Bombers")
local transport=TemplateUnit:HasAttribute("Transports")
if bomber or transport then
self:T(RAT.id..string.format("Transport/bomber group %s is spawned at %s using terminal type %d.",self.alias,departure:GetName(),AIRBASE.TerminalType.OpenBig))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.OpenBig,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
if nfree<nunits then
self:T(RAT.id..string.format("Transport/bomber group %s is at %s using terminal type %d.",self.alias,departure:GetName(),AIRBASE.TerminalType.OpenMed))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.OpenMed,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
end
else
self:T(RAT.id..string.format("Fighter group %s is spawned at %s using terminal type %d.",self.alias,departure:GetName(),AIRBASE.TerminalType.FighterAircraft))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.FighterAircraft,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
end
else
self:T(RAT.id..string.format("Plane group %s is spawned at %s using terminal type %d.",self.alias,departure:GetName(),termtype))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,termtype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
end
end
end
local parkingdata=departure:GetParkingSpotsTable(termtype)
self:T2(RAT.id..string.format("Parking at %s, terminal type %s:",departure:GetName(),tostring(termtype)))
for _,_spot in pairs(parkingdata)do
self:T2(RAT.id..string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d",
departure:GetName(),_spot.TerminalID,_spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy))
end
self:T(RAT.id..string.format("%s at %s: free parking spots = %d - number of units = %d",self.alias,departure:GetName(),nfree,nunits))
local _notenough=false
if spawnonship or spawnonfarp or spawnonrunway then
if nfree>=1 then
for i=1,nunits do
table.insert(parkingspots,spots[1].Coordinate)
table.insert(parkingindex,spots[1].TerminalID)
end
PointVec3=spots[1].Coordinate
else
_notenough=true
end
elseif spawnonairport then
if nfree>=nunits then
for i=1,nunits do
table.insert(parkingspots,spots[i].Coordinate)
table.insert(parkingindex,spots[i].TerminalID)
end
else
_notenough=true
end
end
if _notenough then
if self.respawn_inair and not self.SpawnUnControlled then
self:E(RAT.id..string.format("WARNING: Group %s has no parking spots at %s ==> air start!",self.SpawnTemplatePrefix,departure:GetName()))
spawnonground=false
spawnonship=false
spawnonfarp=false
spawnonrunway=false
waypoints[1].type=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1]
waypoints[1].action=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2]
PointVec3.x=PointVec3.x+math.random(-1500,1500)
PointVec3.z=PointVec3.z+math.random(-1500,1500)
if self.category==RAT.cat.heli then
PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000)
else
PointVec3.y=PointVec3:GetLandHeight()+math.random(500,3000)
end
else
self:E(RAT.id..string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,departure:GetName()))
return nil
end
end
else
end
for UnitID=1,nunits do
local UnitTemplate=SpawnTemplate.units[UnitID]
local SX=UnitTemplate.x
local SY=UnitTemplate.y
local BX=SpawnTemplate.route.points[1].x
local BY=SpawnTemplate.route.points[1].y
local TX=PointVec3.x+(SX-BX)
local TY=PointVec3.z+(SY-BY)
if spawnonground then
if spawnonship or spawnonfarp or spawnonrunway or automatic then
self:T(RAT.id..string.format("RAT group %s spawning at farp, ship or runway %s.",self.alias,departure:GetName()))
SpawnTemplate.units[UnitID].x=PointVec3.x
SpawnTemplate.units[UnitID].y=PointVec3.z
SpawnTemplate.units[UnitID].alt=PointVec3.y
else
self:T(RAT.id..string.format("RAT group %s spawning at airbase %s on parking spot id %d",self.alias,departure:GetName(),parkingindex[UnitID]))
SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x
SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z
SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y
end
else
self:T(RAT.id..string.format("RAT group %s spawning in air at %s.",self.alias,departure:GetName()))
SpawnTemplate.units[UnitID].x=TX
SpawnTemplate.units[UnitID].y=TY
SpawnTemplate.units[UnitID].alt=PointVec3.y
end
if self.Debug then
local unitspawn=COORDINATE:New(SpawnTemplate.units[UnitID].x,SpawnTemplate.units[UnitID].alt,SpawnTemplate.units[UnitID].y)
unitspawn:MarkToAll(string.format("RAT %s Spawnplace unit #%d",self.alias,UnitID))
end
UnitTemplate.parking=nil
UnitTemplate.parking_id=nil
if parkingindex[UnitID]and not automatic then
UnitTemplate.parking=parkingindex[UnitID]
end
self:T2(RAT.id..string.format("RAT group %s unit number %d: Parking    = %s",self.alias,UnitID,tostring(UnitTemplate.parking)))
self:T2(RAT.id..string.format("RAT group %s unit number %d: Parking ID = %s",self.alias,UnitID,tostring(UnitTemplate.parking_id)))
SpawnTemplate.units[UnitID].heading=heading
SpawnTemplate.units[UnitID].psi=-heading
if livery then
SpawnTemplate.units[UnitID].livery_id=livery
end
if self.actype then
SpawnTemplate.units[UnitID]["type"]=self.actype
end
SpawnTemplate.units[UnitID]["skill"]=self.skill
if self.onboardnum then
SpawnTemplate.units[UnitID]["onboard_num"]=string.format("%s%d%02d",self.onboardnum,(self.SpawnIndex-1)%10,(self.onboardnum0-1)+UnitID)
end
SpawnTemplate.CoalitionID=self.coalition
if self.country then
SpawnTemplate.CountryID=self.country
end
end
for i,wp in ipairs(waypoints)do
SpawnTemplate.route.points[i]=wp
end
SpawnTemplate.x=PointVec3.x
SpawnTemplate.y=PointVec3.z
if self.radio then
SpawnTemplate.communication=self.radio
end
if self.frequency then
SpawnTemplate.frequency=self.frequency
end
if self.modulation then
SpawnTemplate.modulation=self.modulation
end
self:T(SpawnTemplate)
end
end
return true
end
function RAT:_ATCInit(airports_map)
if not RAT.ATC.init then
local text
text="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay        = "..RAT.ATC.delay
BASE:T(RAT.id..text)
RAT.ATC.init=true
for _,ap in pairs(airports_map)do
local name=ap:GetName()
RAT.ATC.airport[name]={}
RAT.ATC.airport[name].queue={}
RAT.ATC.airport[name].busy=false
RAT.ATC.airport[name].onfinal={}
RAT.ATC.airport[name].Nonfinal=0
RAT.ATC.airport[name].traffic=0
RAT.ATC.airport[name].Tlastclearance=nil
end
SCHEDULER:New(nil,RAT._ATCCheck,{self},5,15)
SCHEDULER:New(nil,RAT._ATCStatus,{self},5,60)
RAT.ATC.T0=timer.getTime()
end
end
function RAT:_ATCAddFlight(name,dest)
BASE:T(string.format("%sATC %s: Adding flight %s with destination %s.",RAT.id,dest,name,dest))
RAT.ATC.flight[name]={}
RAT.ATC.flight[name].destination=dest
RAT.ATC.flight[name].Tarrive=-1
RAT.ATC.flight[name].holding=-1
RAT.ATC.flight[name].Tonfinal=-1
end
function RAT:_ATCDelFlight(t,entry)
for k,_ in pairs(t)do
if k==entry then
t[entry]=nil
end
end
end
function RAT:_ATCRegisterFlight(name,time)
BASE:T(RAT.id.."Flight "..name.." registered at ATC for landing clearance.")
RAT.ATC.flight[name].Tarrive=time
RAT.ATC.flight[name].holding=0
end
function RAT:_ATCStatus()
local Tnow=timer.getTime()
for name,_ in pairs(RAT.ATC.flight)do
local hold=RAT.ATC.flight[name].holding
local dest=RAT.ATC.flight[name].destination
if hold>=0 then
local busy="Runway state is unknown"
if RAT.ATC.airport[dest].Nonfinal>0 then
busy="Runway is occupied by "..RAT.ATC.airport[dest].Nonfinal
else
busy="Runway is currently clear"
end
local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.",dest,name,hold/60,hold%60,busy)
BASE:T(RAT.id..text)
elseif hold==RAT.ATC.onfinal then
local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal
local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.",dest,name,Tfinal/60,Tfinal%60)
BASE:T(RAT.id..text)
elseif hold==RAT.ATC.unregistered then
else
BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().")
end
end
end
function RAT:_ATCCheck()
RAT:_ATCQueue()
local Tnow=timer.getTime()
for name,_ in pairs(RAT.ATC.airport)do
for qID,flight in ipairs(RAT.ATC.airport[name].queue)do
local nqueue=#RAT.ATC.airport[name].queue
local landing1
if RAT.ATC.airport[name].Tlastclearance then
landing1=(Tnow-RAT.ATC.airport[name].Tlastclearance>RAT.ATC.delay)and RAT.ATC.airport[name].Nonfinal<RAT.ATC.Nclearance
else
landing1=false
end
local landing2=RAT.ATC.airport[name].Nonfinal==0
if not landing1 and not landing2 then
RAT.ATC.flight[flight].holding=Tnow-RAT.ATC.flight[flight].Tarrive
local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.",name,flight,qID,nqueue,RAT.ATC.flight[flight].holding/60,RAT.ATC.flight[flight].holding%60)
BASE:T(RAT.id..text)
else
local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.",name,flight,RAT.ATC.flight[flight].holding/60,RAT.ATC.flight[flight].holding%60)
BASE:T(RAT.id..text)
RAT:_ATCClearForLanding(name,flight)
end
end
end
RAT:_ATCQueue()
end
function RAT:_ATCClearForLanding(airport,flight)
RAT.ATC.flight[flight].holding=RAT.ATC.onfinal
RAT.ATC.airport[airport].busy=true
RAT.ATC.airport[airport].onfinal[flight]=flight
RAT.ATC.airport[airport].Nonfinal=RAT.ATC.airport[airport].Nonfinal+1
RAT.ATC.airport[airport].Tlastclearance=timer.getTime()
RAT.ATC.flight[flight].Tonfinal=timer.getTime()
trigger.action.setUserFlag(flight,1)
local flagvalue=trigger.misc.getUserFlag(flight)
local text1=string.format("ATC %s: Flight %s cleared for landing (flag=%d).",airport,flight,flagvalue)
if string.find(flight,"#")then
flight=string.match(flight,"^(.+)#")
end
local text2=string.format("ATC %s: Flight %s you are cleared for landing.",airport,flight)
BASE:T(RAT.id..text1)
MESSAGE:New(text2,10):ToAllIf(RAT.ATC.messages)
end
function RAT:_ATCFlightLanded(name)
if RAT.ATC.flight[name]then
local dest=RAT.ATC.flight[name].destination
local Tnow=timer.getTime()
local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal
local Thold=RAT.ATC.flight[name].Tonfinal-RAT.ATC.flight[name].Tarrive
RAT.ATC.airport[dest].busy=false
RAT.ATC.airport[dest].onfinal[name]=nil
RAT.ATC.airport[dest].Nonfinal=RAT.ATC.airport[dest].Nonfinal-1
RAT:_ATCDelFlight(RAT.ATC.flight,name)
RAT.ATC.airport[dest].traffic=RAT.ATC.airport[dest].traffic+1
local TrafficPerHour=RAT.ATC.airport[dest].traffic/(timer.getTime()-RAT.ATC.T0)*3600
local text1=string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)
local text2=string.format("ATC %s: Number of flights still on final %d.",dest,RAT.ATC.airport[dest].Nonfinal)
local text3=string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,RAT.ATC.airport[dest].traffic,TrafficPerHour)
if string.find(name,"#")then
name=string.match(name,"^(.+)#")
end
local text4=string.format("ATC %s: Flight %s landed. Welcome to %s.",dest,name,dest)
BASE:T(RAT.id..text1)
BASE:T(RAT.id..text2)
BASE:T(RAT.id..text3)
MESSAGE:New(text4,10):ToAllIf(RAT.ATC.messages)
end
end
function RAT:_ATCQueue()
for airport,_ in pairs(RAT.ATC.airport)do
local _queue={}
for name,_ in pairs(RAT.ATC.flight)do
local Tnow=timer.getTime()
if RAT.ATC.flight[name].holding>=0 then
RAT.ATC.flight[name].holding=Tnow-RAT.ATC.flight[name].Tarrive
end
local hold=RAT.ATC.flight[name].holding
local dest=RAT.ATC.flight[name].destination
if hold>=0 and airport==dest then
_queue[#_queue+1]={name,hold}
end
end
local function compare(a,b)
return a[2]>b[2]
end
table.sort(_queue,compare)
RAT.ATC.airport[airport].queue={}
for k,v in ipairs(_queue)do
table.insert(RAT.ATC.airport[airport].queue,v[1])
end
end
end
RATMANAGER={
ClassName="RATMANAGER",
Debug=false,
rat={},
name={},
alive={},
min={},
nrat=0,
ntot=nil,
Tcheck=60,
dTspawn=1.0,
manager=nil,
managerid=nil,
}
RATMANAGER.id="RATMANAGER | "
function RATMANAGER:New(ntot)
local self=BASE:Inherit(self,BASE:New())
self.ntot=ntot or 1
self:E(RATMANAGER.id..string.format("Creating manager for %d groups.",ntot))
return self
end
function RATMANAGER:Add(ratobject,min)
ratobject.norespawn=true
ratobject.f10menu=false
self.nrat=self.nrat+1
self.rat[self.nrat]=ratobject
self.alive[self.nrat]=0
self.name[self.nrat]=ratobject.alias
self.min[self.nrat]=min or 1
self:T(RATMANAGER.id..string.format("Adding ratobject %s with min flights = %d",self.name[self.nrat],self.min[self.nrat]))
ratobject:Spawn(0)
return self
end
function RATMANAGER:Start(delay)
local delay=delay or 5
local text=string.format(RATMANAGER.id.."RAT manager will be started in %d seconds.\n",delay)
text=text..string.format("Managed groups:\n")
for i=1,self.nrat do
text=text..string.format("- %s with min groups %d\n",self.name[i],self.min[i])
end
text=text..string.format("Number of constantly alive groups %d",self.ntot)
self:E(text)
SCHEDULER:New(nil,self._Start,{self},delay)
return self
end
function RATMANAGER:_Start()
local n=0
for i=1,self.nrat do
n=n+self.min[i]
end
self.ntot=math.max(self.ntot,n)
local N=self:_RollDice(self.nrat,self.ntot,self.min,self.alive)
local time=0.0
for i=1,self.nrat do
for j=1,N[i]do
time=time+self.dTspawn
SCHEDULER:New(nil,RAT._SpawnWithRoute,{self.rat[i]},time)
end
end
for i=1,self.nrat do
if self.rat[i].uncontrolled and self.rat[i].activate_uncontrolled then
local Tactivate=math.max(time+1,self.rat[i].activate_delay)
SCHEDULER:New(self.rat[i],self.rat[i]._ActivateUncontrolled,{self.rat[i]},Tactivate,self.rat[i].activate_delta,self.rat[i].activate_frand)
end
end
local TstartManager=math.max(time+1,self.Tcheck)
self.manager,self.managerid=SCHEDULER:New(self,self._Manage,{self},TstartManager,self.Tcheck)
local text=string.format(RATMANAGER.id.."Starting RAT manager with scheduler ID %s in %d seconds. Repeat interval %d seconds.",self.managerid,TstartManager,self.Tcheck)
self:E(text)
return self
end
function RATMANAGER:Stop(delay)
delay=delay or 1
self:E(string.format(RATMANAGER.id.."Manager will be stopped in %d seconds.",delay))
SCHEDULER:New(nil,self._Stop,{self},delay)
return self
end
function RATMANAGER:_Stop()
self:E(string.format(RATMANAGER.id.."Stopping manager with scheduler ID %s.",self.managerid))
self.manager:Stop(self.managerid)
return self
end
function RATMANAGER:SetTcheck(dt)
self.Tcheck=dt or 60
return self
end
function RATMANAGER:SetTspawn(dt)
self.dTspawn=dt or 1.0
return self
end
function RATMANAGER:_Manage()
local ntot=self:_Count()
local text=string.format("Number of alive groups %d. New groups to be spawned %d.",ntot,self.ntot-ntot)
self:T(RATMANAGER.id..text)
local N=self:_RollDice(self.nrat,self.ntot,self.min,self.alive)
local time=0.0
for i=1,self.nrat do
for j=1,N[i]do
time=time+self.dTspawn
SCHEDULER:New(nil,RAT._SpawnWithRoute,{self.rat[i]},time)
end
end
end
function RATMANAGER:_Count()
local ntotal=0
for i=1,self.nrat do
local n=0
local ratobject=self.rat[i]
for spawnindex,ratcraft in pairs(ratobject.ratcraft)do
local group=ratcraft.group
if group and group:IsAlive()then
n=n+1
end
end
self.alive[i]=n
ntotal=ntotal+n
local text=string.format("Number of alive groups of %s = %d",self.name[i],n)
self:T(RATMANAGER.id..text)
end
return ntotal
end
function RATMANAGER:_RollDice(nrat,ntot,min,alive)
local function sum(A,index)
local summe=0
for _,i in ipairs(index)do
summe=summe+A[i]
end
return summe
end
local N={}
local M={}
local P={}
for i=1,nrat do
N[#N+1]=0
M[#M+1]=math.max(alive[i],min[i])
P[#P+1]=math.max(min[i]-alive[i],0)
end
local mini={}
local maxi={}
local rattab={}
for i=1,nrat do
table.insert(rattab,i)
end
local done={}
local nnew=ntot
for i=1,nrat do
nnew=nnew-alive[i]
end
for i=1,nrat-1 do
local r=math.random(#rattab)
local j=rattab[r]
table.remove(rattab,r)
table.insert(done,j)
local sN=sum(N,done)
local sP=sum(P,rattab)
maxi[j]=nnew-sN-sP
mini[j]=P[j]
if maxi[j]>=mini[j]then
N[j]=math.random(mini[j],maxi[j])
else
N[j]=0
end
self:T3(string.format("RATMANAGER: i=%d, alive=%d, min=%d, mini=%d, maxi=%d, add=%d, sumN=%d, sumP=%d",j,alive[j],min[j],mini[j],maxi[j],N[j],sN,sP))
end
local j=rattab[1]
N[j]=nnew-sum(N,done)
mini[j]=nnew-sum(N,done)
maxi[j]=nnew-sum(N,done)
table.remove(rattab,1)
table.insert(done,j)
local text=RATMANAGER.id.."\n"
for i=1,nrat do
text=text..string.format("%s: i=%d, alive=%d, min=%d, mini=%d, maxi=%d, add=%d\n",self.name[i],i,alive[i],min[i],mini[i],maxi[i],N[i])
end
text=text..string.format("Total # of groups to add = %d",sum(N,done))
self:T(text)
return N
end
RANGE={
ClassName="RANGE",
Debug=false,
verbose=0,
id=nil,
rangename=nil,
location=nil,
messages=true,
rangeradius=5000,
rangezone=nil,
strafeTargets={},
bombingTargets={},
nbombtargets=0,
nstrafetargets=0,
MenuAddedTo={},
planes={},
strafeStatus={},
strafePlayerResults={},
bombPlayerResults={},
PlayerSettings={},
dtBombtrack=0.005,
BombtrackThreshold=25000,
Tmsg=30,
examinergroupname=nil,
examinerexclusive=nil,
strafemaxalt=914,
ndisplayresult=10,
BombSmokeColor=SMOKECOLOR.Red,
StrafeSmokeColor=SMOKECOLOR.Green,
StrafePitSmokeColor=SMOKECOLOR.White,
illuminationminalt=500,
illuminationmaxalt=1000,
scorebombdistance=1000,
TdelaySmoke=3.0,
eventmoose=true,
trackbombs=true,
trackrockets=true,
trackmissiles=true,
defaultsmokebomb=true,
autosave=false,
instructorfreq=nil,
instructor=nil,
rangecontrolfreq=nil,
rangecontrol=nil,
soundpath="Range Soundfiles/",
targetsheet=nil,
targetpath=nil,
targetprefix=nil,
}
RANGE.Defaults={
goodhitrange=25,
strafemaxalt=914,
dtBombtrack=0.005,
Tmsg=30,
ndisplayresult=10,
rangeradius=5000,
TdelaySmoke=3.0,
boxlength=3000,
boxwidth=300,
goodpass=20,
foulline=610
}
RANGE.TargetType={
UNIT="Unit",
STATIC="Static",
COORD="Coordinate"
}
RANGE.Sound={
RC0={filename="RC-0.ogg",duration=0.60},
RC1={filename="RC-1.ogg",duration=0.47},
RC2={filename="RC-2.ogg",duration=0.43},
RC3={filename="RC-3.ogg",duration=0.50},
RC4={filename="RC-4.ogg",duration=0.58},
RC5={filename="RC-5.ogg",duration=0.54},
RC6={filename="RC-6.ogg",duration=0.61},
RC7={filename="RC-7.ogg",duration=0.53},
RC8={filename="RC-8.ogg",duration=0.34},
RC9={filename="RC-9.ogg",duration=0.54},
RCAccuracy={filename="RC-Accuracy.ogg",duration=0.67},
RCDegrees={filename="RC-Degrees.ogg",duration=0.59},
RCExcellentHit={filename="RC-ExcellentHit.ogg",duration=0.76},
RCExcellentPass={filename="RC-ExcellentPass.ogg",duration=0.89},
RCFeet={filename="RC-Feet.ogg",duration=0.49},
RCFor={filename="RC-For.ogg",duration=0.64},
RCGoodHit={filename="RC-GoodHit.ogg",duration=0.52},
RCGoodPass={filename="RC-GoodPass.ogg",duration=0.62},
RCHitsOnTarget={filename="RC-HitsOnTarget.ogg",duration=0.88},
RCImpact={filename="RC-Impact.ogg",duration=0.61},
RCIneffectiveHit={filename="RC-IneffectiveHit.ogg",duration=0.86},
RCIneffectivePass={filename="RC-IneffectivePass.ogg",duration=0.99},
RCInvalidHit={filename="RC-InvalidHit.ogg",duration=2.97},
RCLeftStrafePitTooQuickly={filename="RC-LeftStrafePitTooQuickly.ogg",duration=3.09},
RCPercent={filename="RC-Percent.ogg",duration=0.56},
RCPoorHit={filename="RC-PoorHit.ogg",duration=0.54},
RCPoorPass={filename="RC-PoorPass.ogg",duration=0.68},
RCRollingInOnStrafeTarget={filename="RC-RollingInOnStrafeTarget.ogg",duration=1.38},
RCTotalRoundsFired={filename="RC-TotalRoundsFired.ogg",duration=1.22},
RCWeaponImpactedTooFar={filename="RC-WeaponImpactedTooFar.ogg",duration=3.73},
IR0={filename="IR-0.ogg",duration=0.55},
IR1={filename="IR-1.ogg",duration=0.41},
IR2={filename="IR-2.ogg",duration=0.37},
IR3={filename="IR-3.ogg",duration=0.41},
IR4={filename="IR-4.ogg",duration=0.37},
IR5={filename="IR-5.ogg",duration=0.43},
IR6={filename="IR-6.ogg",duration=0.55},
IR7={filename="IR-7.ogg",duration=0.43},
IR8={filename="IR-8.ogg",duration=0.38},
IR9={filename="IR-9.ogg",duration=0.55},
IRDecimal={filename="IR-Decimal.ogg",duration=0.54},
IRMegaHertz={filename="IR-MegaHertz.ogg",duration=0.87},
IREnterRange={filename="IR-EnterRange.ogg",duration=4.83},
IRExitRange={filename="IR-ExitRange.ogg",duration=3.10},
}
RANGE.Names={}
RANGE.MenuF10={}
RANGE.MenuF10Root=nil
RANGE.version="2.5.1"
function RANGE:New(RangeName)
local self=BASE:Inherit(self,FSM:New())
self.rangename=RangeName or"Practice Range"
self.id=string.format("RANGE %s | ",self.rangename)
local text=string.format("Script version %s - creating new RANGE object %s.",RANGE.version,self.rangename)
self:I(self.id..text)
self:SetDefaultPlayerSmokeBomb()
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","Impact","*")
self:AddTransition("*","RollingIn","*")
self:AddTransition("*","StrafeResult","*")
self:AddTransition("*","EnterRange","*")
self:AddTransition("*","ExitRange","*")
self:AddTransition("*","Save","*")
self:AddTransition("*","Load","*")
return self
end
function RANGE:onafterStart()
local _location=nil
local _count=0
for _,_target in pairs(self.bombingTargets)do
_count=_count+1
if _location==nil then
_location=self:_GetBombTargetCoordinate(_target)
end
end
self.nbombtargets=_count
_count=0
for _,_target in pairs(self.strafeTargets)do
_count=_count+1
for _,_unit in pairs(_target.targets)do
if _location==nil then
_location=_unit:GetCoordinate()
end
end
end
self.nstrafetargets=_count
if self.location==nil then
self.location=_location
end
if self.location==nil then
local text=string.format("ERROR! No range location found. Number of strafe targets = %d. Number of bomb targets = %d.",self.nstrafetargets,self.nbombtargets)
self:E(self.id..text)
return
end
if self.rangezone==nil then
self.rangezone=ZONE_RADIUS:New(self.rangename,{x=self.location.x,y=self.location.z},self.rangeradius)
end
local text=string.format("Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.",self.rangename,self.nstrafetargets,self.nbombtargets)
self:I(self.id..text)
if self.eventmoose then
self:T(self.id.."Events are handled by MOOSE.")
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Hit)
self:HandleEvent(EVENTS.Shot)
else
self:T(self.id.."Events are handled directly by DCS.")
world.addEventHandler(self)
end
for _,_target in pairs(self.bombingTargets)do
local _static=_target.type==RANGE.TargetType.STATIC
if _target.move and _static==false and _target.speed>1 then
local unit=_target.target
_target.target:PatrolZones({self.rangezone},_target.speed*0.75,"Off road")
end
end
if self.rangecontrolfreq and not self.useSRS then
self.rangecontrol=RADIOQUEUE:New(self.rangecontrolfreq,nil,self.rangename)
self.rangecontrol.schedonce=true
self.rangecontrol:SetDigit(0,RANGE.Sound.RC0.filename,RANGE.Sound.RC0.duration,self.soundpath)
self.rangecontrol:SetDigit(1,RANGE.Sound.RC1.filename,RANGE.Sound.RC1.duration,self.soundpath)
self.rangecontrol:SetDigit(2,RANGE.Sound.RC2.filename,RANGE.Sound.RC2.duration,self.soundpath)
self.rangecontrol:SetDigit(3,RANGE.Sound.RC3.filename,RANGE.Sound.RC3.duration,self.soundpath)
self.rangecontrol:SetDigit(4,RANGE.Sound.RC4.filename,RANGE.Sound.RC4.duration,self.soundpath)
self.rangecontrol:SetDigit(5,RANGE.Sound.RC5.filename,RANGE.Sound.RC5.duration,self.soundpath)
self.rangecontrol:SetDigit(6,RANGE.Sound.RC6.filename,RANGE.Sound.RC6.duration,self.soundpath)
self.rangecontrol:SetDigit(7,RANGE.Sound.RC7.filename,RANGE.Sound.RC7.duration,self.soundpath)
self.rangecontrol:SetDigit(8,RANGE.Sound.RC8.filename,RANGE.Sound.RC8.duration,self.soundpath)
self.rangecontrol:SetDigit(9,RANGE.Sound.RC9.filename,RANGE.Sound.RC9.duration,self.soundpath)
self.rangecontrol:SetSenderCoordinate(self.location)
self.rangecontrol:SetSenderUnitName(self.rangecontrolrelayname)
self.rangecontrol:Start(1,0.1)
if self.instructorfreq and not self.useSRS then
self.instructor=RADIOQUEUE:New(self.instructorfreq,nil,self.rangename)
self.instructor.schedonce=true
self.instructor:SetDigit(0,RANGE.Sound.IR0.filename,RANGE.Sound.IR0.duration,self.soundpath)
self.instructor:SetDigit(1,RANGE.Sound.IR1.filename,RANGE.Sound.IR1.duration,self.soundpath)
self.instructor:SetDigit(2,RANGE.Sound.IR2.filename,RANGE.Sound.IR2.duration,self.soundpath)
self.instructor:SetDigit(3,RANGE.Sound.IR3.filename,RANGE.Sound.IR3.duration,self.soundpath)
self.instructor:SetDigit(4,RANGE.Sound.IR4.filename,RANGE.Sound.IR4.duration,self.soundpath)
self.instructor:SetDigit(5,RANGE.Sound.IR5.filename,RANGE.Sound.IR5.duration,self.soundpath)
self.instructor:SetDigit(6,RANGE.Sound.IR6.filename,RANGE.Sound.IR6.duration,self.soundpath)
self.instructor:SetDigit(7,RANGE.Sound.IR7.filename,RANGE.Sound.IR7.duration,self.soundpath)
self.instructor:SetDigit(8,RANGE.Sound.IR8.filename,RANGE.Sound.IR8.duration,self.soundpath)
self.instructor:SetDigit(9,RANGE.Sound.IR9.filename,RANGE.Sound.IR9.duration,self.soundpath)
self.instructor:SetSenderCoordinate(self.location)
self.instructor:SetSenderUnitName(self.instructorrelayname)
self.instructor:Start(1,0.1)
end
end
if self.autosave then
self:Load()
end
if self.Debug then
self:_MarkTargetsOnMap()
self:_SmokeBombTargets()
self:_SmokeStrafeTargets()
self:_SmokeStrafeTargetBoxes()
self.rangezone:SmokeZone(SMOKECOLOR.White)
end
self:__Status(-60)
end
function RANGE:SetMaxStrafeAlt(maxalt)
self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt
return self
end
function RANGE:SetBombtrackTimestep(dt)
self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack
return self
end
function RANGE:SetMessageTimeDuration(time)
self.Tmsg=time or RANGE.Defaults.Tmsg
return self
end
function RANGE:SetAutosaveOn()
self.autosave=true
return self
end
function RANGE:SetAutosaveOff()
self.autosave=false
return self
end
function RANGE:SetTargetSheet(path,prefix)
if io then
self.targetsheet=true
self.targetpath=path
self.targetprefix=prefix
else
self:E(self.lid.."ERROR: io is not desanitized. Cannot save target sheet.")
end
return self
end
function RANGE:SetFunkManOn(Port,Host)
self.funkmanSocket=SOCKET:New(Port,Host)
return self
end
function RANGE:SetMessageToExaminer(examinergroupname,exclusively)
self.examinergroupname=examinergroupname
self.examinerexclusive=exclusively
return self
end
function RANGE:SetDisplayedMaxPlayerResults(nmax)
self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult
return self
end
function RANGE:SetRangeRadius(radius)
self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius
return self
end
function RANGE:SetDefaultPlayerSmokeBomb(switch)
if switch==true or switch==nil then
self.defaultsmokebomb=true
else
self.defaultsmokebomb=false
end
return self
end
function RANGE:SetBombtrackThreshold(distance)
self.BombtrackThreshold=(distance or 25)*1000
return self
end
function RANGE:SetRangeLocation(coordinate)
self.location=coordinate
return self
end
function RANGE:SetRangeZone(zone)
self.rangezone=zone
return self
end
function RANGE:SetBombTargetSmokeColor(colorid)
self.BombSmokeColor=colorid or SMOKECOLOR.Red
return self
end
function RANGE:SetScoreBombDistance(distance)
self.scorebombdistance=distance or 1000
return self
end
function RANGE:SetStrafeTargetSmokeColor(colorid)
self.StrafeSmokeColor=colorid or SMOKECOLOR.Green
return self
end
function RANGE:SetStrafePitSmokeColor(colorid)
self.StrafePitSmokeColor=colorid or SMOKECOLOR.White
return self
end
function RANGE:SetSmokeTimeDelay(delay)
self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke
return self
end
function RANGE:DebugON()
self.Debug=true
return self
end
function RANGE:DebugOFF()
self.Debug=false
return self
end
function RANGE:SetMessagesOFF()
self.messages=false
return self
end
function RANGE:SetMessagesON()
self.messages=true
return self
end
function RANGE:TrackBombsON()
self.trackbombs=true
return self
end
function RANGE:TrackBombsOFF()
self.trackbombs=false
return self
end
function RANGE:TrackRocketsON()
self.trackrockets=true
return self
end
function RANGE:TrackRocketsOFF()
self.trackrockets=false
return self
end
function RANGE:TrackMissilesON()
self.trackmissiles=true
return self
end
function RANGE:TrackMissilesOFF()
self.trackmissiles=false
return self
end
function RANGE:SetSRS(PathToSRS,Port,Coalition,Frequency,Modulation,Volume,PathToGoogleKey)
if PathToSRS then
self.useSRS=true
self.controlmsrs=MSRS:New(PathToSRS,Frequency or 256,Modulation or radio.modulation.AM,Volume or 1.0)
self.controlmsrs:SetPort(Port)
self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE)
self.controlmsrs:SetLabel("RANGEC")
self.controlsrsQ=MSRSQUEUE:New("CONTROL")
self.instructmsrs=MSRS:New(PathToSRS,Frequency or 305,Modulation or radio.modulation.AM,Volume or 1.0)
self.instructmsrs:SetPort(Port)
self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE)
self.instructmsrs:SetLabel("RANGEI")
self.instructsrsQ=MSRSQUEUE:New("INSTRUCT")
if PathToGoogleKey then
self.instructmsrs:SetGoogle(PathToGoogleKey)
self.instructmsrs:SetGoogle(PathToGoogleKey)
end
else
self:E(self.lid..string.format("ERROR: No SRS path specified!"))
end
return self
end
function RANGE:SetSRSRangeControl(frequency,modulation,voice,culture,gender,relayunitname)
self.rangecontrolfreq=frequency or 256
self.controlmsrs:SetFrequencies(self.rangecontrolfreq)
self.controlmsrs:SetModulations(modulation or radio.modulation.AM)
self.controlmsrs:SetVoice(voice)
self.controlmsrs:SetCulture(culture or"en-US")
self.controlmsrs:SetGender(gender or"female")
self.rangecontrol=true
if relayunitname then
local unit=UNIT:FindByName(relayunitname)
local Coordinate=unit:GetCoordinate()
self.rangecontrolrelayname=relayunitname
end
return self
end
function RANGE:SetSRSRangeInstructor(frequency,modulation,voice,culture,gender,relayunitname)
self.instructorfreq=frequency or 305
self.instructmsrs:SetFrequencies(self.instructorfreq)
self.instructmsrs:SetModulations(modulation or radio.modulation.AM)
self.instructmsrs:SetVoice(voice)
self.instructmsrs:SetCulture(culture or"en-US")
self.instructmsrs:SetGender(gender or"male")
self.instructor=true
if relayunitname then
local unit=UNIT:FindByName(relayunitname)
local Coordinate=unit:GetCoordinate()
self.instructmsrs:SetCoordinate(Coordinate)
self.instructorrelayname=relayunitname
end
return self
end
function RANGE:SetRangeControl(frequency,relayunitname)
self.rangecontrolfreq=frequency or 256
self.rangecontrolrelayname=relayunitname
return self
end
function RANGE:SetInstructorRadio(frequency,relayunitname)
self.instructorfreq=frequency or 305
self.instructorrelayname=relayunitname
return self
end
function RANGE:SetSoundfilesPath(path)
self.soundpath=tostring(path or"Range Soundfiles/")
self:I(self.id..string.format("Setting sound files path to %s",self.soundpath))
return self
end
function RANGE:AddStrafePit(targetnames,boxlength,boxwidth,heading,inverseheading,goodpass,foulline)
self:F({targetnames=targetnames,boxlength=boxlength,boxwidth=boxwidth,heading=heading,inverseheading=inverseheading,goodpass=goodpass,foulline=foulline})
if type(targetnames)~="table"then
targetnames={targetnames}
end
local _targets={}
local center=nil
local ntargets=0
for _i,_name in ipairs(targetnames)do
local _isstatic=self:_CheckStatic(_name)
local unit=nil
if _isstatic==true then
self:T(self.id..string.format("Adding STATIC object %s as strafe target #%d.",_name,_i))
unit=STATIC:FindByName(_name,false)
elseif _isstatic==false then
self:T(self.id..string.format("Adding UNIT object %s as strafe target #%d.",_name,_i))
unit=UNIT:FindByName(_name)
else
local text=string.format("ERROR! Could not find ANY strafe target object with name %s.",_name)
self:E(self.id..text)
end
if unit then
table.insert(_targets,unit)
if center==nil then
center=unit
end
ntargets=ntargets+1
end
end
if ntargets==0 then
local text=string.format("ERROR! No strafe target could be found when calling RANGE:AddStrafePit() for range %s",self.rangename)
self:E(self.id..text)
return
end
local l=boxlength or RANGE.Defaults.boxlength
local w=(boxwidth or RANGE.Defaults.boxwidth)/2
local heading=heading or center:GetHeading()
if inverseheading~=nil then
if inverseheading then
heading=heading-180
end
end
if heading<0 then
heading=heading+360
end
if heading>360 then
heading=heading-360
end
goodpass=goodpass or RANGE.Defaults.goodpass
foulline=foulline or RANGE.Defaults.foulline
local Ccenter=center:GetCoordinate()
local _name=center:GetName()
local p={}
p[#p+1]=Ccenter:Translate(w,heading+90)
p[#p+1]=p[#p]:Translate(l,heading)
p[#p+1]=p[#p]:Translate(2*w,heading-90)
p[#p+1]=p[#p]:Translate(-l,heading)
local pv2={}
for i,p in ipairs(p)do
pv2[i]={x=p.x,y=p.z}
end
local _polygon=ZONE_POLYGON_BASE:New(_name,pv2)
local st={}
st.name=_name
st.polygon=_polygon
st.coordinate=Ccenter
st.goodPass=goodpass
st.targets=_targets
st.foulline=foulline
st.smokepoints=p
st.heading=heading
table.insert(self.strafeTargets,st)
local text=string.format("Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f",_name,ntargets,heading,l,w,goodpass,foulline)
self:T(self.id..text)
return self
end
function RANGE:AddStrafePitGroup(group,boxlength,boxwidth,heading,inverseheading,goodpass,foulline)
self:F({group=group,boxlength=boxlength,boxwidth=boxwidth,heading=heading,inverseheading=inverseheading,goodpass=goodpass,foulline=foulline})
if group and group:IsAlive()then
local _units=group:GetUnits()
local _names={}
for _,_unit in ipairs(_units)do
local _unit=_unit
if _unit and _unit:IsAlive()then
local _name=_unit:GetName()
table.insert(_names,_name)
end
end
self:AddStrafePit(_names,boxlength,boxwidth,heading,inverseheading,goodpass,foulline)
end
return self
end
function RANGE:AddBombingTargets(targetnames,goodhitrange,randommove)
self:F({targetnames=targetnames,goodhitrange=goodhitrange,randommove=randommove})
if type(targetnames)~="table"then
targetnames={targetnames}
end
goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange
for _,name in pairs(targetnames)do
local _isstatic=self:_CheckStatic(name)
if _isstatic==true then
local _static=STATIC:FindByName(name)
self:T2(self.id..string.format("Adding static bombing target %s with hit range %d.",name,goodhitrange,false))
self:AddBombingTargetUnit(_static,goodhitrange)
elseif _isstatic==false then
local _unit=UNIT:FindByName(name)
self:T2(self.id..string.format("Adding unit bombing target %s with hit range %d.",name,goodhitrange,randommove))
self:AddBombingTargetUnit(_unit,goodhitrange,randommove)
else
self:E(self.id..string.format("ERROR! Could not find bombing target %s.",name))
end
end
return self
end
function RANGE:AddBombingTargetUnit(unit,goodhitrange,randommove)
self:F({unit=unit,goodhitrange=goodhitrange,randommove=randommove})
local name=unit:GetName()
local _isstatic=self:_CheckStatic(name)
goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange
if randommove==nil or _isstatic==true then
randommove=false
end
if _isstatic==true then
self:I(self.id..string.format("Adding STATIC bombing target %s with good hit range %d. Random move = %s.",name,goodhitrange,tostring(randommove)))
elseif _isstatic==false then
self:I(self.id..string.format("Adding UNIT bombing target %s with good hit range %d. Random move = %s.",name,goodhitrange,tostring(randommove)))
else
self:E(self.id..string.format("ERROR! No bombing target with name %s could be found. Carefully check all UNIT and STATIC names defined in the mission editor!",name))
end
local speed=0
if _isstatic==false then
speed=self:_GetSpeed(unit)
end
local target={}
target.name=name
target.target=unit
target.goodhitrange=goodhitrange
target.move=randommove
target.speed=speed
target.coordinate=unit:GetCoordinate()
if _isstatic then
target.type=RANGE.TargetType.STATIC
else
target.type=RANGE.TargetType.UNIT
end
table.insert(self.bombingTargets,target)
return self
end
function RANGE:AddBombingTargetCoordinate(coord,name,goodhitrange)
local target={}
target.name=name or"Bomb Target"
target.target=nil
target.goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange
target.move=false
target.speed=0
target.coordinate=coord
target.type=RANGE.TargetType.COORD
table.insert(self.bombingTargets,target)
return self
end
function RANGE:AddBombingTargetGroup(group,goodhitrange,randommove)
self:F({group=group,goodhitrange=goodhitrange,randommove=randommove})
if group then
local _units=group:GetUnits()
for _,_unit in pairs(_units)do
if _unit and _unit:IsAlive()then
self:AddBombingTargetUnit(_unit,goodhitrange,randommove)
end
end
end
return self
end
function RANGE:GetFoullineDistance(namepit,namefoulline)
self:F({namepit=namepit,namefoulline=namefoulline})
local _staticpit=self:_CheckStatic(namepit)
local _staticfoul=self:_CheckStatic(namefoulline)
local pit=nil
if _staticpit==true then
pit=STATIC:FindByName(namepit,false)
elseif _staticpit==false then
pit=UNIT:FindByName(namepit)
else
self:E(self.id..string.format("ERROR! Pit object %s could not be found in GetFoullineDistance function. Check the name in the ME.",namepit))
end
local foul=nil
if _staticfoul==true then
foul=STATIC:FindByName(namefoulline,false)
elseif _staticfoul==false then
foul=UNIT:FindByName(namefoulline)
else
self:E(self.id..string.format("ERROR! Foul line object %s could not be found in GetFoullineDistance function. Check the name in the ME.",namefoulline))
end
local fouldist=0
if pit~=nil and foul~=nil then
fouldist=pit:GetCoordinate():Get2DDistance(foul:GetCoordinate())
else
self:E(self.id..string.format("ERROR! Foul line distance could not be determined. Check pit object name %s and foul line object name %s in the ME.",namepit,namefoulline))
end
self:T(self.id..string.format("Foul line distance = %.1f m.",fouldist))
return fouldist
end
function RANGE:onEvent(Event)
self:F3(Event)
if Event==nil or Event.initiator==nil then
self:T3("Skipping onEvent. Event or Event.initiator unknown.")
return true
end
if Unit.getByName(Event.initiator:getName())==nil then
self:T3("Skipping onEvent. Initiator unit name unknown.")
return true
end
local DCSiniunit=Event.initiator
local DCStgtunit=Event.target
local DCSweapon=Event.weapon
local EventData={}
local _playerunit=nil
local _playername=nil
if Event.initiator then
EventData.IniUnitName=Event.initiator:getName()
EventData.IniDCSGroup=Event.initiator:getGroup()
EventData.IniGroupName=Event.initiator:getGroup():getName()
_playerunit,_playername=self:_GetPlayerUnitAndName(EventData.IniUnitName)
end
if Event.target then
EventData.TgtUnitName=Event.target:getName()
EventData.TgtUnit=UNIT:FindByName(EventData.TgtUnitName)
end
if Event.weapon then
EventData.Weapon=Event.weapon
EventData.weapon=Event.weapon
EventData.WeaponTypeName=Event.weapon:getTypeName()
end
self:T3(self.id..string.format("EVENT: Event in onEvent with ID = %s",tostring(Event.id)))
self:T3(self.id..string.format("EVENT: Ini unit   = %s",tostring(EventData.IniUnitName)))
self:T3(self.id..string.format("EVENT: Ini group  = %s",tostring(EventData.IniGroupName)))
self:T3(self.id..string.format("EVENT: Ini player = %s",tostring(_playername)))
self:T3(self.id..string.format("EVENT: Tgt unit   = %s",tostring(EventData.TgtUnitName)))
self:T3(self.id..string.format("EVENT: Wpn type   = %s",tostring(EventData.WeaponTypeName)))
if Event.id==world.event.S_EVENT_BIRTH and _playername then
self:OnEventBirth(EventData)
end
if Event.id==world.event.S_EVENT_SHOT and _playername and Event.weapon then
self:OnEventShot(EventData)
end
if Event.id==world.event.S_EVENT_HIT and _playername and DCStgtunit then
self:OnEventHit(EventData)
end
end
function RANGE:OnEventBirth(EventData)
self:F({eventbirth=EventData})
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.id.."BIRTH: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.id.."BIRTH: group  = "..tostring(EventData.IniGroupName))
self:T3(self.id.."BIRTH: player = "..tostring(_playername))
if _unit and _playername then
local _uid=_unit:GetID()
local _group=_unit:GetGroup()
local _gid=_group:GetID()
local _callsign=_unit:GetCallsign()
local text=string.format("Player %s, callsign %s entered unit %s (UID %d) of group %s (GID %d)",_playername,_callsign,_unitName,_uid,_group:GetName(),_gid)
self:T(self.id..text)
self.strafeStatus[_uid]=nil
self:ScheduleOnce(0.1,self._AddF10Commands,self,_unitName)
self.PlayerSettings[_playername]={}
self.PlayerSettings[_playername].smokebombimpact=self.defaultsmokebomb
self.PlayerSettings[_playername].flaredirecthits=false
self.PlayerSettings[_playername].smokecolor=SMOKECOLOR.Blue
self.PlayerSettings[_playername].flarecolor=FLARECOLOR.Red
self.PlayerSettings[_playername].delaysmoke=true
self.PlayerSettings[_playername].messages=true
self.PlayerSettings[_playername].client=CLIENT:FindByName(_unitName,nil,true)
self.PlayerSettings[_playername].unitname=_unitName
self.PlayerSettings[_playername].playername=_playername
self.PlayerSettings[_playername].airframe=EventData.IniUnit:GetTypeName()
self.PlayerSettings[_playername].inzone=false
if self.planes[_uid]~=true then
self.timerCheckZone=TIMER:New(self._CheckInZone,self,EventData.IniUnitName):Start(1,1)
self.planes[_uid]=true
end
end
end
function RANGE:OnEventHit(EventData)
self:F({eventhit=EventData})
self:T3(self.id.."HIT: Ini unit   = "..tostring(EventData.IniUnitName))
self:T3(self.id.."HIT: Ini group  = "..tostring(EventData.IniGroupName))
self:T3(self.id.."HIT: Tgt target = "..tostring(EventData.TgtUnitName))
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit==nil or _playername==nil then
return
end
local _unitID=_unit:GetID()
local target=EventData.TgtUnit
local targetname=EventData.TgtUnitName
local _currentTarget=self.strafeStatus[_unitID]
if _currentTarget and target:IsAlive()then
local playerPos=_unit:GetCoordinate()
local targetPos=target:GetCoordinate()
for _,_target in pairs(_currentTarget.zone.targets)do
if _target and _target:IsAlive()and _target:GetName()==targetname then
local dist=playerPos:Get2DDistance(targetPos)
if dist>_currentTarget.zone.foulline then
_currentTarget.hits=_currentTarget.hits+1
if _unit and _playername and self.PlayerSettings[_playername].flaredirecthits then
targetPos:Flare(self.PlayerSettings[_playername].flarecolor)
end
else
if _currentTarget.pastfoulline==false and _unit and _playername then
local _d=_currentTarget.zone.foulline
local text=string.format("%s, Invalid hit!\nYou already passed foul line distance of %d m for target %s.",self:_myname(_unitName),_d,targetname)
if self.useSRS then
local ttstext=string.format("%s, Invalid hit! You already passed foul line distance of %d meters for target %s.",self:_myname(_unitName),_d,targetname)
self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2)
end
self:_DisplayMessageToGroup(_unit,text)
self:T2(self.id..text)
_currentTarget.pastfoulline=true
end
end
end
end
end
for _,_bombtarget in pairs(self.bombingTargets)do
local _target=_bombtarget.target
if _target and _target:IsAlive()and _bombtarget.name==targetname then
if _unit and _playername then
if self.PlayerSettings[_playername].flaredirecthits then
local targetPos=_target:GetCoordinate()
targetPos:Flare(self.PlayerSettings[_playername].flarecolor)
end
end
end
end
end
function RANGE:_TrackWeapon(weapon)
end
function RANGE:OnEventShot(EventData)
self:F({eventshot=EventData})
if EventData.Weapon==nil then
return
end
if EventData.IniDCSUnit==nil then
return
end
if EventData.IniPlayerName==nil then
return
end
local _weapon=EventData.Weapon:getTypeName()
local _weaponStrArray=UTILS.Split(_weapon,"%.")
local _weaponName=_weaponStrArray[#_weaponStrArray]
local desc=EventData.Weapon:getDesc()
local weaponcategory=desc.category
self:T(self.id.."EVENT SHOT: Range "..self.rangename)
self:T(self.id.."EVENT SHOT: Ini unit    = "..EventData.IniUnitName)
self:T(self.id.."EVENT SHOT: Ini group   = "..EventData.IniGroupName)
self:T(self.id.."EVENT SHOT: Weapon type = ".._weapon)
self:T(self.id.."EVENT SHOT: Weapon name = ".._weaponName)
self:T(self.id.."EVENT SHOT: Weapon cate = "..weaponcategory)
local _bombs=weaponcategory==Weapon.Category.BOMB
local _rockets=weaponcategory==Weapon.Category.ROCKET
local _missiles=weaponcategory==Weapon.Category.MISSILE
local _track=(_bombs and self.trackbombs)or(_rockets and self.trackrockets)or(_missiles and self.trackmissiles)
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
local attackHdg=_unit:GetHeading()
local attackAlt=_unit:GetHeight()
local attackVel=_unit:GetVelocityKNOTS()
local dPR=self.BombtrackThreshold*2
if _unit and _playername then
dPR=_unit:GetCoordinate():Get2DDistance(self.location)
self:T(self.id..string.format("Range %s, player %s, player-range distance = %d km.",self.rangename,_playername,dPR/1000))
end
if _track and dPR<=self.BombtrackThreshold and _unit and _playername then
local playerData=self.PlayerSettings[_playername]
self:T(self.id..string.format("RANGE %s: Tracking %s - %s.",self.rangename,_weapon,EventData.weapon:getName()))
local _lastBombPos={x=0,y=0,z=0}
local function trackBomb(_ordnance)
local _status,_bombPos=pcall(function()
return _ordnance:getPoint()
end)
self:T2(self.id..string.format("Range %s: Bomb still in air: %s",self.rangename,tostring(_status)))
if _status then
_lastBombPos={x=_bombPos.x,y=_bombPos.y,z=_bombPos.z}
return timer.getTime()+self.dtBombtrack
else
local _closetTarget=nil
local _distance=nil
local _closeCoord=nil
local _hitquality="POOR"
local _callsign=self:_myname(_unitName)
local impactcoord=COORDINATE:NewFromVec3(_lastBombPos)
local insidezone=self.rangezone:IsCoordinateInZone(impactcoord)
if self.Debug then
impactcoord:MarkToAll("Bomb impact point")
end
if playerData.smokebombimpact and insidezone then
if playerData.delaysmoke then
timer.scheduleFunction(self._DelayedSmoke,{coord=impactcoord,color=playerData.smokecolor},timer.getTime()+self.TdelaySmoke)
else
impactcoord:Smoke(playerData.smokecolor)
end
end
for _,_bombtarget in pairs(self.bombingTargets)do
local bombtarget=_bombtarget
local targetcoord=self:_GetBombTargetCoordinate(_bombtarget)
if targetcoord then
local _temp=impactcoord:Get2DDistance(targetcoord)
if _distance==nil or _temp<_distance then
_distance=_temp
_closetTarget=bombtarget
_closeCoord=targetcoord
if _distance<=1.53 then
_hitquality="SHACK"
elseif _distance<=0.5*bombtarget.goodhitrange then
_hitquality="EXCELLENT"
elseif _distance<=bombtarget.goodhitrange then
_hitquality="GOOD"
elseif _distance<=2*bombtarget.goodhitrange then
_hitquality="INEFFECTIVE"
else
_hitquality="POOR"
end
end
end
end
if _distance and _distance<=self.scorebombdistance then
if not self.bombPlayerResults[_playername]then
self.bombPlayerResults[_playername]={}
end
local _results=self.bombPlayerResults[_playername]
local result={}
result.command=SOCKET.DataType.BOMBRESULT
result.name=_closetTarget.name or"unknown"
result.distance=_distance
result.radial=_closeCoord:HeadingTo(impactcoord)
result.weapon=_weaponName or"unknown"
result.quality=_hitquality
result.player=playerData.playername
result.time=timer.getAbsTime()
result.clock=UTILS.SecondsToClock(result.time,true)
result.midate=UTILS.GetDCSMissionDate()
result.theatre=env.mission.theatre
result.airframe=playerData.airframe
result.roundsFired=0
result.roundsHit=0
result.roundsQuality="N/A"
result.rangename=self.rangename
result.attackHdg=attackHdg
result.attackVel=attackVel
result.attackAlt=attackAlt
table.insert(_results,result)
self:Impact(result,playerData)
elseif insidezone then
local _message=string.format("%s, weapon impacted too far from nearest range target (>%.1f km). No score!",_callsign,self.scorebombdistance/1000)
if self.useSRS then
local ttstext=string.format("%s, weapon impacted too far from nearest range target, mor than %.1f kilometer. No score!",_callsign,self.scorebombdistance/1000)
self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2)
end
self:_DisplayMessageToGroup(_unit,_message,nil,false)
if self.rangecontrol then
if self.useSRS then
self.controlsrsQ:NewTransmission(_message,nil,self.controlmsrs,nil,1)
else
self.rangecontrol:NewTransmission(RANGE.Sound.RCWeaponImpactedTooFar.filename,RANGE.Sound.RCWeaponImpactedTooFar.duration,self.soundpath,nil,nil,_message,self.subduration)
end
end
else
self:T(self.id.."Weapon impacted outside range zone.")
end
self:T(self.id..string.format("Range %s, player %s: Terminating bomb track timer.",self.rangename,_playername))
return nil
end
end
self:T(self.id..string.format("Range %s, player %s: Tracking of weapon starts in 0.1 seconds.",self.rangename,_playername))
timer.scheduleFunction(trackBomb,EventData.weapon,timer.getTime()+0.1)
end
end
function RANGE:onafterStatus(From,Event,To)
if self.verbose>0 then
local fsmstate=self:GetState()
local text=string.format("Range status: %s",fsmstate)
if self.instructor then
local alive="N/A"
if self.instructorrelayname then
local relay=UNIT:FindByName(self.instructorrelayname)
if relay then
alive=tostring(relay:IsAlive())
end
end
text=text..string.format(", Instructor %.3f MHz (Relay=%s alive=%s)",self.instructorfreq,tostring(self.instructorrelayname),alive)
end
if self.rangecontrol then
local alive="N/A"
if self.rangecontrolrelayname then
local relay=UNIT:FindByName(self.rangecontrolrelayname)
if relay then
alive=tostring(relay:IsAlive())
end
end
text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)",self.rangecontrolfreq,tostring(self.rangecontrolrelayname),alive)
end
self:I(self.id..text)
end
self:_CheckPlayers()
self:__Status(-10)
end
function RANGE:onafterEnterRange(From,Event,To,player)
if self.instructor and self.rangecontrol then
if self.useSRS then
local text=string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f MHz",self.rangecontrolfreq)
local ttstext=string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f mega hertz.",self.rangecontrolfreq)
local group=player.client:GetGroup()
self.instructsrsQ:NewTransmission(ttstext,nil,self.instructmsrs,nil,1,{group},text,10)
else
local RF=UTILS.Split(string.format("%.3f",self.rangecontrolfreq),".")
self.instructor:NewTransmission(RANGE.Sound.IREnterRange.filename,RANGE.Sound.IREnterRange.duration,self.soundpath)
self.instructor:Number2Transmission(RF[1])
if tonumber(RF[2])>0 then
self.instructor:NewTransmission(RANGE.Sound.IRDecimal.filename,RANGE.Sound.IRDecimal.duration,self.soundpath)
self.instructor:Number2Transmission(RF[2])
end
self.instructor:NewTransmission(RANGE.Sound.IRMegaHertz.filename,RANGE.Sound.IRMegaHertz.duration,self.soundpath)
end
end
end
function RANGE:onafterExitRange(From,Event,To,player)
if self.instructor then
if self.useSRS then
local text="You left the bombing range zone. Have a nice day!"
local group=player.client:GetGroup()
self.instructsrsQ:NewTransmission(text,nil,self.instructmsrs,nil,1,{group},text,10)
else
self.instructor:NewTransmission(RANGE.Sound.IRExitRange.filename,RANGE.Sound.IRExitRange.duration,self.soundpath)
end
end
end
function RANGE:onafterImpact(From,Event,To,result,player)
local targetname=nil
if#self.bombingTargets>1 then
targetname=result.name
end
local text=string.format("%s, impact %03d° for %d ft (%d m)",player.playername,result.radial,UTILS.MetersToFeet(result.distance),result.distance)
if targetname then
text=text..string.format(" from bulls of target %s.",targetname)
else
text=text.."."
end
text=text..string.format(" %s hit.",result.quality)
if self.rangecontrol then
if self.useSRS then
local group=player.client:GetGroup()
self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1,{group},text,10)
else
self.rangecontrol:NewTransmission(RANGE.Sound.RCImpact.filename,RANGE.Sound.RCImpact.duration,self.soundpath,nil,nil,text,self.subduration)
self.rangecontrol:Number2Transmission(string.format("%03d",result.radial),nil,0.1)
self.rangecontrol:NewTransmission(RANGE.Sound.RCDegrees.filename,RANGE.Sound.RCDegrees.duration,self.soundpath)
self.rangecontrol:NewTransmission(RANGE.Sound.RCFor.filename,RANGE.Sound.RCFor.duration,self.soundpath)
self.rangecontrol:Number2Transmission(string.format("%d",UTILS.MetersToFeet(result.distance)))
self.rangecontrol:NewTransmission(RANGE.Sound.RCFeet.filename,RANGE.Sound.RCFeet.duration,self.soundpath)
if result.quality=="POOR"then
self.rangecontrol:NewTransmission(RANGE.Sound.RCPoorHit.filename,RANGE.Sound.RCPoorHit.duration,self.soundpath,nil,0.5)
elseif result.quality=="INEFFECTIVE"then
self.rangecontrol:NewTransmission(RANGE.Sound.RCIneffectiveHit.filename,RANGE.Sound.RCIneffectiveHit.duration,self.soundpath,nil,0.5)
elseif result.quality=="GOOD"then
self.rangecontrol:NewTransmission(RANGE.Sound.RCGoodHit.filename,RANGE.Sound.RCGoodHit.duration,self.soundpath,nil,0.5)
elseif result.quality=="EXCELLENT"then
self.rangecontrol:NewTransmission(RANGE.Sound.RCExcellentHit.filename,RANGE.Sound.RCExcellentHit.duration,self.soundpath,nil,0.5)
end
end
end
if player.unitname and not self.useSRS then
local unit=UNIT:FindByName(player.unitname)
self:_DisplayMessageToGroup(unit,text,nil,true)
self:T(self.id..text)
end
if self.autosave then
self:Save()
end
if self.funkmanSocket then
self.funkmanSocket:SendTable(result)
end
end
function RANGE:onafterStrafeResult(From,Event,To,player,result)
if self.funkmanSocket then
self.funkmanSocket:SendTable(result)
end
end
function RANGE:onbeforeSave(From,Event,To)
if io and lfs then
return true
else
self:E(self.id..string.format("WARNING: io and/or lfs not desanitized. Cannot save player results."))
return false
end
end
function RANGE:onafterSave(From,Event,To)
local function _savefile(filename,data)
local f=io.open(filename,"wb")
if f then
f:write(data)
f:close()
self:I(self.id..string.format("Saving player results to file %s",tostring(filename)))
else
self:E(self.id..string.format("ERROR: Could not save results to file %s",tostring(filename)))
end
end
local path=lfs.writedir()..[[Logs\]]
local filename=path..string.format("RANGE-%s_BombingResults.csv",self.rangename)
local scores="Name,Pass,Target,Distance,Radial,Quality,Weapon,Airframe,Mission Time"
for playername,results in pairs(self.bombPlayerResults)do
for i,_result in pairs(results)do
local result=_result
local distance=result.distance
local weapon=result.weapon
local target=result.name
local radial=result.radial
local quality=result.quality
local time=UTILS.SecondsToClock(result.time,true)
local airframe=result.airframe
local date="n/a"
if os then
date=os.date()
end
scores=scores..string.format("\n%s,%d,%s,%.2f,%03d,%s,%s,%s,%s,%s",playername,i,target,distance,radial,quality,weapon,airframe,time,date)
end
end
_savefile(filename,scores)
end
function RANGE:onbeforeLoad(From,Event,To)
if io and lfs then
return true
else
self:E(self.id..string.format("WARNING: io and/or lfs not desanitized. Cannot load player results."))
return false
end
end
function RANGE:onafterLoad(From,Event,To)
local function _loadfile(filename)
local f=io.open(filename,"rb")
if f then
local data=f:read("*all")
f:close()
return data
else
self:E(self.id..string.format("WARNING: Could not load player results from file %s. File might not exist just yet.",tostring(filename)))
return nil
end
end
local path=lfs.writedir()..[[Logs\]]
local filename=path..string.format("RANGE-%s_BombingResults.csv",self.rangename)
local text=string.format("Loading player bomb results from file %s",filename)
self:I(self.id..text)
local data=_loadfile(filename)
if data then
local results=UTILS.Split(data,"\n")
table.remove(results,1)
self.bombPlayerResults={}
for _,_result in pairs(results)do
local resultdata=UTILS.Split(_result,",")
local result={}
local playername=resultdata[1]
result.player=playername
result.name=tostring(resultdata[3])
result.distance=tonumber(resultdata[4])
result.radial=tonumber(resultdata[5])
result.quality=tostring(resultdata[6])
result.weapon=tostring(resultdata[7])
result.airframe=tostring(resultdata[8])
result.time=UTILS.ClockToSeconds(resultdata[9]or"00:00:00")
result.date=resultdata[10]or"n/a"
self.bombPlayerResults[playername]=self.bombPlayerResults[playername]or{}
table.insert(self.bombPlayerResults[playername],result)
end
end
end
function RANGE:_SaveTargetSheet(_playername,result)
local function _savefile(filename,data)
local f=io.open(filename,"wb")
if f then
f:write(data)
f:close()
else
env.info("RANGEBOSS EDIT - could not save target sheet to file")
end
end
local path=self.targetpath
if lfs then
path=path or lfs.writedir()..[[Logs\]]
end
local filename=nil
for i=1,9999 do
if self.targetprefix then
filename=string.format("%s_%s-%04d.csv",self.targetprefix,result.airframe,i)
else
local name=UTILS.ReplaceIllegalCharacters(_playername,"_")
filename=string.format("RANGERESULTS-%s_Targetsheet-%s-%04d.csv",self.rangename,name,i)
end
if path~=nil then
filename=path.."\\"..filename
end
local _exists=UTILS.FileExists(filename)
if not _exists then
break
end
end
local data="Name,Target,Rounds Fired,Rounds Hit,Rounds Quality,Airframe,Mission Time,OS Time\n"
local target=result.name
local airframe=result.airframe
local roundsFired=result.roundsFired
local roundsHit=result.roundsHit
local strafeResult=result.roundsQuality
local time=UTILS.SecondsToClock(result.time)
local date="n/a"
if os then
date=os.date()
end
data=data..string.format("%s,%s,%d,%d,%s,%s,%s,%s",_playername,target,roundsFired,roundsHit,strafeResult,airframe,time,date)
_savefile(filename,data)
end
function RANGE._DelayedSmoke(_args)
_args.coord:Smoke(_args.color)
end
function RANGE:_DisplayMyStrafePitResults(_unitName)
self:F(_unitName)
local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local _message=string.format("My Top %d Strafe Pit Results:\n",self.ndisplayresult)
local _results=self.strafePlayerResults[_playername]
if _results==nil then
_message=string.format("%s: No Score yet.",_playername)
else
local _sort=function(a,b)
return a.roundsHit>b.roundsHit
end
table.sort(_results,_sort)
local _bestMsg=""
local _count=1
for _,_result in pairs(_results)do
local result=_result
_message=_message..string.format("\n[%d] Hits %d - %s - %s",_count,result.roundsHit,result.name,result.roundsQuality)
if _bestMsg==""then
_bestMsg=string.format("Hits %d - %s - %s",result.roundsHit,result.name,result.roundsQuality)
end
if _count==self.ndisplayresult then
break
end
_count=_count+1
end
_message=_message.."\n\nBEST: ".._bestMsg
end
self:_DisplayMessageToGroup(_unit,_message,nil,true,true,_multiplayer)
end
end
function RANGE:_DisplayStrafePitResults(_unitName)
self:F(_unitName)
local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local _playerResults={}
local _message=string.format("Strafe Pit Results - Top %d Players:\n",self.ndisplayresult)
for _playerName,_results in pairs(self.strafePlayerResults)do
local _best=nil
for _,_result in pairs(_results)do
if _best==nil or _result.roundsHit>_best.roundsHit then
_best=_result
end
end
if _best~=nil then
local text=string.format("%s: Hits %i - %s - %s",_playerName,_best.roundsHit,_best.name,_best.roundsQuality)
table.insert(_playerResults,{msg=text,hits=_best.roundsHit})
end
end
local _sort=function(a,b)
return a.hits>b.hits
end
table.sort(_playerResults,_sort)
for _i=1,math.min(#_playerResults,self.ndisplayresult)do
_message=_message..string.format("\n[%d] %s",_i,_playerResults[_i].msg)
end
if#_playerResults<1 then
_message=_message.."No player scored yet."
end
self:_DisplayMessageToGroup(_unit,_message,nil,true,true,_multiplayer)
end
end
function RANGE:_DisplayMyBombingResults(_unitName)
self:F(_unitName)
local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local _message=string.format("My Top %d Bombing Results:\n",self.ndisplayresult)
local _results=self.bombPlayerResults[_playername]
if _results==nil then
_message=_playername..": No Score yet."
else
local _sort=function(a,b)
return a.distance<b.distance
end
table.sort(_results,_sort)
local _bestMsg=""
for i,_result in pairs(_results)do
local result=_result
_message=_message.."\n"..string.format("[%d] %d m %03d° - %s - %s - %s hit",i,result.distance,result.radial,result.name,result.weapon,result.quality)
if _bestMsg==""then
_bestMsg=string.format("%d m %03d° - %s - %s - %s hit",result.distance,result.radial,result.name,result.weapon,result.quality)
end
if i==self.ndisplayresult then
break
end
end
_message=_message.."\n\nBEST: ".._bestMsg
end
self:_DisplayMessageToGroup(_unit,_message,nil,true,true,_multiplayer)
end
end
function RANGE:_DisplayBombingResults(_unitName)
self:F(_unitName)
local _playerResults={}
local _unit,_player,_multiplayer=self:_GetPlayerUnitAndName(_unitName)
if _unit and _player then
local _message=string.format("Bombing Results - Top %d Players:\n",self.ndisplayresult)
for _playerName,_results in pairs(self.bombPlayerResults)do
local _best=nil
for _,_result in pairs(_results)do
if _best==nil or _result.distance<_best.distance then
_best=_result
end
end
if _best~=nil then
local bestres=string.format("%s: %d m - %s - %s - %s hit",_playerName,_best.distance,_best.name,_best.weapon,_best.quality)
table.insert(_playerResults,{msg=bestres,distance=_best.distance})
end
end
local _sort=function(a,b)
return a.distance<b.distance
end
table.sort(_playerResults,_sort)
for _i=1,math.min(#_playerResults,self.ndisplayresult)do
_message=_message..string.format("\n[%d] %s",_i,_playerResults[_i].msg)
end
if#_playerResults<1 then
_message=_message.."No player scored yet."
end
self:_DisplayMessageToGroup(_unit,_message,nil,true,true,_multiplayer)
end
end
function RANGE:_DisplayRangeInfo(_unitname)
self:F(_unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
self:I(playername)
local text=""
local coord=unit:GetCoordinate()
if self.location then
local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS
local position=self.location
local bulls=position:ToStringBULLS(unit:GetCoalition(),settings)
local lldms=position:ToStringLLDMS(settings)
local llddm=position:ToStringLLDDM(settings)
local rangealt=position:GetLandHeight()
local vec3=coord:GetDirectionVec3(position)
local angle=coord:GetAngleDegrees(vec3)
local range=coord:Get2DDistance(position)
local Bs=string.format('%03d°',angle)
local texthit
if self.PlayerSettings[playername].flaredirecthits then
texthit=string.format("Flare direct hits: ON (flare color %s)\n",self:_flarecolor2text(self.PlayerSettings[playername].flarecolor))
else
texthit=string.format("Flare direct hits: OFF\n")
end
local textbomb
if self.PlayerSettings[playername].smokebombimpact then
textbomb=string.format("Smoke bomb impact points: ON (smoke color %s)\n",self:_smokecolor2text(self.PlayerSettings[playername].smokecolor))
else
textbomb=string.format("Smoke bomb impact points: OFF\n")
end
local textdelay
if self.PlayerSettings[playername].delaysmoke then
textdelay=string.format("Smoke bomb delay: ON (delay %.1f seconds)",self.TdelaySmoke)
else
textdelay=string.format("Smoke bomb delay: OFF")
end
local trange=string.format("%.1f km",range/1000)
local trangealt=string.format("%d m",rangealt)
local tstrafemaxalt=string.format("%d m",self.strafemaxalt)
if settings:IsImperial()then
trange=string.format("%.1f NM",UTILS.MetersToNM(range))
trangealt=string.format("%d feet",UTILS.MetersToFeet(rangealt))
tstrafemaxalt=string.format("%d feet",UTILS.MetersToFeet(self.strafemaxalt))
end
text=text..string.format("Information on %s:\n",self.rangename)
text=text..string.format("-------------------------------------------------------\n")
text=text..string.format("Bearing %s, Range %s\n",Bs,trange)
text=text..string.format("%s\n",bulls)
text=text..string.format("%s\n",lldms)
text=text..string.format("%s\n",llddm)
text=text..string.format("Altitude ASL: %s\n",trangealt)
text=text..string.format("Max strafing alt AGL: %s\n",tstrafemaxalt)
text=text..string.format("# of strafe targets: %d\n",self.nstrafetargets)
text=text..string.format("# of bomb targets: %d\n",self.nbombtargets)
if self.instructor then
local alive="N/A"
if self.instructorrelayname then
local relay=UNIT:FindByName(self.instructorrelayname)
if relay then
alive=relay:IsAlive()and"ok"or"N/A"
end
end
text=text..string.format("Instructor %.3f MHz (Relay=%s)\n",self.instructorfreq,alive)
end
if self.rangecontrol then
local alive="N/A"
if self.rangecontrolrelayname then
local relay=UNIT:FindByName(self.rangecontrolrelayname)
if relay then
alive=tostring(relay:IsAlive())
alive=relay:IsAlive()and"ok"or"N/A"
end
end
text=text..string.format("Control %.3f MHz (Relay=%s)\n",self.rangecontrolfreq,alive)
end
text=text..texthit
text=text..textbomb
text=text..textdelay
self:_DisplayMessageToGroup(unit,text,nil,true,true,_multiplayer)
self:T2(self.id..text)
end
end
end
function RANGE:_DisplayBombTargets(_unitname)
self:F(_unitname)
local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname)
if _unit and _playername then
local _settings=_DATABASE:GetPlayerSettings(_playername)or _SETTINGS
local _text="Bomb Target Locations:"
for _,_bombtarget in pairs(self.bombingTargets)do
local bombtarget=_bombtarget
local coord=self:_GetBombTargetCoordinate(bombtarget)
if coord then
local elevation=coord:GetLandHeight()
local eltxt=string.format("%d m",elevation)
if not _settings:IsMetric()then
elevation=UTILS.MetersToFeet(elevation)
eltxt=string.format("%d ft",elevation)
end
local ca2g=coord:ToStringA2G(_unit,_settings)
_text=_text..string.format("\n- %s:\n%s @ %s",bombtarget.name or"unknown",ca2g,eltxt)
end
end
self:_DisplayMessageToGroup(_unit,_text,120,true,true,_multiplayer)
end
end
function RANGE:_DisplayStrafePits(_unitname)
self:F(_unitname)
local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname)
if _unit and _playername then
local _settings=_DATABASE:GetPlayerSettings(_playername)or _SETTINGS
local _text="Strafe Target Locations:"
for _,_strafepit in pairs(self.strafeTargets)do
local _target=_strafepit
local coord=_strafepit.coordinate
local heading=_strafepit.heading
if heading>180 then
heading=heading-180
else
heading=heading+180
end
local mycoord=coord:ToStringA2G(_unit,_settings)
_text=_text..string.format("\n- %s: heading %03d°\n%s",_strafepit.name,heading,mycoord)
end
self:_DisplayMessageToGroup(_unit,_text,nil,true,true,_multiplayer)
end
end
function RANGE:_DisplayRangeWeather(_unitname)
self:F(_unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local text=""
local coord=unit:GetCoordinate()
if self.location then
local position=self.location
local T=position:GetTemperature()
local P=position:GetPressure()
local Wd,Ws=position:GetWind()
local Bn,Bd=UTILS.BeaufortScale(Ws)
local WD=string.format('%03d°',Wd)
local Ts=string.format("%d°C",T)
local hPa2inHg=0.0295299830714
local hPa2mmHg=0.7500615613030
local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS
local tT=string.format("%d°C",T)
local tW=string.format("%.1f m/s",Ws)
local tP=string.format("%.1f mmHg",P*hPa2mmHg)
if settings:IsImperial()then
tW=string.format("%.1f knots",UTILS.MpsToKnots(Ws))
tP=string.format("%.2f inHg",P*hPa2inHg)
end
text=text..string.format("Weather Report at %s:\n",self.rangename)
text=text..string.format("--------------------------------------------------\n")
text=text..string.format("Temperature %s\n",tT)
text=text..string.format("Wind from %s at %s (%s)\n",WD,tW,Bd)
text=text..string.format("QFE %.1f hPa = %s",P,tP)
else
text=string.format("No range location defined for range %s.",self.rangename)
end
self:_DisplayMessageToGroup(unit,text,nil,true,true,_multiplayer)
self:T2(self.id..text)
else
self:T(self.id..string.format("ERROR! Could not find player unit in RangeInfo! Name = %s",_unitname))
end
end
function RANGE:_CheckPlayers()
for playername,_playersettings in pairs(self.PlayerSettings)do
local playersettings=_playersettings
local unitname=playersettings.unitname
local unit=UNIT:FindByName(unitname)
if unit and unit:IsAlive()then
if unit:IsInZone(self.rangezone)then
if not playersettings.inzone then
playersettings.inzone=true
self:EnterRange(playersettings)
end
else
if playersettings.inzone==true then
playersettings.inzone=false
self:ExitRange(playersettings)
end
end
end
end
end
function RANGE:_CheckInZone(_unitName)
self:F2(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
local unitheading=0
if _unit and _playername then
local playerData=self.PlayerSettings[_playername]
local function checkme(targetheading,_zone)
local zone=_zone
local unitheading=_unit:GetHeading()
local pitheading=targetheading-180
local deltaheading=unitheading-pitheading
local towardspit=math.abs(deltaheading)<=90 or math.abs(deltaheading-360)<=90
if towardspit then
local vec3=_unit:GetVec3()
local vec2={x=vec3.x,y=vec3.z}
local landheight=land.getHeight(vec2)
local unitalt=vec3.y-landheight
if unitalt<=self.strafemaxalt then
local unitinzone=zone:IsVec2InZone(vec2)
return unitinzone
end
end
return false
end
local _unitID=_unit:GetID()
local _currentStrafeRun=self.strafeStatus[_unitID]
if _currentStrafeRun then
local zone=_currentStrafeRun.zone.polygon
local unitinzone=checkme(_currentStrafeRun.zone.heading,zone)
if unitinzone then
_currentStrafeRun.time=_currentStrafeRun.time+1
else
_currentStrafeRun.time=_currentStrafeRun.time+1
if _currentStrafeRun.time<=3 then
self.strafeStatus[_unitID]=nil
local _msg=string.format("%s left strafing zone %s too quickly. No Score.",_playername,_currentStrafeRun.zone.name)
self:_DisplayMessageToGroup(_unit,_msg,nil,true)
if self.rangecontrol then
if self.useSRS then
local group=_unit:GetGroup()
local text="You left the strafing zone too quickly! No score!"
self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1)
else
self.rangecontrol:NewTransmission(RANGE.Sound.RCLeftStrafePitTooQuickly.filename,RANGE.Sound.RCLeftStrafePitTooQuickly.duration,self.soundpath)
end
end
else
local _ammo=self:_GetAmmo(_unitName)
local _result=self.strafeStatus[_unitID]
local _sound=nil
local shots=_result.ammo-_ammo
local accur=0
if shots>0 then
accur=_result.hits/shots*100
if accur>100 then
accur=100
end
end
local resulttext=""
if _result.pastfoulline==true then
resulttext="* INVALID - PASSED FOUL LINE *"
_sound=RANGE.Sound.RCPoorPass
else
if accur>=90 then
resulttext="DEADEYE PASS"
_sound=RANGE.Sound.RCExcellentPass
elseif accur>=75 then
resulttext="EXCELLENT PASS"
_sound=RANGE.Sound.RCExcellentPass
elseif accur>=50 then
resulttext="GOOD PASS"
_sound=RANGE.Sound.RCGoodPass
elseif accur>=25 then
resulttext="INEFFECTIVE PASS"
_sound=RANGE.Sound.RCIneffectivePass
else
resulttext="POOR PASS"
_sound=RANGE.Sound.RCPoorPass
end
end
local _text=string.format("%s, hits on target %s: %d",self:_myname(_unitName),_result.zone.name,_result.hits)
local ttstext=string.format("%s, hits on target %s: %d.",self:_myname(_unitName),_result.zone.name,_result.hits)
if shots and accur then
_text=_text..string.format("\nTotal rounds fired %d. Accuracy %.1f %%.",shots,accur)
ttstext=ttstext..string.format(". Total rounds fired %d. Accuracy %.1f percent.",shots,accur)
end
_text=_text..string.format("\n%s",resulttext)
ttstext=ttstext..string.format(" %s",resulttext)
self:_DisplayMessageToGroup(_unit,_text)
local result={}
result.command=SOCKET.DataType.STRAFERESULT
result.player=_playername
result.name=_result.zone.name or"unknown"
result.time=timer.getAbsTime()
result.clock=UTILS.SecondsToClock(result.time)
result.midate=UTILS.GetDCSMissionDate()
result.theatre=env.mission.theatre
result.roundsFired=shots
result.roundsHit=_result.hits
result.roundsQuality=resulttext
result.strafeAccuracy=accur
result.rangename=self.rangename
result.airframe=playerData.airframe
result.invalid=_result.pastfoulline
self:StrafeResult(playerData,result)
if playerData and playerData.targeton and self.targetsheet then
self:_SaveTargetSheet(_playername,result)
end
if self.rangecontrol then
if self.useSRS then
self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,1)
else
self.rangecontrol:NewTransmission(RANGE.Sound.RCHitsOnTarget.filename,RANGE.Sound.RCHitsOnTarget.duration,self.soundpath)
self.rangecontrol:Number2Transmission(string.format("%d",_result.hits))
if shots and accur then
self.rangecontrol:NewTransmission(RANGE.Sound.RCTotalRoundsFired.filename,RANGE.Sound.RCTotalRoundsFired.duration,self.soundpath,nil,0.2)
self.rangecontrol:Number2Transmission(string.format("%d",shots),nil,0.2)
self.rangecontrol:NewTransmission(RANGE.Sound.RCAccuracy.filename,RANGE.Sound.RCAccuracy.duration,self.soundpath,nil,0.2)
self.rangecontrol:Number2Transmission(string.format("%d",UTILS.Round(accur,0)))
self.rangecontrol:NewTransmission(RANGE.Sound.RCPercent.filename,RANGE.Sound.RCPercent.duration,self.soundpath)
end
self.rangecontrol:NewTransmission(_sound.filename,_sound.duration,self.soundpath,nil,0.5)
end
end
self.strafeStatus[_unitID]=nil
local _stats=self.strafePlayerResults[_playername]or{}
table.insert(_stats,result)
self.strafePlayerResults[_playername]=_stats
end
end
else
for _,_targetZone in pairs(self.strafeTargets)do
local target=_targetZone
local zone=target.polygon
local unitinzone=checkme(target.heading,zone)
if unitinzone then
local _ammo=self:_GetAmmo(_unitName)
self.strafeStatus[_unitID]={hits=0,zone=target,time=1,ammo=_ammo,pastfoulline=false}
local _msg=string.format("%s, rolling in on strafe pit %s.",self:_myname(_unitName),target.name)
if self.rangecontrol then
if self.useSRS then
self.controlsrsQ:NewTransmission(_msg,nil,self.controlmsrs,nil,1)
else
self.rangecontrol:NewTransmission(RANGE.Sound.RCRollingInOnStrafeTarget.filename,RANGE.Sound.RCRollingInOnStrafeTarget.duration,self.soundpath)
end
end
self:_DisplayMessageToGroup(_unit,_msg,10,true)
self:RollingIn(playerData,target)
break
end
end
end
end
end
function RANGE:_AddF10Commands(_unitName)
self:F(_unitName)
local _unit,playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and playername then
local group=_unit:GetGroup()
local _gid=group:GetID()
if group and _gid then
if not self.MenuAddedTo[_gid]then
self.MenuAddedTo[_gid]=true
local _rangePath=nil
if RANGE.MenuF10Root then
_rangePath=MENU_GROUP:New(group,"On the Range")
else
if RANGE.MenuF10[_gid]==nil then
RANGE.MenuF10[_gid]=MENU_GROUP:New(group,"On the Range")
end
_rangePath=MENU_GROUP:New(group,self.rangename,RANGE.MenuF10[_gid])
end
local _statsPath=MENU_GROUP:New(group,"Statistics",_rangePath)
local _markPath=MENU_GROUP:New(group,"Mark Targets",_rangePath)
local _settingsPath=MENU_GROUP:New(group,"My Settings",_rangePath)
local _infoPath=MENU_GROUP:New(group,"Range Info",_rangePath)
local _mysmokePath=MENU_GROUP:New(group,"Smoke Color",_settingsPath)
local _myflarePath=MENU_GROUP:New(group,"Flare Color",_settingsPath)
local _MoMap=MENU_GROUP_COMMAND:New(group,"Mark On Map",_markPath,self._MarkTargetsOnMap,self,_unitName)
local _IllRng=MENU_GROUP_COMMAND:New(group,"Illuminate Range",_markPath,self._IlluminateBombTargets,self,_unitName)
local _SSpit=MENU_GROUP_COMMAND:New(group,"Smoke Strafe Pits",_markPath,self._SmokeStrafeTargetBoxes,self,_unitName)
local _SStgts=MENU_GROUP_COMMAND:New(group,"Smoke Strafe Tgts",_markPath,self._SmokeStrafeTargets,self,_unitName)
local _SBtgts=MENU_GROUP_COMMAND:New(group,"Smoke Bomb Tgts",_markPath,self._SmokeBombTargets,self,_unitName)
local _AllSR=MENU_GROUP_COMMAND:New(group,"All Strafe Results",_statsPath,self._DisplayStrafePitResults,self,_unitName)
local _AllBR=MENU_GROUP_COMMAND:New(group,"All Bombing Results",_statsPath,self._DisplayBombingResults,self,_unitName)
local _MySR=MENU_GROUP_COMMAND:New(group,"My Strafe Results",_statsPath,self._DisplayMyStrafePitResults,self,_unitName)
local _MyBR=MENU_GROUP_COMMAND:New(group,"My Bomb Results",_statsPath,self._DisplayMyBombingResults,self,_unitName)
local _ResetST=MENU_GROUP_COMMAND:New(group,"Reset All Stats",_statsPath,self._ResetRangeStats,self,_unitName)
local _BlueSM=MENU_GROUP_COMMAND:New(group,"Blue Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Blue)
local _GrSM=MENU_GROUP_COMMAND:New(group,"Green Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Green)
local _OrSM=MENU_GROUP_COMMAND:New(group,"Orange Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Orange)
local _ReSM=MENU_GROUP_COMMAND:New(group,"Red Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Red)
local _WhSm=MENU_GROUP_COMMAND:New(group,"White Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.White)
local _GrFl=MENU_GROUP_COMMAND:New(group,"Green Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Green)
local _ReFl=MENU_GROUP_COMMAND:New(group,"Red Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Red)
local _WhFl=MENU_GROUP_COMMAND:New(group,"White Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.White)
local _YeFl=MENU_GROUP_COMMAND:New(group,"Yellow Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Yellow)
local _SmDe=MENU_GROUP_COMMAND:New(group,"Smoke Delay On/Off",_settingsPath,self._SmokeBombDelayOnOff,self,_unitName)
local _SmIm=MENU_GROUP_COMMAND:New(group,"Smoke Impact On/Off",_settingsPath,self._SmokeBombImpactOnOff,self,_unitName)
local _FlHi=MENU_GROUP_COMMAND:New(group,"Flare Hits On/Off",_settingsPath,self._FlareDirectHitsOnOff,self,_unitName)
local _AlMeA=MENU_GROUP_COMMAND:New(group,"All Messages On/Off",_settingsPath,self._MessagesToPlayerOnOff,self,_unitName)
local _TrpSh=MENU_GROUP_COMMAND:New(group,"Targetsheet On/Off",_settingsPath,self._TargetsheetOnOff,self,_unitName)
local _WeIn=MENU_GROUP_COMMAND:New(group,"General Info",_infoPath,self._DisplayRangeInfo,self,_unitName)
local _WeRe=MENU_GROUP_COMMAND:New(group,"Weather Report",_infoPath,self._DisplayRangeWeather,self,_unitName)
local _BoTgtgs=MENU_GROUP_COMMAND:New(group,"Bombing Targets",_infoPath,self._DisplayBombTargets,self,_unitName)
local _StrPits=MENU_GROUP_COMMAND:New(group,"Strafe Pits",_infoPath,self._DisplayStrafePits,self,_unitName):Refresh()
end
else
self:E(self.id.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName or"N/A")
end
else
self:E(self.id.."Player unit does not exist in AddF10Menu() function. Unit name: ".._unitName or"N/A")
end
end
function RANGE:_GetBombTargetCoordinate(target)
local coord=nil
if target.type==RANGE.TargetType.UNIT then
if not target.move then
coord=target.coordinate
else
if target.target and target.target:IsAlive()then
coord=target.target:GetCoordinate()
end
end
elseif target.type==RANGE.TargetType.STATIC then
coord=target.coordinate
elseif target.type==RANGE.TargetType.COORD then
coord=target.coordinate
else
self:E(self.id.."ERROR: Unknown target type.")
end
return coord
end
function RANGE:_GetAmmo(unitname)
self:F2(unitname)
local ammo=0
local unit,playername=self:_GetPlayerUnitAndName(unitname)
if unit and playername then
local has_ammo=false
local ammotable=unit:GetAmmo()
self:T2({ammotable=ammotable})
if ammotable~=nil then
local weapons=#ammotable
self:T2(self.id..string.format("Number of weapons %d.",weapons))
for w=1,weapons do
local Nammo=ammotable[w]["count"]
local Tammo=ammotable[w]["desc"]["typeName"]
if string.match(Tammo,"shell")then
ammo=ammo+Nammo
local text=string.format("Player %s has %d rounds ammo of type %s",playername,Nammo,Tammo)
self:T(self.id..text)
else
local text=string.format("Player %s has %d ammo of type %s",playername,Nammo,Tammo)
self:T(self.id..text)
end
end
end
end
return ammo
end
function RANGE:_MarkTargetsOnMap(_unitName)
self:F(_unitName)
local group=nil
if _unitName then
group=UNIT:FindByName(_unitName):GetGroup()
end
for _,_bombtarget in pairs(self.bombingTargets)do
local bombtarget=_bombtarget
local coord=self:_GetBombTargetCoordinate(_bombtarget)
if group then
coord:MarkToGroup(string.format("Bomb target %s:\n%s\n%s",bombtarget.name,coord:ToStringLLDMS(),coord:ToStringBULLS(group:GetCoalition())),group)
else
coord:MarkToAll(string.format("Bomb target %s",bombtarget.name))
end
end
for _,_strafepit in pairs(self.strafeTargets)do
for _,_target in pairs(_strafepit.targets)do
local _target=_target
if _target and _target:IsAlive()then
local coord=_target:GetCoordinate()
if group then
coord:MarkToGroup(string.format("Strafe target %s:\n%s\n%s",_target:GetName(),coord:ToStringLLDMS(),coord:ToStringBULLS(group:GetCoalition())),group)
else
coord:MarkToAll("Strafe target ".._target:GetName())
end
end
end
end
if _unitName then
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
local text=string.format("%s, %s, range targets are now marked on F10 map.",self.rangename,_playername)
self:_DisplayMessageToGroup(_unit,text,5)
end
end
function RANGE:_IlluminateBombTargets(_unitName)
self:F(_unitName)
local bomb={}
for _,_bombtarget in pairs(self.bombingTargets)do
local _target=_bombtarget.target
local coord=self:_GetBombTargetCoordinate(_bombtarget)
if coord then
table.insert(bomb,coord)
end
end
if#bomb>0 then
local coord=bomb[math.random(#bomb)]
local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z)
c:IlluminationBomb()
end
local strafe={}
for _,_strafepit in pairs(self.strafeTargets)do
for _,_target in pairs(_strafepit.targets)do
local _target=_target
if _target and _target:IsAlive()then
local coord=_target:GetCoordinate()
table.insert(strafe,coord)
end
end
end
if#strafe>0 then
local coord=strafe[math.random(#strafe)]
local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z)
c:IlluminationBomb()
end
if _unitName then
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
local text=string.format("%s, %s, range targets are illuminated.",self.rangename,_playername)
self:_DisplayMessageToGroup(_unit,text,5)
end
end
function RANGE:_ResetRangeStats(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
self.strafePlayerResults[_playername]=nil
self.bombPlayerResults[_playername]=nil
local text=string.format("%s, %s, your range stats were cleared.",self.rangename,_playername)
self:DisplayMessageToGroup(_unit,text,5,false,true)
end
end
function RANGE:_DisplayMessageToGroup(_unit,_text,_time,_clear,display,_togroup)
self:F({unit=_unit,text=_text,time=_time,clear=_clear})
_time=_time or self.Tmsg
if _clear==nil or _clear==false then
_clear=false
else
_clear=true
end
if self.messages==false then
return
end
if _unit and _unit:IsAlive()then
local _gid=_unit:GetGroup():GetID()
local _grp=_unit:GetGroup()
local _,playername=self:_GetPlayerUnitAndName(_unit:GetName())
local playermessage=self.PlayerSettings[playername].messages
if _gid and(playermessage==true or display)and(not self.examinerexclusive)then
if _togroup and _grp then
local m=MESSAGE:New(_text,_time,nil,_clear):ToGroup(_grp)
else
local m=MESSAGE:New(_text,_time,nil,_clear):ToUnit(_unit)
end
end
if self.examinergroupname~=nil then
local _examinerid=GROUP:FindByName(self.examinergroupname)
if _examinerid then
local m=MESSAGE:New(_text,_time,nil,_clear):ToGroup(_examinerid)
end
end
end
end
function RANGE:_SmokeBombImpactOnOff(unitname)
self:F(unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname)
if unit and playername then
local text
if self.PlayerSettings[playername].smokebombimpact==true then
self.PlayerSettings[playername].smokebombimpact=false
text=string.format("%s, %s, smoking impact points of bombs is now OFF.",self.rangename,playername)
else
self.PlayerSettings[playername].smokebombimpact=true
text=string.format("%s, %s, smoking impact points of bombs is now ON.",self.rangename,playername)
end
self:_DisplayMessageToGroup(unit,text,5,false,true)
end
end
function RANGE:_SmokeBombDelayOnOff(unitname)
self:F(unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname)
if unit and playername then
local text
if self.PlayerSettings[playername].delaysmoke==true then
self.PlayerSettings[playername].delaysmoke=false
text=string.format("%s, %s, delayed smoke of bombs is now OFF.",self.rangename,playername)
else
self.PlayerSettings[playername].delaysmoke=true
text=string.format("%s, %s, delayed smoke of bombs is now ON.",self.rangename,playername)
end
self:_DisplayMessageToGroup(unit,text,5,false,true)
end
end
function RANGE:_MessagesToPlayerOnOff(unitname)
self:F(unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname)
if unit and playername then
local text
if self.PlayerSettings[playername].messages==true then
text=string.format("%s, %s, display of ALL messages is now OFF.",self.rangename,playername)
else
text=string.format("%s, %s, display of ALL messages is now ON.",self.rangename,playername)
end
self:_DisplayMessageToGroup(unit,text,5,false,true)
self.PlayerSettings[playername].messages=not self.PlayerSettings[playername].messages
end
end
function RANGE:_TargetsheetOnOff(_unitname)
self:F2(_unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.PlayerSettings[playername]
if playerData then
local text=""
if self.targetsheet then
playerData.targeton=not playerData.targeton
if playerData and playerData.targeton==true then
text=string.format("roger, your targetsheets are now SAVED.")
else
text=string.format("affirm, your targetsheets are NOT SAVED.")
end
else
text="negative, target sheet data recorder is broken on this range."
end
self:_DisplayMessageToGroup(unit,text,5,false,false)
end
end
end
function RANGE:_FlareDirectHitsOnOff(unitname)
self:F(unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname)
if unit and playername then
local text
if self.PlayerSettings[playername].flaredirecthits==true then
self.PlayerSettings[playername].flaredirecthits=false
text=string.format("%s, %s, flaring direct hits is now OFF.",self.rangename,playername)
else
self.PlayerSettings[playername].flaredirecthits=true
text=string.format("%s, %s, flaring direct hits is now ON.",self.rangename,playername)
end
self:_DisplayMessageToGroup(unit,text,5,false,true)
end
end
function RANGE:_SmokeBombTargets(unitname)
self:F(unitname)
for _,_bombtarget in pairs(self.bombingTargets)do
local _target=_bombtarget.target
local coord=self:_GetBombTargetCoordinate(_bombtarget)
if coord then
coord:Smoke(self.BombSmokeColor)
end
end
if unitname then
local unit,playername=self:_GetPlayerUnitAndName(unitname)
local text=string.format("%s, %s, bombing targets are now marked with %s smoke.",self.rangename,playername,self:_smokecolor2text(self.BombSmokeColor))
self:_DisplayMessageToGroup(unit,text,5)
end
end
function RANGE:_SmokeStrafeTargets(unitname)
self:F(unitname)
for _,_target in pairs(self.strafeTargets)do
_target.coordinate:Smoke(self.StrafeSmokeColor)
end
if unitname then
local unit,playername=self:_GetPlayerUnitAndName(unitname)
local text=string.format("%s, %s, strafing tragets are now marked with %s smoke.",self.rangename,playername,self:_smokecolor2text(self.StrafeSmokeColor))
self:_DisplayMessageToGroup(unit,text,5)
end
end
function RANGE:_SmokeStrafeTargetBoxes(unitname)
self:F(unitname)
for _,_target in pairs(self.strafeTargets)do
local zone=_target.polygon
zone:SmokeZone(self.StrafePitSmokeColor,4)
for _,_point in pairs(_target.smokepoints)do
_point:SmokeOrange()
end
end
if unitname then
local unit,playername=self:_GetPlayerUnitAndName(unitname)
local text=string.format("%s, %s, strafing pit approach boxes are now marked with %s smoke.",self.rangename,playername,self:_smokecolor2text(self.StrafePitSmokeColor))
self:_DisplayMessageToGroup(unit,text,5)
end
end
function RANGE:_playersmokecolor(_unitName,color)
self:F({unitname=_unitName,color=color})
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
self.PlayerSettings[_playername].smokecolor=color
local text=string.format("%s, %s, your bomb impacts are now smoked in %s.",self.rangename,_playername,self:_smokecolor2text(color))
self:_DisplayMessageToGroup(_unit,text,5)
end
end
function RANGE:_playerflarecolor(_unitName,color)
self:F({unitname=_unitName,color=color})
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
self.PlayerSettings[_playername].flarecolor=color
local text=string.format("%s, %s, your direct hits are now flared in %s.",self.rangename,_playername,self:_flarecolor2text(color))
self:_DisplayMessageToGroup(_unit,text,5)
end
end
function RANGE:_smokecolor2text(color)
self:F(color)
local txt=""
if color==SMOKECOLOR.Blue then
txt="blue"
elseif color==SMOKECOLOR.Green then
txt="green"
elseif color==SMOKECOLOR.Orange then
txt="orange"
elseif color==SMOKECOLOR.Red then
txt="red"
elseif color==SMOKECOLOR.White then
txt="white"
else
txt=string.format("unknown color (%s)",tostring(color))
end
return txt
end
function RANGE:_flarecolor2text(color)
self:F(color)
local txt=""
if color==FLARECOLOR.Green then
txt="green"
elseif color==FLARECOLOR.Red then
txt="red"
elseif color==FLARECOLOR.White then
txt="white"
elseif color==FLARECOLOR.Yellow then
txt="yellow"
else
txt=string.format("unknown color (%s)",tostring(color))
end
return txt
end
function RANGE:_CheckStatic(name)
self:F2(name)
local _DCSstatic=StaticObject.getByName(name)
if _DCSstatic and _DCSstatic:isExist()then
local _MOOSEstatic=STATIC:FindByName(name,false)
if not _MOOSEstatic then
self:T(self.id..string.format("Adding DCS static to MOOSE database. Name = %s.",name))
_DATABASE:AddStatic(name)
end
return true
else
self:T3(self.id..string.format("No static object with name %s exists.",name))
end
if UNIT:FindByName(name)then
return false
else
self:T3(self.id..string.format("No unit object with name %s exists.",name))
end
return nil
end
function RANGE:_GetSpeed(controllable)
self:F2(controllable)
local desc=controllable:GetDesc()
local speed=0
if desc then
speed=desc.speedMax*3.6
self:T({speed=speed})
end
return speed
end
function RANGE:_GetPlayerUnitAndName(_unitName)
self:F2(_unitName)
if _unitName~=nil then
local multiplayer=false
local DCSunit=Unit.getByName(_unitName)
if DCSunit then
local playername=DCSunit:getPlayerName()
local unit=UNIT:Find(DCSunit)
self:T2({DCSunit=DCSunit,unit=unit,playername=playername})
if DCSunit and unit and playername then
self:F2(playername)
local grp=unit:GetGroup()
if grp and grp:CountAliveUnits()>1 then
multiplayer=true
end
return unit,playername,multiplayer
end
end
end
return nil,nil,nil
end
function RANGE:_myname(unitname)
self:F2(unitname)
local pname="Ghost 1 1"
local unit=UNIT:FindByName(unitname)
if unit and unit:IsAlive()then
local grp=unit:GetGroup()
if grp and grp:IsAlive()then
pname=grp:GetCustomCallSign(true,true)
end
end
return pname
end
do
ZONE_GOAL={
ClassName="ZONE_GOAL",
Goal=nil,
SmokeTime=nil,
SmokeScheduler=nil,
SmokeColor=nil,
SmokeZone=nil,
}
function ZONE_GOAL:New(Zone)
BASE:I({Zone=Zone})
local self=BASE:Inherit(self,BASE:New())
if type(Zone)=="string"then
self=BASE:Inherit(self,ZONE_POLYGON:NewFromGroupName(Zone))
else
self=BASE:Inherit(self,ZONE_RADIUS:New(Zone:GetName(),Zone:GetVec2(),Zone:GetRadius()))
self:F({Zone=Zone})
end
self.Goal=GOAL:New()
self.SmokeTime=nil
self:SetSmokeZone(true)
self:AddTransition("*","DestroyedUnit","*")
return self
end
function ZONE_GOAL:GetZone()
return self
end
function ZONE_GOAL:GetZoneName()
return self:GetName()
end
function ZONE_GOAL:SetSmokeZone(switch)
self.SmokeZone=switch
return self
end
function ZONE_GOAL:Smoke(SmokeColor)
self:F({SmokeColor=SmokeColor})
self.SmokeColor=SmokeColor
end
function ZONE_GOAL:Flare(FlareColor)
self:FlareZone(FlareColor,30)
end
function ZONE_GOAL:onafterGuard()
self:F("Guard")
if self.SmokeZone and not self.SmokeScheduler then
self.SmokeScheduler=self:ScheduleRepeat(1,1,0.1,nil,self.StatusSmoke,self)
end
end
function ZONE_GOAL:StatusSmoke()
self:F({self.SmokeTime,self.SmokeColor})
if self.SmokeZone then
local CurrentTime=timer.getTime()
if self.SmokeTime==nil or self.SmokeTime+300<=CurrentTime then
if self.SmokeColor then
self:GetCoordinate():Smoke(self.SmokeColor)
self.SmokeTime=CurrentTime
end
end
end
end
function ZONE_GOAL:__Destroyed(EventData)
self:F({"EventDead",EventData})
self:F({EventData.IniUnit})
if EventData.IniDCSUnit then
local Vec3=EventData.IniDCSUnit:getPosition().p
self:F({Vec3=Vec3})
if Vec3 and self:IsVec3InZone(Vec3)then
local PlayerHits=_DATABASE.HITS[EventData.IniUnitName]
if PlayerHits then
for PlayerName,PlayerHit in pairs(PlayerHits.Players or{})do
self.Goal:AddPlayerContribution(PlayerName)
self:DestroyedUnit(EventData.IniUnitName,PlayerName)
end
end
end
end
end
function ZONE_GOAL:MonitorDestroyedUnits()
self:HandleEvent(EVENTS.Dead,self.__Destroyed)
self:HandleEvent(EVENTS.Crash,self.__Destroyed)
end
end
do
ZONE_GOAL_COALITION={
ClassName="ZONE_GOAL_COALITION",
Coalition=nil,
PreviousCoalition=nil,
UnitCategories=nil,
ObjectCategories=nil,
}
ZONE_GOAL_COALITION.States={}
function ZONE_GOAL_COALITION:New(Zone,Coalition,UnitCategories)
if not Zone then
BASE:E("ERROR: No Zone specified in ZONE_GOAL_COALITION!")
return nil
end
local self=BASE:Inherit(self,ZONE_GOAL:New(Zone))
self:F({Zone=Zone,Coalition=Coalition})
self:SetCoalition(Coalition or coalition.side.NEUTRAL)
self:SetUnitCategories(UnitCategories)
self:SetObjectCategories()
return self
end
function ZONE_GOAL_COALITION:SetCoalition(Coalition)
self.PreviousCoalition=self.Coalition or Coalition
self.Coalition=Coalition
return self
end
function ZONE_GOAL_COALITION:SetUnitCategories(UnitCategories)
if UnitCategories and type(UnitCategories)~="table"then
UnitCategories={UnitCategories}
end
self.UnitCategories=UnitCategories or{Unit.Category.GROUND_UNIT}
return self
end
function ZONE_GOAL_COALITION:SetObjectCategories(ObjectCategories)
if ObjectCategories and type(ObjectCategories)~="table"then
ObjectCategories={ObjectCategories}
end
self.ObjectCategories=ObjectCategories or{Object.Category.UNIT,Object.Category.STATIC}
return self
end
function ZONE_GOAL_COALITION:GetCoalition()
return self.Coalition
end
function ZONE_GOAL_COALITION:GetPreviousCoalition()
return self.PreviousCoalition
end
function ZONE_GOAL_COALITION:GetCoalitionName()
return UTILS.GetCoalitionName(self.Coalition)
end
function ZONE_GOAL_COALITION:StatusZone()
local State=self:GetState()
local text=string.format("Zone state=%s, Owner=%s, Scanning...",State,self:GetCoalitionName())
self:F(text)
self:Scan(self.ObjectCategories,self.UnitCategories)
return self
end
end
do
ZONE_CAPTURE_COALITION={
ClassName="ZONE_CAPTURE_COALITION",
MarkBlue=nil,
MarkRed=nil,
StartInterval=nil,
RepeatInterval=nil,
HitsOn=nil,
HitTimeLast=nil,
HitTimeAttackOver=nil,
MarkOn=nil,
}
function ZONE_CAPTURE_COALITION:New(Zone,Coalition,UnitCategories,ObjectCategories)
local self=BASE:Inherit(self,ZONE_GOAL_COALITION:New(Zone,Coalition,UnitCategories))
self:F({Zone=Zone,Coalition=Coalition,UnitCategories=UnitCategories,ObjectCategories=ObjectCategories})
self:SetObjectCategories(ObjectCategories)
self:SetSmokeZone(false)
self:SetMarkZone(true)
self:SetStartState("Empty")
do
end
do
end
do
end
do
end
self:AddTransition("*","Guard","Guarded")
self:AddTransition("*","Empty","Empty")
self:AddTransition({"Guarded","Empty"},"Attack","Attacked")
self:AddTransition({"Guarded","Attacked","Empty"},"Capture","Captured")
_EVENTDISPATCHER:CreateEventNewZoneGoal(self)
return self
end
function ZONE_CAPTURE_COALITION:Start(StartInterval,RepeatInterval)
self.StartInterval=StartInterval or 1
self.RepeatInterval=RepeatInterval or 15
if self.ScheduleStatusZone then
self:ScheduleStop(self.ScheduleStatusZone)
end
self.ScheduleStatusZone=self:ScheduleRepeat(self.StartInterval,self.RepeatInterval,0.1,nil,self.StatusZone,self)
self:HandleEvent(EVENTS.Hit,self.OnEventHit)
self:Mark()
return self
end
function ZONE_CAPTURE_COALITION:Stop()
if self.ScheduleStatusZone then
self:ScheduleStop(self.ScheduleStatusZone)
end
if self.SmokeScheduler then
self:ScheduleStop(self.SmokeScheduler)
end
self:UnHandleEvent(EVENTS.Hit)
end
function ZONE_CAPTURE_COALITION:SetMonitorHits(Switch,TimeAttackOver)
self.HitsOn=Switch
self.HitTimeAttackOver=TimeAttackOver or 5*60
return self
end
function ZONE_CAPTURE_COALITION:SetMarkZone(Switch)
if Switch==nil or Switch==true then
self.MarkOn=true
else
self.MarkOn=false
end
return self
end
function ZONE_CAPTURE_COALITION:OnEventHit(EventData)
if self.HitsOn then
local UnitHit=EventData.TgtUnit
if UnitHit.ClassName~="SCENERY"then
if UnitHit and UnitHit:IsInZone(self)and UnitHit:GetCoalition()==self.Coalition then
self.HitTimeLast=timer.getTime()
if self:GetState()~="Attacked"then
self:F2("Hit ==> Attack")
self:Attack()
end
end
end
end
end
function ZONE_CAPTURE_COALITION:onafterGuard()
self:F2("After Guard")
if self.SmokeZone and not self.SmokeScheduler then
self.SmokeScheduler=self:ScheduleRepeat(self.StartInterval,self.RepeatInterval,0.1,nil,self.StatusSmoke,self)
end
end
function ZONE_CAPTURE_COALITION:onenterGuarded()
self:F2("Enter Guarded")
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterCaptured()
self:F2("Enter Captured")
local NewCoalition=self:GetScannedCoalition()
self:F({NewCoalition=NewCoalition})
self:SetCoalition(NewCoalition)
self:Mark()
self.Goal:Achieved()
end
function ZONE_CAPTURE_COALITION:onenterEmpty()
self:F2("Enter Empty")
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterAttacked()
self:F2("Enter Attacked")
self:Mark()
end
function ZONE_CAPTURE_COALITION:IsEmpty()
local IsEmpty=self:IsNoneInZone()
self:F({IsEmpty=IsEmpty})
return IsEmpty
end
function ZONE_CAPTURE_COALITION:IsGuarded()
local IsGuarded=self:IsAllInZoneOfCoalition(self.Coalition)
self:F({IsGuarded=IsGuarded})
return IsGuarded
end
function ZONE_CAPTURE_COALITION:IsCaptured()
local IsCaptured=self:IsAllInZoneOfOtherCoalition(self.Coalition)
self:F({IsCaptured=IsCaptured})
return IsCaptured
end
function ZONE_CAPTURE_COALITION:IsAttacked()
local IsAttacked=self:IsSomeInZoneOfCoalition(self.Coalition)
self:F({IsAttacked=IsAttacked})
return IsAttacked
end
function ZONE_CAPTURE_COALITION:StatusZone()
local State=self:GetState()
self:GetParent(self,ZONE_CAPTURE_COALITION).StatusZone(self)
local Tnow=timer.getTime()
if State~="Guarded"and self:IsGuarded()then
if self.HitTimeLast==nil or Tnow>=self.HitTimeLast+self.HitTimeAttackOver then
self:Guard()
self.HitTimeLast=nil
end
end
if State~="Empty"and self:IsEmpty()then
self:Empty()
end
if State~="Attacked"and self:IsAttacked()then
self:Attack()
end
if State~="Captured"and self:IsCaptured()then
self:Capture()
end
local unitset=self:GetScannedSetUnit()
local nRed=0
local nBlue=0
for _,object in pairs(unitset:GetSet())do
local coal=object:GetCoalition()
if object:IsAlive()then
if coal==coalition.side.RED then
nRed=nRed+1
elseif coal==coalition.side.BLUE then
nBlue=nBlue+1
end
end
end
if false then
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s",self:GetZoneName(),self:GetCoalitionName(),UTILS.GetCoalitionName(self:GetPreviousCoalition()),nBlue,nRed,State)
local NewState=self:GetState()
if NewState~=State then
text=text..string.format(" --> %s",NewState)
end
self:I(text)
end
end
function ZONE_CAPTURE_COALITION:Mark()
if self.MarkOn then
local Coord=self:GetCoordinate()
local ZoneName=self:GetZoneName()
local State=self:GetState()
if self.MarkRed then
Coord:RemoveMark(self.MarkRed)
end
if self.MarkBlue then
Coord:RemoveMark(self.MarkBlue)
end
if self.Coalition==coalition.side.BLUE then
self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Blue\nGuard Zone: "..ZoneName.."\nStatus: "..State)
self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Blue\nCapture Zone: "..ZoneName.."\nStatus: "..State)
elseif self.Coalition==coalition.side.RED then
self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Red\nGuard Zone: "..ZoneName.."\nStatus: "..State)
self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Red\nCapture Zone: "..ZoneName.."\nStatus: "..State)
else
self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Neutral\nCapture Zone: "..ZoneName.."\nStatus: "..State)
self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Neutral\nCapture Zone: "..ZoneName.."\nStatus: "..State)
end
end
end
end
ARTY={
ClassName="ARTY",
lid=nil,
Debug=false,
targets={},
moves={},
currentTarget=nil,
currentMove=nil,
Nammo0=0,
Nshells0=0,
Nrockets0=0,
Nmissiles0=0,
Nukes0=0,
Nillu0=0,
Nsmoke0=0,
StatusInterval=10,
WaitForShotTime=300,
DCSdesc=nil,
Type=nil,
DisplayName=nil,
groupname=nil,
alias=nil,
clusters={},
ismobile=true,
iscargo=false,
cargogroup=nil,
IniGroupStrength=0,
IsArtillery=nil,
RearmingDistance=100,
RearmingGroup=nil,
RearmingGroupSpeed=nil,
RearmingGroupOnRoad=false,
RearmingGroupCoord=nil,
RearmingPlaceCoord=nil,
RearmingArtyOnRoad=false,
InitialCoord=nil,
report=true,
ammoshells={},
ammorockets={},
ammomissiles={},
Nshots=0,
minrange=300,
maxrange=1000000,
nukewarhead=75000,
Nukes=nil,
nukefire=false,
nukefires=nil,
nukerange=nil,
Nillu=nil,
illuPower=1000000,
illuMinalt=500,
illuMaxalt=1000,
Nsmoke=nil,
smokeColor=SMOKECOLOR.Red,
relocateafterfire=false,
relocateRmin=300,
relocateRmax=800,
markallow=false,
markkey=nil,
markreadonly=false,
autorelocate=false,
autorelocatemaxdist=50000,
autorelocateonroad=false,
coalition=nil,
respawnafterdeath=false,
respawndelay=nil
}
ARTY.WeaponType={
Auto=1073741822,
Cannon=805306368,
Rockets=30720,
CruiseMissile=2097152,
TacticalNukes=666,
IlluminationShells=667,
SmokeShells=668,
}
ARTY.db={
["2B11 mortar"]={
minrange=500,
maxrange=7000,
reloadtime=30,
},
["SPH 2S1 Gvozdika"]={
minrange=300,
maxrange=15000,
reloadtime=nil,
},
["SPH 2S19 Msta"]={
minrange=300,
maxrange=23500,
reloadtime=nil,
},
["SPH 2S3 Akatsia"]={
minrange=300,
maxrange=17000,
reloadtime=nil,
},
["SPH 2S9 Nona"]={
minrange=500,
maxrange=7000,
reloadtime=nil,
},
["SPH M109 Paladin"]={
minrange=300,
maxrange=22000,
reloadtime=nil,
},
["SpGH Dana"]={
minrange=300,
maxrange=18700,
reloadtime=nil,
},
["MLRS BM-21 Grad"]={
minrange=5000,
maxrange=19000,
reloadtime=420,
},
["MLRS 9K57 Uragan BM-27"]={
minrange=11500,
maxrange=35800,
reloadtime=840,
},
["MLRS 9A52 Smerch"]={
minrange=20000,
maxrange=70000,
reloadtime=2160,
},
["MLRS M270"]={
minrange=10000,
maxrange=32000,
reloadtime=540,
},
}
ARTY.version="1.2.0"
function ARTY:New(group,alias)
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
if type(group)=="string"then
self.groupname=group
group=GROUP:FindByName(group)
if not group then
self:E(string.format("ERROR: Requested ARTY group %s does not exist! (Has to be a MOOSE group.)",self.groupname))
return nil
end
end
if group then
self:T(string.format("ARTY script version %s. Added group %s.",ARTY.version,group:GetName()))
else
self:E("ERROR: Requested ARTY group does not exist! (Has to be a MOOSE group.)")
return nil
end
if not(group:IsGround()or group:IsShip())then
self:E(string.format("ERROR: ARTY group %s has to be a GROUND or SHIP group!",group:GetName()))
return nil
end
self:SetControllable(group)
self.groupname=group:GetName()
self.coalition=group:GetCoalition()
if alias~=nil then
self.alias=tostring(alias)
else
self.alias=self.groupname
end
self.lid=string.format("ARTY %s | ",self.alias)
self.InitialCoord=group:GetCoordinate()
local DCSgroup=Group.getByName(group:GetName())
local DCSunit=DCSgroup:getUnit(1)
self.DCSdesc=DCSunit:getDesc()
self:T3(self.lid.."DCS descriptors for group "..group:GetName())
for id,desc in pairs(self.DCSdesc)do
self:T3({id=id,desc=desc})
end
self.SpeedMax=group:GetSpeedMax()
if self.SpeedMax>1 then
self.ismobile=true
else
self.ismobile=false
end
self.Speed=self.SpeedMax*0.7
self.DisplayName=self.DCSdesc.displayName
self.IsArtillery=DCSunit:hasAttribute("Artillery")
self.Type=group:GetTypeName()
self.IniGroupStrength=#group:GetUnits()
self:AddTransition("*","Start","CombatReady")
self:AddTransition("CombatReady","OpenFire","Firing")
self:AddTransition("Firing","CeaseFire","CombatReady")
self:AddTransition("CombatReady","Winchester","OutOfAmmo")
self:AddTransition({"CombatReady","OutOfAmmo"},"Rearm","Rearming")
self:AddTransition("Rearming","Rearmed","Rearmed")
self:AddTransition("*","Move","Moving")
self:AddTransition("Moving","Arrived","Arrived")
self:AddTransition("*","NewTarget","*")
self:AddTransition("*","CombatReady","CombatReady")
self:AddTransition("*","Status","*")
self:AddTransition("*","NewMove","*")
self:AddTransition("*","Dead","*")
self:AddTransition("*","Respawn","CombatReady")
self:AddTransition("*","Loaded","InTransit")
self:AddTransition("InTransit","UnLoaded","CombatReady")
self:AddTransition("Rearming","Arrived","Rearming")
self:AddTransition("Rearming","Move","Rearming")
return self
end
function ARTY:NewFromCargoGroup(cargogroup,alias)
if cargogroup then
BASE:T(string.format("ARTY script version %s. Added CARGO group %s.",ARTY.version,cargogroup:GetName()))
else
BASE:E("ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)")
return nil
end
local group=cargogroup:GetObject()
local arty=ARTY:New(group,alias)
arty.iscargo=true
arty.cargogroup=cargogroup
return arty
end
function ARTY:AssignTargetCoord(coord,prio,radius,nshells,maxengage,time,weapontype,name,unique)
self:F({coord=coord,prio=prio,radius=radius,nshells=nshells,maxengage=maxengage,time=time,weapontype=weapontype,name=name,unique=unique})
nshells=nshells or 5
radius=radius or 100
maxengage=maxengage or 1
prio=prio or 50
prio=math.max(1,prio)
prio=math.min(100,prio)
if unique==nil then
unique=false
end
weapontype=weapontype or ARTY.WeaponType.Auto
local text=nil
if coord:IsInstanceOf("GROUP")then
text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a GROUP. Converting to COORDINATE..."
coord=coord:GetCoordinate()
elseif coord:IsInstanceOf("UNIT")then
text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a UNIT. Converting to COORDINATE..."
coord=coord:GetCoordinate()
elseif coord:IsInstanceOf("POSITIONABLE")then
text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a POSITIONABLE. Converting to COORDINATE..."
coord=coord:GetCoordinate()
elseif coord:IsInstanceOf("COORDINATE")then
else
text="ERROR: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter!"
MESSAGE:New(text,30):ToAll()
self:E(self.lid..text)
return nil
end
if text~=nil then
self:E(self.lid..text)
end
local _name=name or coord:ToStringLLDMS()
local _unique=true
_name,_unique=self:_CheckName(self.targets,_name,not unique)
if unique==true and _unique==false then
self:T(self.lid..string.format("%s: target %s should have a unique name but name was already given. Rejecting target!",self.groupname,_name))
return nil
end
local _time
if type(time)=="string"then
_time=self:_ClockToSeconds(time)
elseif type(time)=="number"then
_time=timer.getAbsTime()+time
else
_time=timer.getAbsTime()
end
local _target={name=_name,coord=coord,radius=radius,nshells=nshells,engaged=0,underfire=false,prio=prio,maxengage=maxengage,time=_time,weapontype=weapontype}
table.insert(self.targets,_target)
self:__NewTarget(1,_target)
return _name
end
function ARTY:AssignAttackGroup(group,prio,radius,nshells,maxengage,time,weapontype,name,unique)
nshells=nshells or 5
radius=radius or 100
maxengage=maxengage or 1
prio=prio or 50
prio=math.max(1,prio)
prio=math.min(100,prio)
if unique==nil then
unique=false
end
weapontype=weapontype or ARTY.WeaponType.Auto
if type(group)=="string"then
group=GROUP:FindByName(group)
end
if group and group:IsAlive()then
local coord=group:GetCoordinate()
local _name=group:GetName()
local _unique=true
_name,_unique=self:_CheckName(self.targets,_name,not unique)
if unique==true and _unique==false then
self:T(self.lid..string.format("%s: target %s should have a unique name but name was already given. Rejecting target!",self.groupname,_name))
return nil
end
local _time
if type(time)=="string"then
_time=self:_ClockToSeconds(time)
elseif type(time)=="number"then
_time=timer.getAbsTime()+time
else
_time=timer.getAbsTime()
end
local target={}
target.attackgroup=true
target.name=_name
target.coord=coord
target.radius=radius
target.nshells=nshells
target.engaged=0
target.underfire=false
target.prio=prio
target.time=_time
target.maxengage=maxengage
target.weapontype=weapontype
table.insert(self.targets,target)
self:__NewTarget(1,target)
return _name
else
self:E("ERROR: Group does not exist!")
end
return nil
end
function ARTY:AssignMoveCoord(coord,time,speed,onroad,cancel,name,unique)
self:F({coord=coord,time=time,speed=speed,onroad=onroad,cancel=cancel,name=name,unique=unique})
if not self.ismobile then
self:T(self.lid..string.format("%s: group is immobile. Rejecting move request!",self.groupname))
return nil
end
if unique==nil then
unique=false
end
local _name=name or coord:ToStringLLDMS()
local _unique=true
_name,_unique=self:_CheckName(self.moves,_name,not unique)
if unique==true and _unique==false then
self:T(self.lid..string.format("%s: move %s should have a unique name but name was already given. Rejecting move!",self.groupname,_name))
return nil
end
if speed then
speed=math.min(speed,self.SpeedMax)
elseif self.Speed then
speed=self.Speed
else
speed=self.SpeedMax*0.7
end
if onroad==nil then
onroad=false
end
if cancel==nil then
cancel=false
end
local _time
if type(time)=="string"then
_time=self:_ClockToSeconds(time)
elseif type(time)=="number"then
_time=timer.getAbsTime()+time
else
_time=timer.getAbsTime()
end
local _move={name=_name,coord=coord,time=_time,speed=speed,onroad=onroad,cancel=cancel}
table.insert(self.moves,_move)
return _name
end
function ARTY:SetAlias(alias)
self:F({alias=alias})
self.alias=tostring(alias)
return self
end
function ARTY:AddToCluster(clusters)
self:F({clusters=clusters})
local names
if type(clusters)=="table"then
names=clusters
elseif type(clusters)=="string"then
names={clusters}
else
self:E(self.lid.."ERROR: Input parameter must be a string or a table in ARTY:AddToCluster()!")
return
end
for _,cluster in pairs(names)do
table.insert(self.clusters,cluster)
end
return self
end
function ARTY:SetMinFiringRange(range)
self:F({range=range})
self.minrange=range*1000 or 100
return self
end
function ARTY:SetMaxFiringRange(range)
self:F({range=range})
self.maxrange=range*1000 or 1000*1000
return self
end
function ARTY:SetStatusInterval(interval)
self:F({interval=interval})
self.StatusInterval=interval or 10
return self
end
function ARTY:SetWaitForShotTime(waittime)
self:F({waittime=waittime})
self.WaitForShotTime=waittime or 300
return self
end
function ARTY:SetRearmingDistance(distance)
self:F({distance=distance})
self.RearmingDistance=distance or 100
return self
end
function ARTY:SetRearmingGroup(group)
self:F({group=group})
self.RearmingGroup=group
return self
end
function ARTY:SetRearmingGroupSpeed(speed)
self:F({speed=speed})
self.RearmingGroupSpeed=speed
return self
end
function ARTY:SetRearmingGroupOnRoad(onroad)
self:F({onroad=onroad})
if onroad==nil then
onroad=true
end
self.RearmingGroupOnRoad=onroad
return self
end
function ARTY:SetRearmingArtyOnRoad(onroad)
self:F({onroad=onroad})
if onroad==nil then
onroad=true
end
self.RearmingArtyOnRoad=onroad
return self
end
function ARTY:SetRearmingPlace(coord)
self:F({coord=coord})
self.RearmingPlaceCoord=coord
return self
end
function ARTY:SetAutoRelocateToFiringRange(maxdistance,onroad)
self:F({distance=maxdistance,onroad=onroad})
self.autorelocate=true
self.autorelocatemaxdist=maxdistance or 50
self.autorelocatemaxdist=self.autorelocatemaxdist*1000
if onroad==nil then
onroad=false
end
self.autorelocateonroad=onroad
return self
end
function ARTY:SetAutoRelocateAfterEngagement(rmax,rmin)
self.relocateafterfire=true
self.relocateRmax=rmax or 800
self.relocateRmin=rmin or 300
self.relocateRmin=math.min(self.relocateRmin,self.relocateRmax)
return self
end
function ARTY:SetReportON()
self.report=true
return self
end
function ARTY:SetReportOFF()
self.report=false
return self
end
function ARTY:SetRespawnOnDeath(delay)
self.respawnafterdeath=true
self.respawndelay=delay
return self
end
function ARTY:SetDebugON()
self.Debug=true
return self
end
function ARTY:SetDebugOFF()
self.Debug=false
return self
end
function ARTY:SetSpeed(speed)
self.Speed=speed
return self
end
function ARTY:RemoveTarget(name)
self:F2(name)
local id=self:_GetTargetIndexByName(name)
if id then
self:T(self.lid..string.format("Group %s: Removing target %s (id=%d).",self.groupname,name,id))
table.remove(self.targets,id)
if self.markallow then
local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name)
if batteryname==self.groupname and markTargetID~=nil then
COORDINATE:RemoveMark(markTargetID)
end
end
end
self:T(self.lid..string.format("Group %s: Number of targets = %d.",self.groupname,#self.targets))
end
function ARTY:RemoveMove(name)
self:F2(name)
local id=self:_GetMoveIndexByName(name)
if id then
self:T(self.lid..string.format("Group %s: Removing move %s (id=%d).",self.groupname,name,id))
table.remove(self.moves,id)
if self.markallow then
local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name)
if batteryname==self.groupname and markMoveID~=nil then
COORDINATE:RemoveMark(markMoveID)
end
end
end
self:T(self.lid..string.format("Group %s: Number of moves = %d.",self.groupname,#self.moves))
end
function ARTY:RemoveAllTargets()
self:F2()
for _,target in pairs(self.targets)do
self:RemoveTarget(target.name)
end
end
function ARTY:SetShellTypes(tableofnames)
self:F2(tableofnames)
self.ammoshells={}
for _,_type in pairs(tableofnames)do
table.insert(self.ammoshells,_type)
end
return self
end
function ARTY:SetRocketTypes(tableofnames)
self:F2(tableofnames)
self.ammorockets={}
for _,_type in pairs(tableofnames)do
table.insert(self.ammorockets,_type)
end
return self
end
function ARTY:SetMissileTypes(tableofnames)
self:F2(tableofnames)
self.ammomissiles={}
for _,_type in pairs(tableofnames)do
table.insert(self.ammomissiles,_type)
end
return self
end
function ARTY:SetTacNukeShells(n)
self.Nukes=n
return self
end
function ARTY:SetTacNukeWarhead(strength)
self.nukewarhead=strength or 0.075
self.nukewarhead=self.nukewarhead*1000*1000
return self
end
function ARTY:SetIlluminationShells(n,power)
self.Nillu=n
self.illuPower=power or 1.0
self.illuPower=self.illuPower*1000000
return self
end
function ARTY:SetIlluminationMinMaxAlt(minalt,maxalt)
self.illuMinalt=minalt or 500
self.illuMaxalt=maxalt or 1000
if self.illuMinalt>self.illuMaxalt then
self.illuMinalt=self.illuMaxalt
end
return self
end
function ARTY:SetSmokeShells(n,color)
self.Nsmoke=n
self.smokeColor=color or SMOKECOLOR.Red
return self
end
function ARTY:SetTacNukeFires(nfires,range)
self.nukefire=true
self.nukefires=nfires
self.nukerange=range
return self
end
function ARTY:SetMarkAssignmentsOn(key,readonly)
self.markkey=key
self.markallow=true
if readonly==nil then
self.markreadonly=false
end
return self
end
function ARTY:SetMarkTargetsOff()
self.markallow=false
self.markkey=nil
return self
end
function ARTY:onafterStart(Controllable,From,Event,To)
self:_EventFromTo("onafterStart",Event,From,To)
local text=string.format("Started ARTY version %s for group %s.",ARTY.version,Controllable:GetName())
self:I(self.lid..text)
MESSAGE:New(text,5):ToAllIf(self.Debug)
self.Nammo0,self.Nshells0,self.Nrockets0,self.Nmissiles0=self:GetAmmo(self.Debug)
if self.nukerange==nil then
self.nukerange=1500/75000*self.nukewarhead
end
if self.nukefires==nil then
self.nukefires=20/1000/1000*self.nukerange*self.nukerange
end
if self.Nukes~=nil then
self.Nukes0=math.min(self.Nukes,self.Nshells0)
else
self.Nukes=0
self.Nukes0=0
end
if self.Nillu~=nil then
self.Nillu0=math.min(self.Nillu,self.Nshells0)
else
self.Nillu=0
self.Nillu0=0
end
if self.Nsmoke~=nil then
self.Nsmoke0=math.min(self.Nsmoke,self.Nshells0)
else
self.Nsmoke=0
self.Nsmoke0=0
end
local _dbproperties=self:_CheckDB(self.DisplayName)
self:T({dbproperties=_dbproperties})
if _dbproperties~=nil then
for property,value in pairs(_dbproperties)do
self:T({property=property,value=value})
self[property]=value
end
end
if not self.ismobile then
self.RearmingPlaceCoord=nil
self.relocateafterfire=false
self.autorelocate=false
end
self.Speed=math.min(self.Speed,self.SpeedMax)
if self.RearmingGroup then
local speedmax=self.RearmingGroup:GetSpeedMax()
self:T(self.lid..string.format("%s, rearming group %s max speed = %.1f km/h.",self.groupname,self.RearmingGroup:GetName(),speedmax))
if self.RearmingGroupSpeed==nil then
self.RearmingGroupSpeed=speedmax*0.5
else
self.RearmingGroupSpeed=math.min(self.RearmingGroupSpeed,self.RearmingGroup:GetSpeedMax())
end
else
self.RearmingGroupSpeed=23
end
local text=string.format("\n******************************************************\n")
text=text..string.format("Arty group          = %s\n",self.groupname)
text=text..string.format("Arty alias          = %s\n",self.alias)
text=text..string.format("Artillery attribute = %s\n",tostring(self.IsArtillery))
text=text..string.format("Type                = %s\n",self.Type)
text=text..string.format("Display Name        = %s\n",self.DisplayName)
text=text..string.format("Number of units     = %d\n",self.IniGroupStrength)
text=text..string.format("Speed max           = %d km/h\n",self.SpeedMax)
text=text..string.format("Speed default       = %d km/h\n",self.Speed)
text=text..string.format("Is mobile           = %s\n",tostring(self.ismobile))
text=text..string.format("Is cargo            = %s\n",tostring(self.iscargo))
text=text..string.format("Min range           = %.1f km\n",self.minrange/1000)
text=text..string.format("Max range           = %.1f km\n",self.maxrange/1000)
text=text..string.format("Total ammo count    = %d\n",self.Nammo0)
text=text..string.format("Number of shells    = %d\n",self.Nshells0)
text=text..string.format("Number of rockets   = %d\n",self.Nrockets0)
text=text..string.format("Number of missiles  = %d\n",self.Nmissiles0)
text=text..string.format("Number of nukes     = %d\n",self.Nukes0)
text=text..string.format("Nuclear warhead     = %d tons TNT\n",self.nukewarhead/1000)
text=text..string.format("Nuclear demolition  = %d m\n",self.nukerange)
text=text..string.format("Nuclear fires       = %d (active=%s)\n",self.nukefires,tostring(self.nukefire))
text=text..string.format("Number of illum.    = %d\n",self.Nillu0)
text=text..string.format("Illuminaton Power   = %.3f mcd\n",self.illuPower/1000000)
text=text..string.format("Illuminaton Minalt  = %d m\n",self.illuMinalt)
text=text..string.format("Illuminaton Maxalt  = %d m\n",self.illuMaxalt)
text=text..string.format("Number of smoke     = %d\n",self.Nsmoke0)
text=text..string.format("Smoke color         = %d\n",self.smokeColor)
if self.RearmingGroup or self.RearmingPlaceCoord then
text=text..string.format("Rearming safe dist. = %d m\n",self.RearmingDistance)
end
if self.RearmingGroup then
text=text..string.format("Rearming group      = %s\n",self.RearmingGroup:GetName())
text=text..string.format("Rearming group speed= %d km/h\n",self.RearmingGroupSpeed)
text=text..string.format("Rearming group roads= %s\n",tostring(self.RearmingGroupOnRoad))
end
if self.RearmingPlaceCoord then
local dist=self.InitialCoord:Get2DDistance(self.RearmingPlaceCoord)
text=text..string.format("Rearming coord dist = %d m\n",dist)
text=text..string.format("Rearming ARTY roads = %s\n",tostring(self.RearmingArtyOnRoad))
end
text=text..string.format("Relocate after fire = %s\n",tostring(self.relocateafterfire))
text=text..string.format("Relocate min dist.  = %d m\n",self.relocateRmin)
text=text..string.format("Relocate max dist.  = %d m\n",self.relocateRmax)
text=text..string.format("Auto move in range  = %s\n",tostring(self.autorelocate))
text=text..string.format("Auto move dist. max = %.1f km\n",self.autorelocatemaxdist/1000)
text=text..string.format("Auto move on road   = %s\n",tostring(self.autorelocateonroad))
text=text..string.format("Marker assignments  = %s\n",tostring(self.markallow))
text=text..string.format("Marker auth. key    = %s\n",tostring(self.markkey))
text=text..string.format("Marker readonly     = %s\n",tostring(self.markreadonly))
text=text..string.format("Clusters:\n")
for _,cluster in pairs(self.clusters)do
text=text..string.format("- %s\n",tostring(cluster))
end
text=text..string.format("******************************************************\n")
text=text..string.format("Targets:\n")
for _,target in pairs(self.targets)do
text=text..string.format("- %s\n",self:_TargetInfo(target))
local possible=self:_CheckWeaponTypePossible(target)
if not possible then
self:E(self.lid..string.format("WARNING: Selected weapon type %s is not possible",self:_WeaponTypeName(target.weapontype)))
end
if self.Debug then
local zone=ZONE_RADIUS:New(target.name,target.coord:GetVec2(),target.radius)
zone:BoundZone(180,coalition.side.NEUTRAL)
end
end
text=text..string.format("Moves:\n")
for i=1,#self.moves do
text=text..string.format("- %s\n",self:_MoveInfo(self.moves[i]))
end
text=text..string.format("******************************************************\n")
text=text..string.format("Shell types:\n")
for _,_type in pairs(self.ammoshells)do
text=text..string.format("- %s\n",_type)
end
text=text..string.format("Rocket types:\n")
for _,_type in pairs(self.ammorockets)do
text=text..string.format("- %s\n",_type)
end
text=text..string.format("Missile types:\n")
for _,_type in pairs(self.ammomissiles)do
text=text..string.format("- %s\n",_type)
end
text=text..string.format("******************************************************")
if self.Debug then
self:I(self.lid..text)
else
self:T(self.lid..text)
end
self.Controllable:OptionROEHoldFire()
self:HandleEvent(EVENTS.Shot)
self:HandleEvent(EVENTS.Dead)
if self.markallow then
world.addEventHandler(self)
end
self:__Status(self.StatusInterval)
end
function ARTY:_CheckDB(displayname)
for _type,_properties in pairs(ARTY.db)do
self:T({type=_type,properties=_properties})
if _type==displayname then
self:T({type=_type,properties=_properties})
return _properties
end
end
return nil
end
function ARTY:_StatusReport(display)
if display==nil then
display=false
end
local Nammo,Nshells,Nrockets,Nmissiles=self:GetAmmo()
local Nnukes=self.Nukes
local Nillu=self.Nillu
local Nsmoke=self.Nsmoke
local Tnow=timer.getTime()
local Clock=self:_SecondsToClock(timer.getAbsTime())
local text=string.format("\n******************* STATUS ***************************\n")
text=text..string.format("ARTY group          = %s\n",self.groupname)
text=text..string.format("Clock               = %s\n",Clock)
text=text..string.format("FSM state           = %s\n",self:GetState())
text=text..string.format("Total ammo count    = %d\n",Nammo)
text=text..string.format("Number of shells    = %d\n",Nshells)
text=text..string.format("Number of rockets   = %d\n",Nrockets)
text=text..string.format("Number of missiles  = %d\n",Nmissiles)
text=text..string.format("Number of nukes     = %d\n",Nnukes)
text=text..string.format("Number of illum.    = %d\n",Nillu)
text=text..string.format("Number of smoke     = %d\n",Nsmoke)
if self.currentTarget then
text=text..string.format("Current Target      = %s\n",tostring(self.currentTarget.name))
text=text..string.format("Curr. Tgt assigned  = %d\n",Tnow-self.currentTarget.Tassigned)
else
text=text..string.format("Current Target      = %s\n","none")
end
text=text..string.format("Nshots curr. Target = %d\n",self.Nshots)
text=text..string.format("Targets:\n")
for i=1,#self.targets do
text=text..string.format("- %s\n",self:_TargetInfo(self.targets[i]))
end
if self.currentMove then
text=text..string.format("Current Move        = %s\n",tostring(self.currentMove.name))
else
text=text..string.format("Current Move        = %s\n","none")
end
text=text..string.format("Moves:\n")
for i=1,#self.moves do
text=text..string.format("- %s\n",self:_MoveInfo(self.moves[i]))
end
text=text..string.format("******************************************************")
env.info(self.lid..text)
MESSAGE:New(text,20):Clear():ToCoalitionIf(self.coalition,display)
end
function ARTY:OnEventShot(EventData)
self:F(EventData)
local _weapon=EventData.Weapon:getTypeName()
local _weaponStrArray=self:_split(_weapon,"%.")
local _weaponName=_weaponStrArray[#_weaponStrArray]
self:T3(self.lid.."EVENT SHOT: Ini unit    = "..EventData.IniUnitName)
self:T3(self.lid.."EVENT SHOT: Ini group   = "..EventData.IniGroupName)
self:T3(self.lid.."EVENT SHOT: Weapon type = ".._weapon)
self:T3(self.lid.."EVENT SHOT: Weapon name = ".._weaponName)
local group=EventData.IniGroup
if group and group:IsAlive()then
if EventData.IniGroupName==self.groupname then
if self.currentTarget then
self.Nshots=self.Nshots+1
local text=string.format("%s, fired shot %d of %d with weapon %s on target %s.",self.alias,self.Nshots,self.currentTarget.nshells,_weaponName,self.currentTarget.name)
self:T(self.lid..text)
MESSAGE:New(text,5):Clear():ToAllIf(self.report or self.Debug)
local _lastpos={x=0,y=0,z=0}
local function _TrackWeapon(_data)
local _weaponalive,_currpos=pcall(
function()
return _data.weapon:getPoint()
end)
self:T3(self.lid..string.format("ARTY %s: Weapon still in air: %s",self.groupname,tostring(_weaponalive)))
local _destroyweapon=false
if _weaponalive then
_lastpos={x=_currpos.x,y=_currpos.y,z=_currpos.z}
local _coord=COORDINATE:NewFromVec3(_lastpos)
local _dist=_coord:Get2DDistance(_data.target.coord)
self:T3(self.lid..string.format("ARTY %s weapon to target dist = %d m",self.groupname,_dist))
if _data.target.weapontype==ARTY.WeaponType.IlluminationShells then
if _dist<_data.target.radius then
local _cr=_data.target.coord:GetRandomCoordinateInRadius(_data.target.radius)
local _alt=_cr:GetLandHeight()+math.random(self.illuMinalt,self.illuMaxalt)
local _ci=COORDINATE:New(_cr.x,_alt,_cr.z)
_ci:IlluminationBomb(self.illuPower)
_destroyweapon=true
end
elseif _data.target.weapontype==ARTY.WeaponType.SmokeShells then
if _dist<_data.target.radius then
local _cr=_coord:GetRandomCoordinateInRadius(_data.target.radius)
_cr:Smoke(self.smokeColor)
_destroyweapon=true
end
end
if _destroyweapon then
self:T2(self.lid..string.format("ARTY %s destroying shell, stopping timer.",self.groupname))
_data.weapon:destroy()
return nil
else
local dt=0.02
self:T3(self.lid..string.format("ARTY %s tracking weapon again in %.3f seconds",self.groupname,dt))
return timer.getTime()+dt
end
else
local _impactcoord=COORDINATE:NewFromVec3(_lastpos)
self:I(self.lid..string.format("ARTY %s weapon NOT ALIVE any more.",self.groupname))
if _data.target.weapontype==ARTY.WeaponType.TacticalNukes then
self:T(self.lid..string.format("ARTY %s triggering nuclear explosion in one second.",self.groupname))
SCHEDULER:New(nil,ARTY._NuclearBlast,{self,_impactcoord},1.0)
end
return nil
end
end
local _tracknuke=self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes>0
local _trackillu=self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu>0
local _tracksmoke=self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke>0
if _tracknuke or _trackillu or _tracksmoke then
self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.",self.groupname))
local _peter={}
_peter.weapon=EventData.weapon
_peter.target=UTILS.DeepCopy(self.currentTarget)
timer.scheduleFunction(_TrackWeapon,_peter,timer.getTime()+2.0)
end
local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo()
if self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes then
self.Nukes=self.Nukes-1
end
if self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells then
self.Nillu=self.Nillu-1
end
if self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells then
self.Nsmoke=self.Nsmoke-1
end
local _outofammo=false
if _nammo==0 then
self:T(self.lid..string.format("Group %s completely out of ammo.",self.groupname))
_outofammo=true
end
local _partlyoutofammo=self:_CheckOutOfAmmo({self.currentTarget})
local _weapontype=self:_WeaponTypeName(self.currentTarget.weapontype)
self:T(self.lid..string.format("Group %s ammo: total=%d, shells=%d, rockets=%d, missiles=%d",self.groupname,_nammo,_nshells,_nrockets,_nmissiles))
self:T(self.lid..string.format("Group %s uses weapontype %s for current target.",self.groupname,_weapontype))
local _ceasefire=false
local _relocate=false
if self.Nshots>=self.currentTarget.nshells then
local text=string.format("Group %s stop firing on target %s.",self.groupname,self.currentTarget.name)
self:T(self.lid..text)
MESSAGE:New(text,5):ToAllIf(self.Debug)
_ceasefire=true
_relocate=self.relocateafterfire
end
if _outofammo or _partlyoutofammo then
_ceasefire=true
end
if _relocate then
self:_Relocate()
end
if _ceasefire then
self:CeaseFire(self.currentTarget)
end
else
self:E(self.lid..string.format("WARNING: No current target for group %s?!",self.groupname))
end
end
end
end
function ARTY:onEvent(Event)
if Event==nil or Event.idx==nil then
self:T3("Skipping onEvent. Event or Event.idx unknown.")
return true
end
self:T2(string.format("Event captured  = %s",tostring(self.groupname)))
self:T2(string.format("Event id        = %s",tostring(Event.id)))
self:T2(string.format("Event time      = %s",tostring(Event.time)))
self:T2(string.format("Event idx       = %s",tostring(Event.idx)))
self:T2(string.format("Event coalition = %s",tostring(Event.coalition)))
self:T2(string.format("Event group id  = %s",tostring(Event.groupID)))
self:T2(string.format("Event text      = %s",tostring(Event.text)))
if Event.initiator~=nil then
local _unitname=Event.initiator:getName()
self:T2(string.format("Event ini unit name = %s",tostring(_unitname)))
end
if Event.id==world.event.S_EVENT_MARK_ADDED then
self:T2({event="S_EVENT_MARK_ADDED",battery=self.groupname,vec3=Event.pos})
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
self:T({event="S_EVENT_MARK_CHANGE",battery=self.groupname,vec3=Event.pos})
self:_OnEventMarkChange(Event)
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
self:T2({event="S_EVENT_MARK_REMOVED",battery=self.groupname,vec3=Event.pos})
self:_OnEventMarkRemove(Event)
end
end
function ARTY:_OnEventMarkRemove(Event)
local batterycoalition=self.coalition
if Event.text~=nil and Event.text:find("BATTERY")then
local _cancelmove=false
local _canceltarget=false
local _name=""
local _id=nil
if Event.text:find("Marked Relocation")then
_cancelmove=true
_name=self:_MarkMoveName(Event.idx)
_id=self:_GetMoveIndexByName(_name)
elseif Event.text:find("Marked Target")then
_canceltarget=true
_name=self:_MarkTargetName(Event.idx)
_id=self:_GetTargetIndexByName(_name)
else
return
end
if _id==nil then
return
end
if(batterycoalition==Event.coalition and self.markkey==nil)or self.markkey~=nil then
local _validkey=self:_MarkerKeyAuthentification(Event.text)
if _validkey then
if _cancelmove then
if self.currentMove and self.currentMove.name==_name then
self.Controllable:ClearTasks()
self:Arrived()
else
self:RemoveMove(_name)
end
elseif _canceltarget then
if self.currentTarget and self.currentTarget.name==_name then
self:CeaseFire(self.currentTarget)
self:RemoveTarget(_name)
else
self:RemoveTarget(_name)
end
end
end
end
end
end
function ARTY:_OnEventMarkChange(Event)
if Event.text~=nil and Event.text:lower():find("arty")then
local vec3={y=Event.pos.y,x=Event.pos.x,z=Event.pos.z}
local _coord=COORDINATE:NewFromVec3(vec3)
_coord.y=_coord:GetLandHeight()
local batterycoalition=self.coalition
local batteryname=self.groupname
if(batterycoalition==Event.coalition and self.markkey==nil)or self.markkey~=nil then
local _assign=self:_Markertext(Event.text)
if _assign==nil or not(_assign.engage or _assign.move or _assign.request or _assign.cancel or _assign.set)then
self:T(self.lid..string.format("WARNING: %s, no keyword ENGAGE, MOVE, REQUEST, CANCEL or SET in mark text! Command will not be executed. Text:\n%s",self.groupname,Event.text))
return
end
local _assigned=false
if _assign.everyone then
_assigned=true
else
for _,bat in pairs(_assign.battery)do
if self.groupname==bat then
_assigned=true
end
end
for _,alias in pairs(_assign.aliases)do
if self.alias==alias then
_assigned=true
end
end
for _,bat in pairs(_assign.cluster)do
for _,cluster in pairs(self.clusters)do
if cluster==bat then
_assigned=true
end
end
end
end
if not _assigned then
self:T3(self.lid..string.format("INFO: ARTY group %s was not addressed! Mark text:\n%s",self.groupname,Event.text))
return
else
if self.Controllable and self.Controllable:IsAlive()then
else
self:T3(self.lid..string.format("INFO: ARTY group %s was addressed but is NOT alive! Mark text:\n%s",self.groupname,Event.text))
return
end
end
if _assign.coord then
_coord=_assign.coord
end
local _validkey=self:_MarkerKeyAuthentification(Event.text)
if _assign.request and _validkey then
if _assign.requestammo then
self:_MarkRequestAmmo()
end
if _assign.requestmoves then
self:_MarkRequestMoves()
end
if _assign.requesttargets then
self:_MarkRequestTargets()
end
if _assign.requeststatus then
self:_MarkRequestStatus()
end
if _assign.requestrearming then
self:Rearm()
end
return
end
if _assign.cancel and _validkey then
if _assign.cancelmove and self.currentMove then
self.Controllable:ClearTasks()
self:Arrived()
elseif _assign.canceltarget and self.currentTarget then
self.currentTarget.engaged=self.currentTarget.engaged+1
self:CeaseFire(self.currentTarget)
elseif _assign.cancelrearm and self:is("Rearming")then
local nammo=self:GetAmmo()
if nammo>0 then
self:Rearmed()
else
self:Winchester()
end
end
return
end
if _assign.set and _validkey then
if _assign.setrearmingplace and self.ismobile then
self:SetRearmingPlace(_coord)
_coord:RemoveMark(Event.idx)
_coord:MarkToCoalition(string.format("Rearming place for battery %s",self.groupname),self.coalition,false,string.format("New rearming place for battery %s defined.",self.groupname))
if self.Debug then
_coord:SmokeOrange()
end
end
if _assign.setrearminggroup then
_coord:RemoveMark(Event.idx)
local rearminggroupcoord=_assign.setrearminggroup:GetCoordinate()
rearminggroupcoord:MarkToCoalition(string.format("Rearming group for battery %s",self.groupname),self.coalition,false,string.format("New rearming group for battery %s defined.",self.groupname))
self:SetRearmingGroup(_assign.setrearminggroup)
if self.Debug then
rearminggroupcoord:SmokeOrange()
end
end
return
end
if _validkey then
_coord:RemoveMark(Event.idx)
local _id=UTILS._MarkID+1
if _assign.move then
local _name=self:_MarkMoveName(_id)
local text=string.format("%s, received new relocation assignment.",self.alias)
text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS())
MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug)
local _movename=self:AssignMoveCoord(_coord,_assign.time,_assign.speed,_assign.onroad,_assign.movecanceltarget,_name,true)
if _movename~=nil then
local _mid=self:_GetMoveIndexByName(_movename)
local _move=self.moves[_mid]
local clock=tostring(self:_SecondsToClock(_move.time))
local _markertext=_movename..string.format(", Time=%s, Speed=%d km/h, Use Roads=%s.",clock,_move.speed,tostring(_move.onroad))
local _randomcoord=_coord:GetRandomCoordinateInRadius(100)
_randomcoord:MarkToCoalition(_markertext,batterycoalition,self.markreadonly or _assign.readonly)
else
local text=string.format("%s, relocation not possible.",self.alias)
MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug)
end
else
local _name=self:_MarkTargetName(_id)
local text=string.format("%s, received new target assignment.",self.alias)
text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS())
if _assign.time then
text=text..string.format("\nTime %s",_assign.time)
end
if _assign.prio then
text=text..string.format("\nPrio %d",_assign.prio)
end
if _assign.radius then
text=text..string.format("\nRadius %d m",_assign.radius)
end
if _assign.nshells then
text=text..string.format("\nShots %d",_assign.nshells)
end
if _assign.maxengage then
text=text..string.format("\nEngagements %d",_assign.maxengage)
end
if _assign.weapontype then
text=text..string.format("\nWeapon %s",self:_WeaponTypeName(_assign.weapontype))
end
MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug)
local _targetname=self:AssignTargetCoord(_coord,_assign.prio,_assign.radius,_assign.nshells,_assign.maxengage,_assign.time,_assign.weapontype,_name,true)
if _targetname~=nil then
local _tid=self:_GetTargetIndexByName(_targetname)
local _target=self.targets[_tid]
local clock=tostring(self:_SecondsToClock(_target.time))
local weapon=self:_WeaponTypeName(_target.weapontype)
local _markertext=_targetname..string.format(", Priority=%d, Radius=%d m, Shots=%d, Engagements=%d, Weapon=%s, Time=%s",_target.prio,_target.radius,_target.nshells,_target.maxengage,weapon,clock)
local _randomcoord=_coord:GetRandomCoordinateInRadius(250)
_randomcoord:MarkToCoalition(_markertext,batterycoalition,self.markreadonly or _assign.readonly)
end
end
end
end
end
end
function ARTY:OnEventDead(EventData)
self:F(EventData)
local _name=self.groupname
if EventData and EventData.IniGroupName and EventData.IniGroupName==_name then
local unitname=tostring(EventData.IniUnitName)
self:T(self.lid..string.format("%s: Captured dead event for unit %s.",_name,unitname))
self:Dead(unitname)
end
end
function ARTY:onafterStatus(Controllable,From,Event,To)
self:_EventFromTo("onafterStatus",Event,From,To)
local nammo,nshells,nrockets,nmissiles=self:GetAmmo()
if self.iscargo and self.cargogroup then
if self.cargogroup:IsLoaded()and not self:is("InTransit")then
self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.",self.alias))
self:Loaded()
elseif self.cargogroup:IsUnLoaded()then
self:T(self.lid..string.format("Group %s has been unloaded from the carrier.",self.alias))
self:UnLoaded()
end
end
local fsmstate=self:GetState()
self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d",fsmstate,nammo,nshells,self.Nsmoke,self.Nillu,self.Nukes,self.nukewarhead/1000000,nrockets,nmissiles))
if self.Controllable and self.Controllable:IsAlive()then
if self.Debug then
self:_StatusReport()
end
if self:is("Moving")then
self:T2(self.lid..string.format("%s: Moving",Controllable:GetName()))
end
if self:is("Rearming")then
local _rearmed=self:_CheckRearmed()
if _rearmed then
self:T2(self.lid..string.format("%s: Rearming ==> Rearmed",Controllable:GetName()))
self:Rearmed()
end
end
if self:is("Rearmed")then
local distance=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord)
self:T2(self.lid..string.format("%s: Rearmed. Distance ARTY to InitalCoord = %d m",Controllable:GetName(),distance))
if distance<=self.RearmingDistance then
self:T2(self.lid..string.format("%s: Rearmed ==> CombatReady",Controllable:GetName()))
self:CombatReady()
end
end
if self:is("Arrived")then
self:T2(self.lid..string.format("%s: Arrived ==> CombatReady",Controllable:GetName()))
self:CombatReady()
end
if self:is("Firing")then
self:_CheckShootingStarted()
end
self:_CheckTargetsInRange()
local notpossible={}
for i=1,#self.targets do
local _target=self.targets[i]
local possible=self:_CheckWeaponTypePossible(_target)
if not possible then
table.insert(notpossible,_target.name)
end
end
for _,targetname in pairs(notpossible)do
self:E(self.lid..string.format("%s: Removing target %s because requested weapon is not possible with this type of unit.",self.groupname,targetname))
self:RemoveTarget(targetname)
end
local _timedTarget=self:_CheckTimedTargets()
local _normalTarget=self:_CheckNormalTargets()
local _move=self:_CheckMoves()
if _move then
self:Move(_move)
elseif _timedTarget then
if self.currentTarget then
self:CeaseFire(self.currentTarget)
end
self:OpenFire(_timedTarget)
elseif _normalTarget then
self:OpenFire(_normalTarget)
end
local gotsome=false
if#self.targets>0 then
for i=1,#self.targets do
local _target=self.targets[i]
if self:_CheckWeaponTypeAvailable(_target)>0 then
gotsome=true
end
end
else
gotsome=true
end
if(nammo==0 or not gotsome)and not(self:is("Moving")or self:is("Rearming")or self:is("OutOfAmmo"))then
self:Winchester()
end
if self:is("OutOfAmmo")then
self:T2(self.lid..string.format("%s: OutOfAmmo ==> Rearm ==> Rearming",Controllable:GetName()))
self:Rearm()
end
self:__Status(self.StatusInterval)
elseif self.iscargo then
if self.cargogroup and self.cargogroup:IsAlive()then
if self:is("InTransit")then
self:__Status(-5)
end
end
else
self:E(self.lid..string.format("Arty group %s is not alive!",self.groupname))
end
end
function ARTY:onbeforeLoaded(Controllable,From,Event,To)
if self.currentTarget then
self:CeaseFire(self.currentTarget)
end
return true
end
function ARTY:onafterUnLoaded(Controllable,From,Event,To)
self:CombatReady()
end
function ARTY:onenterCombatReady(Controllable,From,Event,To)
self:_EventFromTo("onenterCombatReady",Event,From,To)
self:T3(self.lid..string.format("onenterComabReady, from=%s, event=%s, to=%s",From,Event,To))
end
function ARTY:onbeforeOpenFire(Controllable,From,Event,To,target)
self:_EventFromTo("onbeforeOpenFire",Event,From,To)
if self.currentTarget then
self:E(self.lid..string.format("ERROR: Group %s already has a target %s!",self.groupname,self.currentTarget.name))
return false
end
if not self:_TargetInRange(target)then
self:E(self.lid..string.format("ERROR: Group %s, target %s is out of range!",self.groupname,self.currentTarget.name))
return false
end
local nfire=self:_CheckWeaponTypeAvailable(target)
target.nshells=math.min(target.nshells,nfire)
if target.nshells<1 then
local text=string.format("%s, no ammo left to engage target %s with selected weapon type %s.")
return false
end
return true
end
function ARTY:onafterOpenFire(Controllable,From,Event,To,target)
self:_EventFromTo("onafterOpenFire",Event,From,To)
local id=self:_GetTargetIndexByName(target.name)
if id then
self.targets[id].underfire=true
self.currentTarget=target
self.currentTarget.Tassigned=timer.getTime()
end
local range=Controllable:GetCoordinate():Get2DDistance(target.coord)
local Nammo,Nshells,Nrockets,Nmissiles=self:GetAmmo()
local nfire=Nammo
local _type="shots"
if target.weapontype==ARTY.WeaponType.Auto then
nfire=Nammo
_type="shots"
elseif target.weapontype==ARTY.WeaponType.Cannon then
nfire=Nshells
_type="shells"
elseif target.weapontype==ARTY.WeaponType.TacticalNukes then
nfire=self.Nukes
_type="nuclear shells"
elseif target.weapontype==ARTY.WeaponType.IlluminationShells then
nfire=self.Nillu
_type="illumination shells"
elseif target.weapontype==ARTY.WeaponType.SmokeShells then
nfire=self.Nsmoke
_type="smoke shells"
elseif target.weapontype==ARTY.WeaponType.Rockets then
nfire=Nrockets
_type="rockets"
elseif target.weapontype==ARTY.WeaponType.CruiseMissile then
nfire=Nmissiles
_type="cruise missiles"
end
target.nshells=math.min(target.nshells,nfire)
local text=string.format("%s, opening fire on target %s with %d %s. Distance %.1f km.",Controllable:GetName(),target.name,target.nshells,_type,range/1000)
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report)
if target.attackgroup then
self:_AttackGroup(target)
else
self:_FireAtCoord(target.coord,target.radius,target.nshells,target.weapontype)
end
end
function ARTY:onafterCeaseFire(Controllable,From,Event,To,target)
self:_EventFromTo("onafterCeaseFire",Event,From,To)
if target then
local text=string.format("%s, ceasing fire on target %s.",Controllable:GetName(),target.name)
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report)
local id=self:_GetTargetIndexByName(target.name)
if id then
if self.Nshots>0 then
self.targets[id].engaged=self.targets[id].engaged+1
self.targets[id].time=nil
end
self.targets[id].underfire=false
end
if target.engaged>=target.maxengage then
self:RemoveTarget(target.name)
end
self.Controllable:OptionROEHoldFire()
self.Controllable:ClearTasks()
else
self:E(self.lid..string.format("ERROR: No target in cease fire for group %s.",self.groupname))
end
self.Nshots=0
self.currentTarget=nil
end
function ARTY:onafterWinchester(Controllable,From,Event,To)
self:_EventFromTo("onafterWinchester",Event,From,To)
local text=string.format("%s, winchester!",Controllable:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
end
function ARTY:onbeforeRearm(Controllable,From,Event,To)
self:_EventFromTo("onbeforeRearm",Event,From,To)
local _rearmed=self:_CheckRearmed()
if _rearmed then
self:T(self.lid..string.format("%s, group is already armed to the teeth. Rearming request denied!",self.groupname))
return false
else
self:T(self.lid..string.format("%s, group might be rearmed.",self.groupname))
end
if self.RearmingGroup and self.RearmingGroup:IsAlive()then
return true
elseif self.RearmingPlaceCoord then
return true
else
return false
end
end
function ARTY:onafterRearm(Controllable,From,Event,To)
self:_EventFromTo("onafterRearm",Event,From,To)
local coordARTY=self.Controllable:GetCoordinate()
self.InitialCoord=coordARTY
local coordRARM=nil
if self.RearmingGroup then
coordRARM=self.RearmingGroup:GetCoordinate()
self.RearmingGroupCoord=coordRARM
end
if self.RearmingGroup and self.RearmingPlaceCoord and self.ismobile then
local text=string.format("%s, %s, request rearming at rearming place.",Controllable:GetName(),self.RearmingGroup:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord)
local dR=coordRARM:Get2DDistance(self.RearmingPlaceCoord)
if dA>self.RearmingDistance then
local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord,self.RearmingDistance/4,self.RearmingDistance/2)
self:AssignMoveCoord(_tocoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE TO REARMING PLACE",true)
end
if dR>self.RearmingDistance then
local ToCoord=self:_VicinityCoord(self.RearmingPlaceCoord,self.RearmingDistance/4,self.RearmingDistance/2)
self:_Move(self.RearmingGroup,ToCoord,self.RearmingGroupSpeed,self.RearmingGroupOnRoad)
end
elseif self.RearmingGroup then
local text=string.format("%s, %s, request rearming.",Controllable:GetName(),self.RearmingGroup:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
local distance=coordARTY:Get2DDistance(coordRARM)
if distance>self.RearmingDistance then
self:_Move(self.RearmingGroup,self:_VicinityCoord(coordARTY),self.RearmingGroupSpeed,self.RearmingGroupOnRoad)
end
elseif self.RearmingPlaceCoord then
local text=string.format("%s, moving to rearming place.",Controllable:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord)
if dA>self.RearmingDistance then
local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord)
self:AssignMoveCoord(_tocoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE TO REARMING PLACE",true)
end
end
end
function ARTY:onafterRearmed(Controllable,From,Event,To)
self:_EventFromTo("onafterRearmed",Event,From,To)
local text=string.format("%s, rearming complete.",Controllable:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
self.Nukes=self.Nukes0
self.Nillu=self.Nillu0
self.Nsmoke=self.Nsmoke0
local dist=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord)
if dist>self.RearmingDistance then
self:AssignMoveCoord(self.InitialCoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE REARMING COMPLETE",true)
end
if self.RearmingGroup and self.RearmingGroup:IsAlive()then
local d=self.RearmingGroup:GetCoordinate():Get2DDistance(self.RearmingGroupCoord)
if d>self.RearmingDistance then
self:_Move(self.RearmingGroup,self.RearmingGroupCoord,self.RearmingGroupSpeed,self.RearmingGroupOnRoad)
else
self.RearmingGroup:ClearTasks()
end
end
end
function ARTY:_CheckRearmed()
self:F2()
local nammo,nshells,nrockets,nmissiles=self:GetAmmo()
local units=self.Controllable:GetUnits()
local nunits=0
if units then
nunits=#units
end
local FullAmmo=self.Nammo0*nunits/self.IniGroupStrength
local _rearmpc=nammo/FullAmmo*100
if _rearmpc>1 then
local text=string.format("%s, rearming %d %% complete.",self.alias,_rearmpc)
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
end
if nammo>=FullAmmo then
return true
else
return false
end
end
function ARTY:onbeforeMove(Controllable,From,Event,To,move)
self:_EventFromTo("onbeforeMove",Event,From,To)
if not self.ismobile then
return false
end
if self.currentTarget then
if move.cancel then
self:CeaseFire(self.currentTarget)
else
return false
end
end
return true
end
function ARTY:onafterMove(Controllable,From,Event,To,move)
self:_EventFromTo("onafterMove",Event,From,To)
self.Controllable:OptionAlarmStateGreen()
self.Controllable:OptionROEHoldFire()
local _Speed=math.min(move.speed,self.SpeedMax)
if self.Debug then
move.coord:SmokeRed()
end
self.currentMove=move
self:_Move(self.Controllable,move.coord,move.speed,move.onroad)
end
function ARTY:onafterArrived(Controllable,From,Event,To)
self:_EventFromTo("onafterArrived",Event,From,To)
self.Controllable:OptionAlarmStateAuto()
local text=string.format("%s, arrived at destination.",Controllable:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
if self.currentMove then
self:RemoveMove(self.currentMove.name)
self.currentMove=nil
end
end
function ARTY:onafterNewTarget(Controllable,From,Event,To,target)
self:_EventFromTo("onafterNewTarget",Event,From,To)
local text=string.format("Adding new target %s.",target.name)
MESSAGE:New(text,5):ToAllIf(self.Debug)
self:T(self.lid..text)
end
function ARTY:onafterNewMove(Controllable,From,Event,To,move)
self:_EventFromTo("onafterNewTarget",Event,From,To)
local text=string.format("Adding new move %s.",move.name)
MESSAGE:New(text,5):ToAllIf(self.Debug)
self:T(self.lid..text)
end
function ARTY:onafterDead(Controllable,From,Event,To,Unitname)
self:_EventFromTo("onafterDead",Event,From,To)
local nunits=self.Controllable:CountAliveUnits()
local text=string.format("%s, our unit %s just died! %d units left.",self.groupname,Unitname,nunits)
MESSAGE:New(text,5):ToAllIf(self.Debug)
self:I(self.lid..text)
if nunits==0 then
if self.currentTarget then
self:CeaseFire(self.currentTarget)
end
if self.respawnafterdeath then
if not self.respawning then
self.respawning=true
self:__Respawn(self.respawndelay or 1)
end
else
self:Stop()
end
end
end
function ARTY:onafterRespawn(Controllable,From,Event,To)
self:_EventFromTo("onafterRespawn",Event,From,To)
env.info("FF Respawning arty group")
local group=self.Controllable
self.Controllable=group:Respawn()
self.respawning=false
self:__Status(-1)
end
function ARTY:onafterStop(Controllable,From,Event,To)
self:_EventFromTo("onafterStop",Event,From,To)
self:I(self.lid..string.format("Stopping ARTY FSM for group %s.",tostring(Controllable:GetName())))
if self.currentTarget then
self:CeaseFire(self.currentTarget)
end
self:UnHandleEvent(EVENTS.Shot)
self:UnHandleEvent(EVENTS.Dead)
end
function ARTY:_FireAtCoord(coord,radius,nshells,weapontype)
self:F({coord=coord,radius=radius,nshells=nshells})
local group=self.Controllable
if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then
weapontype=ARTY.WeaponType.Cannon
end
group:OptionROEOpenFire()
local vec2=coord:GetVec2()
local fire=group:TaskFireAtPoint(vec2,radius,nshells,weapontype)
group:SetTask(fire)
end
function ARTY:_AttackGroup(target)
local group=self.Controllable
local weapontype=target.weapontype
if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then
weapontype=ARTY.WeaponType.Cannon
end
group:OptionROEOpenFire()
local targetgroup=GROUP:FindByName(target.name)
local fire=group:TaskAttackGroup(targetgroup,weapontype,AI.Task.WeaponExpend.ONE,1)
group:SetTask(fire)
end
function ARTY:_NuclearBlast(_coord)
local S0=self.nukewarhead
local R0=self.nukerange
local N0=self.nukefires
_coord:Explosion(S0)
_coord:BigSmokeAndFireHuge()
local _fires={}
for i=1,N0 do
local _fire=_coord:GetRandomCoordinateInRadius(R0)
local _dist=_fire:Get2DDistance(_coord)
table.insert(_fires,{distance=_dist,coord=_fire})
end
local _sort=function(a,b)return a.distance<b.distance end
table.sort(_fires,_sort)
local function _explosion(R)
local alpha=math.log(100)
local strength=S0*math.exp(-alpha*R/R0)
self:T2(self.lid..string.format("Nuclear explosion strength s(%.1f m) = %.5f (s/s0=%.1f %%), alpha=%.3f",R,strength,strength/S0*100,alpha))
return strength
end
local function ignite(_fires)
for _,fire in pairs(_fires)do
local _fire=fire.coord
local R=_fire:Get2DDistance(_coord)
local S=_explosion(R)
self:T2(self.lid..string.format("Explosion r=%.1f, s=%.3f",R,S))
local _preset=math.random(0,7)
local _density=S/S0
_fire:BigSmokeAndFire(_preset,_density)
_fire:Explosion(S)
end
end
if self.nukefire==true then
ignite(_fires)
end
end
function ARTY:_Move(group,ToCoord,Speed,OnRoad)
self:F2({group=group:GetName(),Speed=Speed,OnRoad=OnRoad})
group:ClearTasks()
group:OptionAlarmStateGreen()
group:OptionROEHoldFire()
local formation="Off Road"
local SpeedMax=group:GetSpeedMax()
Speed=Speed or SpeedMax*0.7
Speed=math.min(Speed,SpeedMax)
local cpini=group:GetCoordinate()
local dist=cpini:Get2DDistance(ToCoord)
local path={}
local task={}
path[#path+1]=cpini:WaypointGround(Speed,formation)
task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint",self,#path-1,false)
if OnRoad then
local _pathonroad=cpini:GetPathOnRoad(ToCoord)
if _pathonroad then
local _first=_pathonroad[1]
local _last=_pathonroad[#_pathonroad]
if self.Debug then
_first:SmokeGreen()
_last:SmokeGreen()
end
path[#path+1]=_first:WaypointGround(Speed,"On Road")
task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint",self,#path-1,false)
path[#path+1]=_last:WaypointGround(Speed,"On Road")
task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint",self,#path-1,false)
end
end
path[#path+1]=ToCoord:WaypointGround(Speed,formation)
task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint",self,#path-1,true)
local Waypoints={}
for i=1,#path do
table.insert(Waypoints,i,path[i])
end
for i=1,#Waypoints do
group:SetTaskWaypoint(Waypoints[i],task[i])
end
group:Route(Waypoints)
end
function ARTY._PassingWaypoint(group,arty,i,final)
if group and group:IsAlive()then
local groupname=tostring(group:GetName())
local text=string.format("%s, passing waypoint %d.",groupname,i)
if final then
text=string.format("%s, arrived at destination.",groupname)
end
arty:T(arty.lid..text)
if final and arty.groupname==groupname then
arty:Arrived()
end
end
end
function ARTY:_Relocate()
local _pos=self.Controllable:GetCoordinate()
local _new=nil
local _gotit=false
local _n=0
local _nmax=1000
repeat
_new=_pos:GetRandomCoordinateInRadius(self.relocateRmax,self.relocateRmin)
local _surface=_new:GetSurfaceType()
if _surface~=land.SurfaceType.WATER and _surface~=land.SurfaceType.SHALLOW_WATER then
_gotit=true
end
_n=_n+1
until _gotit or _n>_nmax
if _gotit then
self:AssignMoveCoord(_new,nil,nil,false,false,"RELOCATION MOVE AFTER FIRING")
end
end
function ARTY:GetAmmo(display)
self:F3({display=display})
if display==nil then
display=false
end
local nammo=0
local nshells=0
local nrockets=0
local nmissiles=0
local units=self.Controllable:GetUnits()
if units==nil then
return nammo,nshells,nrockets,nmissiles
end
for _,unit in pairs(units)do
if unit and unit:IsAlive()then
local text=string.format("ARTY group %s - unit %s:\n",self.groupname,unit:GetName())
local ammotable=unit:GetAmmo()
if ammotable~=nil then
local weapons=#ammotable
if display then
self:I(self.lid..string.format("Number of weapons %d.",weapons))
self:I({ammotable=ammotable})
self:I(self.lid.."Ammotable:")
for id,bla in pairs(ammotable)do
self:I({id=id,ammo=bla})
end
end
for w=1,weapons do
local Nammo=ammotable[w]["count"]
local Tammo=ammotable[w]["desc"]["typeName"]
local _weaponString=self:_split(Tammo,"%.")
local _weaponName=_weaponString[#_weaponString]
local Category=ammotable[w].desc.category
local MissileCategory=nil
if Category==Weapon.Category.MISSILE then
MissileCategory=ammotable[w].desc.missileCategory
end
local _gotshell=false
if#self.ammoshells>0 then
for _,_type in pairs(self.ammoshells)do
if string.match(Tammo,_type)and Category==Weapon.Category.SHELL then
_gotshell=true
end
end
else
if Category==Weapon.Category.SHELL then
_gotshell=true
end
end
local _gotrocket=false
if#self.ammorockets>0 then
for _,_type in pairs(self.ammorockets)do
if string.match(Tammo,_type)and Category==Weapon.Category.ROCKET then
_gotrocket=true
end
end
else
if Category==Weapon.Category.ROCKET then
_gotrocket=true
end
end
local _gotmissile=false
if#self.ammomissiles>0 then
for _,_type in pairs(self.ammomissiles)do
if string.match(Tammo,_type)and Category==Weapon.Category.MISSILE then
_gotmissile=true
end
end
else
if Category==Weapon.Category.MISSILE then
_gotmissile=true
end
end
if _gotshell then
nshells=nshells+Nammo
text=text..string.format("- %d shells of type %s\n",Nammo,_weaponName)
elseif _gotrocket then
nrockets=nrockets+Nammo
text=text..string.format("- %d rockets of type %s\n",Nammo,_weaponName)
elseif _gotmissile then
if MissileCategory==Weapon.MissileCategory.CRUISE then
nmissiles=nmissiles+Nammo
end
text=text..string.format("- %d %s missiles of type %s\n",Nammo,self:_MissileCategoryName(MissileCategory),_weaponName)
else
text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n",Nammo,Tammo,Category,tostring(MissileCategory))
end
end
end
if display then
self:I(self.lid..text)
else
self:T3(self.lid..text)
end
MESSAGE:New(text,10):ToAllIf(display)
end
end
nammo=nshells+nrockets+nmissiles
return nammo,nshells,nrockets,nmissiles
end
function ARTY:_MissileCategoryName(categorynumber)
local cat="unknown"
if categorynumber==Weapon.MissileCategory.AAM then
cat="air-to-air"
elseif categorynumber==Weapon.MissileCategory.SAM then
cat="surface-to-air"
elseif categorynumber==Weapon.MissileCategory.BM then
cat="ballistic"
elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then
cat="anti-ship"
elseif categorynumber==Weapon.MissileCategory.CRUISE then
cat="cruise"
elseif categorynumber==Weapon.MissileCategory.OTHER then
cat="other"
end
return cat
end
function ARTY:_MarkerKeyAuthentification(text)
local batterycoalition=self.coalition
local mykey=nil
if self.markkey~=nil then
local keywords=self:_split(text,",")
for _,key in pairs(keywords)do
local s=self:_split(key," ")
local val=s[2]
if key:lower():find("key")then
mykey=tonumber(val)
self:T(self.lid..string.format("Authorisation Key=%s.",val))
end
end
end
local _validkey=true
if self.markkey~=nil then
_validkey=false
if mykey~=nil then
_validkey=self.markkey==mykey
end
self:T2(self.lid..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s",self.groupname,tostring(self.markkey),tostring(mykey),tostring(_validkey)))
local text=""
if mykey==nil then
text=string.format("%s, authorization required but did not receive a key!",self.alias)
elseif _validkey==false then
text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!",self.alias,tostring(mykey))
elseif _validkey==true then
text=string.format("%s, authentification successful!",self.alias)
end
MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug)
end
return _validkey
end
function ARTY:_Markertext(text)
self:F(text)
local assignment={}
assignment.battery={}
assignment.aliases={}
assignment.cluster={}
assignment.everyone=false
assignment.move=false
assignment.engage=false
assignment.request=false
assignment.cancel=false
assignment.set=false
assignment.readonly=false
assignment.movecanceltarget=false
assignment.cancelmove=false
assignment.canceltarget=false
assignment.cancelrearm=false
assignment.setrearmingplace=false
assignment.setrearminggroup=false
if text:lower():find("arty engage")or text:lower():find("arty attack")then
assignment.engage=true
elseif text:lower():find("arty move")or text:lower():find("arty relocate")then
assignment.move=true
elseif text:lower():find("arty request")then
assignment.request=true
elseif text:lower():find("arty cancel")then
assignment.cancel=true
elseif text:lower():find("arty set")then
assignment.set=true
else
self:E(self.lid..'ERROR: Neither "ARTY ENGAGE" nor "ARTY MOVE" nor "ARTY RELOCATE" nor "ARTY REQUEST" nor "ARTY CANCEL" nor "ARTY SET" keyword specified!')
return nil
end
local keywords=self:_split(text,",")
self:T({keywords=keywords})
for _,keyphrase in pairs(keywords)do
local str=self:_split(keyphrase," ")
local key=str[1]
local val=str[2]
self:T3(self.lid..string.format("%s, keyphrase = %s, key = %s, val = %s",self.groupname,tostring(keyphrase),tostring(key),tostring(val)))
if key:lower():find("battery")then
local v=self:_split(keyphrase,'"')
for i=2,#v,2 do
table.insert(assignment.battery,v[i])
self:T2(self.lid..string.format("Key Battery=%s.",v[i]))
end
elseif key:lower():find("alias")then
local v=self:_split(keyphrase,'"')
for i=2,#v,2 do
table.insert(assignment.aliases,v[i])
self:T2(self.lid..string.format("Key Aliases=%s.",v[i]))
end
elseif key:lower():find("cluster")then
local v=self:_split(keyphrase,'"')
for i=2,#v,2 do
table.insert(assignment.cluster,v[i])
self:T2(self.lid..string.format("Key Cluster=%s.",v[i]))
end
elseif keyphrase:lower():find("everyone")or keyphrase:lower():find("all batteries")or keyphrase:lower():find("allbatteries")then
assignment.everyone=true
self:T(self.lid..string.format("Key Everyone=true."))
elseif keyphrase:lower():find("irrevocable")or keyphrase:lower():find("readonly")then
assignment.readonly=true
self:T2(self.lid..string.format("Key Readonly=true."))
elseif(assignment.engage or assignment.move)and key:lower():find("time")then
if val:lower():find("now")then
assignment.time=self:_SecondsToClock(timer.getTime0()+2)
else
assignment.time=val
end
self:T2(self.lid..string.format("Key Time=%s.",val))
elseif assignment.engage and key:lower():find("shot")then
assignment.nshells=tonumber(val)
self:T(self.lid..string.format("Key Shot=%s.",val))
elseif assignment.engage and key:lower():find("prio")then
assignment.prio=tonumber(val)
self:T2(string.format("Key Prio=%s.",val))
elseif assignment.engage and key:lower():find("maxengage")then
assignment.maxengage=tonumber(val)
self:T2(self.lid..string.format("Key Maxengage=%s.",val))
elseif assignment.engage and key:lower():find("radius")then
assignment.radius=tonumber(val)
self:T2(self.lid..string.format("Key Radius=%s.",val))
elseif assignment.engage and key:lower():find("weapon")then
if val:lower():find("cannon")then
assignment.weapontype=ARTY.WeaponType.Cannon
elseif val:lower():find("rocket")then
assignment.weapontype=ARTY.WeaponType.Rockets
elseif val:lower():find("missile")then
assignment.weapontype=ARTY.WeaponType.CruiseMissile
elseif val:lower():find("nuke")then
assignment.weapontype=ARTY.WeaponType.TacticalNukes
elseif val:lower():find("illu")then
assignment.weapontype=ARTY.WeaponType.IlluminationShells
elseif val:lower():find("smoke")then
assignment.weapontype=ARTY.WeaponType.SmokeShells
else
assignment.weapontype=ARTY.WeaponType.Auto
end
self:T2(self.lid..string.format("Key Weapon=%s.",val))
elseif(assignment.move or assignment.set)and key:lower():find("speed")then
assignment.speed=tonumber(val)
self:T2(self.lid..string.format("Key Speed=%s.",val))
elseif(assignment.move or assignment.set)and(keyphrase:lower():find("on road")or keyphrase:lower():find("onroad")or keyphrase:lower():find("use road"))then
assignment.onroad=true
self:T2(self.lid..string.format("Key Onroad=true."))
elseif assignment.move and(keyphrase:lower():find("cancel target")or keyphrase:lower():find("canceltarget"))then
assignment.movecanceltarget=true
self:T2(self.lid..string.format("Key Cancel Target (before move)=true."))
elseif assignment.request and keyphrase:lower():find("rearm")then
assignment.requestrearming=true
self:T2(self.lid..string.format("Key Request Rearming=true."))
elseif assignment.request and keyphrase:lower():find("ammo")then
assignment.requestammo=true
self:T2(self.lid..string.format("Key Request Ammo=true."))
elseif assignment.request and keyphrase:lower():find("target")then
assignment.requesttargets=true
self:T2(self.lid..string.format("Key Request Targets=true."))
elseif assignment.request and keyphrase:lower():find("status")then
assignment.requeststatus=true
self:T2(self.lid..string.format("Key Request Status=true."))
elseif assignment.request and(keyphrase:lower():find("move")or keyphrase:lower():find("relocation"))then
assignment.requestmoves=true
self:T2(self.lid..string.format("Key Request Moves=true."))
elseif assignment.cancel and(keyphrase:lower():find("engagement")or keyphrase:lower():find("attack")or keyphrase:lower():find("target"))then
assignment.canceltarget=true
self:T2(self.lid..string.format("Key Cancel Target=true."))
elseif assignment.cancel and(keyphrase:lower():find("move")or keyphrase:lower():find("relocation"))then
assignment.cancelmove=true
self:T2(self.lid..string.format("Key Cancel Move=true."))
elseif assignment.cancel and keyphrase:lower():find("rearm")then
assignment.cancelrearm=true
self:T2(self.lid..string.format("Key Cancel Rearm=true."))
elseif assignment.set and keyphrase:lower():find("rearming place")then
assignment.setrearmingplace=true
self:T(self.lid..string.format("Key Set Rearming Place=true."))
elseif assignment.set and keyphrase:lower():find("rearming group")then
local v=self:_split(keyphrase,'"')
local groupname=v[2]
local group=GROUP:FindByName(groupname)
if group and group:IsAlive()then
assignment.setrearminggroup=group
end
self:T2(self.lid..string.format("Key Set Rearming Group = %s.",tostring(groupname)))
elseif key:lower():find("lldms")then
local _flat="%d+:%d+:%d+%s*[N,S]"
local _flon="%d+:%d+:%d+%s*[W,E]"
local _lat=keyphrase:match(_flat)
local _lon=keyphrase:match(_flon)
self:T2(self.lid..string.format("Key LLDMS: lat=%s, long=%s  format=DMS",_lat,_lon))
if _lat and _lon then
local _latitude,_longitude=self:_LLDMS2DD(_lat,_lon)
self:T2(self.lid..string.format("Key LLDMS: lat=%.3f, long=%.3f  format=DD",_latitude,_longitude))
if _latitude and _longitude then
assignment.coord=COORDINATE:NewFromLLDD(_latitude,_longitude)
end
end
end
end
return assignment
end
function ARTY:_MarkRequestAmmo()
self:GetAmmo(true)
end
function ARTY:_MarkRequestStatus()
self:_StatusReport(true)
end
function ARTY:_MarkRequestMoves()
local text=string.format("%s, relocations:",self.groupname)
if#self.moves>0 then
for _,move in pairs(self.moves)do
if self.currentMove and move.name==self.currentMove.name then
text=text..string.format("\n- %s (current)",self:_MoveInfo(move))
else
text=text..string.format("\n- %s",self:_MoveInfo(move))
end
end
else
text=text..string.format("\n- no queued relocations")
end
MESSAGE:New(text,20):Clear():ToCoalition(self.coalition)
end
function ARTY:_MarkRequestTargets()
local text=string.format("%s, targets:",self.groupname)
if#self.targets>0 then
for _,target in pairs(self.targets)do
if self.currentTarget and target.name==self.currentTarget.name then
text=text..string.format("\n- %s (current)",self:_TargetInfo(target))
else
text=text..string.format("\n- %s",self:_TargetInfo(target))
end
end
else
text=text..string.format("\n- no queued targets")
end
MESSAGE:New(text,20):Clear():ToCoalition(self.coalition)
end
function ARTY:_MarkTargetName(markerid)
return string.format("BATTERY=%s, Marked Target ID=%d",self.groupname,markerid)
end
function ARTY:_MarkMoveName(markerid)
return string.format("BATTERY=%s, Marked Relocation ID=%d",self.groupname,markerid)
end
function ARTY:_GetMarkIDfromName(name)
local keywords=self:_split(name,",")
local battery=nil
local markTID=nil
local markMID=nil
for _,key in pairs(keywords)do
local str=self:_split(key,"=")
local par=str[1]
local val=str[2]
if par:find("BATTERY")then
battery=val
end
if par:find("Marked Target ID")then
markTID=tonumber(val)
end
if par:find("Marked Relocation ID")then
markMID=tonumber(val)
end
end
return battery,markTID,markMID
end
function ARTY:_SortTargetQueuePrio()
self:F2()
local function _sort(a,b)
return(a.engaged<b.engaged)or(a.engaged==b.engaged and a.prio<b.prio)
end
table.sort(self.targets,_sort)
self:T3(self.lid.."Sorted targets wrt prio and number of engagements:")
for i=1,#self.targets do
local _target=self.targets[i]
self:T3(self.lid..string.format("Target %s",self:_TargetInfo(_target)))
end
end
function ARTY:_SortQueueTime(queue)
self:F3({queue=queue})
local function _sort(a,b)
if a.time==nil and b.time==nil then
return false
end
if a.time==nil then
return false
end
if b.time==nil then
return true
end
return a.time<b.time
end
table.sort(queue,_sort)
self:T3(self.lid.."Sorted queue wrt time:")
for i=1,#queue do
local _queue=queue[i]
local _time=tostring(_queue.time)
local _clock=tostring(self:_SecondsToClock(_queue.time))
self:T3(self.lid..string.format("%s: time=%s, clock=%s",_queue.name,_time,_clock))
end
end
function ARTY:_GetHeading(a,b)
local dx=b.x-a.x
local dy=b.z-a.z
local angle=math.deg(math.atan2(dy,dx))
if angle<0 then
angle=360+angle
end
return angle
end
function ARTY:_CheckTargetsInRange()
local targets2delete={}
for i=1,#self.targets do
local _target=self.targets[i]
self:T3(self.lid..string.format("Before: Target %s - in range = %s",_target.name,tostring(_target.inrange)))
local _inrange,_toofar,_tooclose,_remove=self:_TargetInRange(_target)
self:T3(self.lid..string.format("Inbetw: Target %s - in range = %s, toofar = %s, tooclose = %s",_target.name,tostring(_target.inrange),tostring(_toofar),tostring(_tooclose)))
if _remove then
table.insert(targets2delete,_target.name)
else
local _movetowards=false
local _moveaway=false
if _target.inrange==nil then
_target.inrange,_toofar,_tooclose=self:_TargetInRange(_target,self.report or self.Debug)
if _toofar then
_movetowards=true
elseif _tooclose then
_moveaway=true
end
elseif _target.inrange==true then
if _toofar then
_movetowards=true
elseif _tooclose then
_moveaway=true
end
elseif _target.inrange==false then
if _inrange then
local text=string.format("%s, target %s is now in range.",self.alias,_target.name)
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
end
end
if self.autorelocate and(_movetowards or _moveaway)then
local _from=self.Controllable:GetCoordinate()
local _dist=_from:Get2DDistance(_target.coord)
if _dist<=self.autorelocatemaxdist then
local _tocoord
local _name=""
local _safetymargin=500
if _movetowards then
local _waytogo=_dist-self.maxrange+_safetymargin
local _heading=self:_GetHeading(_from,_target.coord)
_tocoord=_from:Translate(_waytogo,_heading)
_name=string.format("%s, relocation to within max firing range of target %s",self.alias,_target.name)
elseif _moveaway then
local _waytogo=_dist-self.minrange+_safetymargin
local _heading=self:_GetHeading(_target.coord,_from)
_tocoord=_from:Translate(_waytogo,_heading)
_name=string.format("%s, relocation to within min firing range of target %s",self.alias,_target.name)
end
MESSAGE:New(_name.." assigned.",10):ToCoalitionIf(self.coalition,self.report or self.Debug)
self:AssignMoveCoord(_tocoord,nil,nil,self.autorelocateonroad,false,_name,true)
end
end
_target.inrange=_inrange
self:T3(self.lid..string.format("After: Target %s - in range = %s",_target.name,tostring(_target.inrange)))
end
end
for _,targetname in pairs(targets2delete)do
self:RemoveTarget(targetname)
end
end
function ARTY:_CheckNormalTargets()
self:F3()
self:_SortTargetQueuePrio()
if self:is("Rearming")then
return nil
end
for i=1,#self.targets do
local _target=self.targets[i]
self:T3(self.lid..string.format("Check NORMAL target %d: %s",i,self:_TargetInfo(_target)))
if _target.underfire==false and _target.time==nil and _target.maxengage>_target.engaged and self:_TargetInRange(_target)and self:_CheckWeaponTypeAvailable(_target)>0 then
self:T2(self.lid..string.format("Found NORMAL target %s",self:_TargetInfo(_target)))
return _target
end
end
return nil
end
function ARTY:_CheckTimedTargets()
self:F3()
local Tnow=timer.getAbsTime()
self:_SortQueueTime(self.targets)
if self:is("Rearming")then
return nil
end
for i=1,#self.targets do
local _target=self.targets[i]
self:T3(self.lid..string.format("Check TIMED target %d: %s",i,self:_TargetInfo(_target)))
if _target.time and Tnow>=_target.time and _target.underfire==false and self:_TargetInRange(_target)and self:_CheckWeaponTypeAvailable(_target)>0 then
if self.currentTarget then
if self.currentTarget.prio>_target.prio then
self:T2(self.lid..string.format("Found TIMED HIGH PRIO target %s.",self:_TargetInfo(_target)))
return _target
end
else
self:T2(self.lid..string.format("Found TIMED target %s.",self:_TargetInfo(_target)))
return _target
end
end
end
return nil
end
function ARTY:_CheckMoves()
self:F3()
local Tnow=timer.getAbsTime()
self:_SortQueueTime(self.moves)
local firing=false
if self.currentTarget then
firing=true
end
for i=1,#self.moves do
local _move=self.moves[i]
if string.find(_move.name,"REARMING MOVE")and((self.currentMove and self.currentMove.name~=_move.name)or self.currentMove==nil)then
return _move
elseif(Tnow>=_move.time)and(firing==false or _move.cancel)and(not self.currentMove)and(not self:is("Rearming"))then
return _move
end
end
return nil
end
function ARTY:_CheckShootingStarted()
self:F2()
if self.currentTarget then
local Tnow=timer.getTime()
local name=self.currentTarget.name
local dt=Tnow-self.currentTarget.Tassigned
if self.Nshots==0 then
self:T(self.lid..string.format("%s, waiting for %d seconds for first shot on target %s.",self.groupname,dt,name))
end
if dt>self.WaitForShotTime and(self.Nshots==0 or self.currentTarget.nshells>=self.Nshots)then
self:T(self.lid..string.format("%s, no shot event after %d seconds. Removing current target %s from list.",self.groupname,self.WaitForShotTime,name))
self:CeaseFire(self.currentTarget)
self:RemoveTarget(name)
end
end
end
function ARTY:_GetTargetIndexByName(name)
self:F2(name)
for i=1,#self.targets do
local targetname=self.targets[i].name
self:T3(self.lid..string.format("Have target with name %s. Index = %d",targetname,i))
if targetname==name then
self:T2(self.lid..string.format("Found target with name %s. Index = %d",name,i))
return i
end
end
self:T2(self.lid..string.format("WARNING: Target with name %s could not be found. (This can happen.)",name))
return nil
end
function ARTY:_GetMoveIndexByName(name)
self:F2(name)
for i=1,#self.moves do
local movename=self.moves[i].name
self:T3(self.lid..string.format("Have move with name %s. Index = %d",movename,i))
if movename==name then
self:T2(self.lid..string.format("Found move with name %s. Index = %d",name,i))
return i
end
end
self:T2(self.lid..string.format("WARNING: Move with name %s could not be found. (This can happen.)",name))
return nil
end
function ARTY:_CheckOutOfAmmo(targets)
local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo()
local _partlyoutofammo=false
for _,Target in pairs(targets)do
if Target.weapontype==ARTY.WeaponType.Auto and _nammo==0 then
self:T(self.lid..string.format("Group %s, auto weapon requested for target %s but all ammo is empty.",self.groupname,Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.Cannon and _nshells==0 then
self:T(self.lid..string.format("Group %s, cannons requested for target %s but shells empty.",self.groupname,Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes<=0 then
self:T(self.lid..string.format("Group %s, tactical nukes requested for target %s but nukes empty.",self.groupname,Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu<=0 then
self:T(self.lid..string.format("Group %s, illumination shells requested for target %s but illumination shells empty.",self.groupname,Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke<=0 then
self:T(self.lid..string.format("Group %s, smoke shells requested for target %s but smoke shells empty.",self.groupname,Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.Rockets and _nrockets==0 then
self:T(self.lid..string.format("Group %s, rockets requested for target %s but rockets empty.",self.groupname,Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.CruiseMissile and _nmissiles==0 then
self:T(self.lid..string.format("Group %s, cruise missiles requested for target %s but all missiles empty.",self.groupname,Target.name))
_partlyoutofammo=true
end
end
return _partlyoutofammo
end
function ARTY:_CheckWeaponTypeAvailable(target)
local Nammo,Nshells,Nrockets,Nmissiles=self:GetAmmo()
local nfire=Nammo
if target.weapontype==ARTY.WeaponType.Auto then
nfire=Nammo
elseif target.weapontype==ARTY.WeaponType.Cannon then
nfire=Nshells
elseif target.weapontype==ARTY.WeaponType.TacticalNukes then
nfire=self.Nukes
elseif target.weapontype==ARTY.WeaponType.IlluminationShells then
nfire=self.Nillu
elseif target.weapontype==ARTY.WeaponType.SmokeShells then
nfire=self.Nsmoke
elseif target.weapontype==ARTY.WeaponType.Rockets then
nfire=Nrockets
elseif target.weapontype==ARTY.WeaponType.CruiseMissile then
nfire=Nmissiles
end
return nfire
end
function ARTY:_CheckWeaponTypePossible(target)
local possible=false
if target.weapontype==ARTY.WeaponType.Auto then
possible=self.Nammo0>0
elseif target.weapontype==ARTY.WeaponType.Cannon then
possible=self.Nshells0>0
elseif target.weapontype==ARTY.WeaponType.TacticalNukes then
possible=self.Nukes0>0
elseif target.weapontype==ARTY.WeaponType.IlluminationShells then
possible=self.Nillu0>0
elseif target.weapontype==ARTY.WeaponType.SmokeShells then
possible=self.Nsmoke0>0
elseif target.weapontype==ARTY.WeaponType.Rockets then
possible=self.Nrockets0>0
elseif target.weapontype==ARTY.WeaponType.CruiseMissile then
possible=self.Nmissiles0>0
end
return possible
end
function ARTY:_CheckName(givennames,name,makeunique)
self:F2({givennames=givennames,name=name})
local newname=name
local counter=1
local n=1
local nmax=100
if makeunique==nil then
makeunique=true
end
repeat
local _unique=true
for _,_target in pairs(givennames)do
local _givenname=_target.name
if _givenname==newname then
_unique=false
end
self:T3(self.lid..string.format("%d: givenname = %s, newname=%s, unique = %s, makeunique = %s",n,tostring(_givenname),newname,tostring(_unique),tostring(makeunique)))
end
if _unique==false and makeunique==true then
newname=string.format("%s #%02d",name,counter)
counter=counter+1
end
if _unique==false and makeunique==false then
self:T3(self.lid..string.format("Name %s is not unique. Return false.",tostring(newname)))
return name,false
end
n=n+1
until(_unique or n==nmax)
self:T3(self.lid..string.format("Original name %s, new name = %s",name,newname))
return newname,true
end
function ARTY:_TargetInRange(target,message)
self:F3(target)
if message==nil then
message=false
end
self:T3({controllable=self.Controllable,targetcoord=target.coord})
local _dist=self.Controllable:GetCoordinate():Get2DDistance(target.coord)
local _inrange=true
local _tooclose=false
local _toofar=false
local text=""
if _dist<self.minrange then
_inrange=false
_tooclose=true
text=string.format("%s, target is out of range. Distance of %.1f km is below min range of %.1f km.",self.alias,_dist/1000,self.minrange/1000)
elseif _dist>self.maxrange then
_inrange=false
_toofar=true
text=string.format("%s, target is out of range. Distance of %.1f km is greater than max range of %.1f km.",self.alias,_dist/1000,self.maxrange/1000)
end
if not _inrange then
self:T(self.lid..text)
MESSAGE:New(text,5):ToCoalitionIf(self.coalition,(self.report and message)or(self.Debug and message))
end
local _remove=false
if not(self.ismobile or self.iscargo)and _inrange==false then
_remove=true
end
return _inrange,_toofar,_tooclose,_remove
end
function ARTY:_WeaponTypeName(tnumber)
self:F2(tnumber)
local name="unknown"
if tnumber==ARTY.WeaponType.Auto then
name="Auto"
elseif tnumber==ARTY.WeaponType.Cannon then
name="Cannons"
elseif tnumber==ARTY.WeaponType.Rockets then
name="Rockets"
elseif tnumber==ARTY.WeaponType.CruiseMissile then
name="Cruise Missiles"
elseif tnumber==ARTY.WeaponType.TacticalNukes then
name="Tactical Nukes"
elseif tnumber==ARTY.WeaponType.IlluminationShells then
name="Illumination Shells"
elseif tnumber==ARTY.WeaponType.SmokeShells then
name="Smoke Shells"
end
return name
end
function ARTY:_VicinityCoord(coord,rmin,rmax)
self:F2({coord=coord,rmin=rmin,rmax=rmax})
rmin=rmin or 20
rmax=rmax or 80
local vec2=coord:GetRandomVec2InRadius(rmax,rmin)
local pops=COORDINATE:NewFromVec2(vec2)
self:T3(self.lid..string.format("Vicinity distance = %d (rmin=%d, rmax=%d)",pops:Get2DDistance(coord),rmin,rmax))
return pops
end
function ARTY:_EventFromTo(BA,Event,From,To)
local text=string.format("%s: %s EVENT %s: %s --> %s",BA,self.groupname,Event,From,To)
self:T3(self.lid..text)
end
function ARTY:_split(str,sep)
self:F3({str=str,sep=sep})
local result={}
local regex=("([^%s]+)"):format(sep)
for each in str:gmatch(regex)do
table.insert(result,each)
end
return result
end
function ARTY:_TargetInfo(target)
local clock=tostring(self:_SecondsToClock(target.time))
local weapon=self:_WeaponTypeName(target.weapontype)
local _underfire=tostring(target.underfire)
return string.format("%s: prio=%d, radius=%d, nshells=%d, engaged=%d/%d, weapontype=%s, time=%s, underfire=%s, attackgroup=%s",
target.name,target.prio,target.radius,target.nshells,target.engaged,target.maxengage,weapon,clock,_underfire,tostring(target.attackgroup))
end
function ARTY:_MoveInfo(move)
self:F3(move)
local _clock=self:_SecondsToClock(move.time)
return string.format("%s: time=%s, speed=%d, onroad=%s, cancel=%s",move.name,_clock,move.speed,tostring(move.onroad),tostring(move.cancel))
end
function ARTY:_LLDMS2DD(l1,l2)
self:F2(l1,l2)
local _latlong={l1,l2}
local _latitude=nil
local _longitude=nil
for _,ll in pairs(_latlong)do
local _format="%d+:%d+:%d+"
local _ldms=ll:match(_format)
if _ldms then
local _dms=self:_split(_ldms,":")
local _deg=tonumber(_dms[1])
local _min=tonumber(_dms[2])
local _sec=tonumber(_dms[3])
local function DMS2DD(d,m,s)
return d+m/60+s/3600
end
if ll:match("N")then
_latitude=DMS2DD(_deg,_min,_sec)
elseif ll:match("S")then
_latitude=-DMS2DD(_deg,_min,_sec)
elseif ll:match("W")then
_longitude=-DMS2DD(_deg,_min,_sec)
elseif ll:match("E")then
_longitude=DMS2DD(_deg,_min,_sec)
end
local text=string.format("DMS %02d Deg %02d min %02d sec",_deg,_min,_sec)
self:T2(self.lid..text)
end
end
local text=string.format("\nLatitude  %s",tostring(_latitude))
text=text..string.format("\nLongitude %s",tostring(_longitude))
self:T2(self.lid..text)
return _latitude,_longitude
end
function ARTY:_SecondsToClock(seconds)
self:F3({seconds=seconds})
if seconds==nil then
return nil
end
local seconds=tonumber(seconds)
local _seconds=seconds%(60*60*24)
if seconds<=0 then
return nil
else
local hours=string.format("%02.f",math.floor(_seconds/3600))
local mins=string.format("%02.f",math.floor(_seconds/60-(hours*60)))
local secs=string.format("%02.f",math.floor(_seconds-hours*3600-mins*60))
local days=string.format("%d",seconds/(60*60*24))
return hours..":"..mins..":"..secs.."+"..days
end
end
function ARTY:_ClockToSeconds(clock)
self:F3({clock=clock})
if clock==nil then
return nil
end
local seconds=0
local dsplit=self:_split(clock,"+")
if#dsplit>1 then
seconds=seconds+tonumber(dsplit[2])*60*60*24
end
local tsplit=self:_split(dsplit[1],":")
local i=1
for _,time in ipairs(tsplit)do
if i==1 then
seconds=seconds+tonumber(time)*60*60
elseif i==2 then
seconds=seconds+tonumber(time)*60
elseif i==3 then
seconds=seconds+tonumber(time)
end
i=i+1
end
self:T3(self.lid..string.format("Clock %s = %d seconds",clock,seconds))
return seconds
end
SUPPRESSION={
ClassName="SUPPRESSION",
Debug=false,
lid=nil,
flare=false,
smoke=false,
DCSdesc=nil,
Type=nil,
IsInfantry=nil,
SpeedMax=nil,
Tsuppress_ave=15,
Tsuppress_min=5,
Tsuppress_max=25,
TsuppressOver=nil,
IniGroupStrength=nil,
Nhit=0,
Formation="Off road",
Speed=4,
MenuON=false,
FallbackON=false,
FallbackWait=60,
FallbackDist=100,
FallbackHeading=nil,
TakecoverON=false,
TakecoverWait=120,
TakecoverRange=300,
hideout=nil,
PminFlee=10,
PmaxFlee=90,
RetreatZone=nil,
RetreatDamage=nil,
RetreatWait=7200,
CurrentAlarmState="unknown",
CurrentROE="unknown",
DefaultAlarmState="Auto",
DefaultROE="Weapon Free",
eventmoose=true,
waypoints={},
}
SUPPRESSION.ROE={
Hold="Weapon Hold",
Free="Weapon Free",
Return="Return Fire",
}
SUPPRESSION.AlarmState={
Auto="Auto",
Green="Green",
Red="Red",
}
SUPPRESSION.MenuF10=nil
SUPPRESSION.version="0.9.4"
function SUPPRESSION:New(group)
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
if group then
self.lid=string.format("SUPPRESSION %s | ",tostring(group:GetName()))
self:T(self.lid..string.format("SUPPRESSION version %s. Activating suppressive fire for group %s",SUPPRESSION.version,group:GetName()))
else
self:E("SUPPRESSION | Requested group does not exist! (Has to be a MOOSE group)")
return nil
end
if group:IsGround()==false then
self:E(self.lid..string.format("SUPPRESSION fire group %s has to be a GROUND group!",group:GetName()))
return nil
end
self:SetControllable(group)
self.DCSdesc=group:GetDCSDesc(1)
self.SpeedMax=group:GetSpeedMax()
self.Speed=self.SpeedMax
self.IsInfantry=group:GetUnit(1):HasAttribute("Infantry")
self.Type=group:GetTypeName()
self.IniGroupStrength=#group:GetUnits()
self:SetDefaultROE("Free")
self:SetDefaultAlarmState("Auto")
self:AddTransition("*","Start","CombatReady")
self:AddTransition("*","Status","*")
self:AddTransition("CombatReady","Hit","Suppressed")
self:AddTransition("Suppressed","Hit","Suppressed")
self:AddTransition("Suppressed","Recovered","CombatReady")
self:AddTransition("Suppressed","TakeCover","TakingCover")
self:AddTransition("Suppressed","FallBack","FallingBack")
self:AddTransition("*","Retreat","Retreating")
self:AddTransition("TakingCover","FightBack","CombatReady")
self:AddTransition("FallingBack","FightBack","CombatReady")
self:AddTransition("Retreating","Retreated","Retreated")
self:AddTransition("*","OutOfAmmo","*")
self:AddTransition("*","Dead","*")
self:AddTransition("*","Stop","Stopped")
self:AddTransition("TakingCover","Hit","TakingCover")
self:AddTransition("FallingBack","Hit","FallingBack")
return self
end
function SUPPRESSION:SetSuppressionTime(Tave,Tmin,Tmax)
self:F({Tave=Tave,Tmin=Tmin,Tmax=Tmax})
self.Tsuppress_min=Tmin or self.Tsuppress_min
self.Tsuppress_min=math.max(self.Tsuppress_min,1)
self.Tsuppress_max=Tmax or self.Tsuppress_max
self.Tsuppress_max=math.max(self.Tsuppress_max,self.Tsuppress_min)
self.Tsuppress_ave=Tave or self.Tsuppress_ave
self.Tsuppress_ave=math.max(self.Tsuppress_min)
self.Tsuppress_ave=math.min(self.Tsuppress_max)
self:T(self.lid..string.format("Set ave suppression time to %d seconds.",self.Tsuppress_ave))
self:T(self.lid..string.format("Set min suppression time to %d seconds.",self.Tsuppress_min))
self:T(self.lid..string.format("Set max suppression time to %d seconds.",self.Tsuppress_max))
end
function SUPPRESSION:SetRetreatZone(zone)
self:F({zone=zone})
self.RetreatZone=zone
end
function SUPPRESSION:DebugOn()
self:F()
self.Debug=true
end
function SUPPRESSION:FlareOn()
self:F()
self.flare=true
end
function SUPPRESSION:SmokeOn()
self:F()
self.smoke=true
end
function SUPPRESSION:SetFormation(formation)
self:F(formation)
self.Formation=formation or"Vee"
end
function SUPPRESSION:SetSpeed(speed)
self:F(speed)
self.Speed=speed or self.SpeedMax
self.Speed=math.min(self.Speed,self.SpeedMax)
end
function SUPPRESSION:Fallback(switch)
self:F(switch)
if switch==nil then
switch=true
end
self.FallbackON=switch
end
function SUPPRESSION:SetFallbackDistance(distance)
self:F(distance)
self.FallbackDist=distance
end
function SUPPRESSION:SetFallbackWait(time)
self:F(time)
self.FallbackWait=time
end
function SUPPRESSION:Takecover(switch)
self:F(switch)
if switch==nil then
switch=true
end
self.TakecoverON=switch
end
function SUPPRESSION:SetTakecoverWait(time)
self:F(time)
self.TakecoverWait=time
end
function SUPPRESSION:SetTakecoverRange(range)
self:F(range)
self.TakecoverRange=range
end
function SUPPRESSION:SetTakecoverPlace(Hideout)
self.hideout=Hideout
end
function SUPPRESSION:SetMinimumFleeProbability(probability)
self:F(probability)
self.PminFlee=probability or 10
end
function SUPPRESSION:SetMaximumFleeProbability(probability)
self:F(probability)
self.PmaxFlee=probability or 90
end
function SUPPRESSION:SetRetreatDamage(damage)
self:F(damage)
self.RetreatDamage=damage or 50
end
function SUPPRESSION:SetRetreatWait(time)
self:F(time)
self.RetreatWait=time or 7200
end
function SUPPRESSION:SetDefaultAlarmState(alarmstate)
self:F(alarmstate)
if alarmstate:lower()=="auto"then
self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto
elseif alarmstate:lower()=="green"then
self.DefaultAlarmState=SUPPRESSION.AlarmState.Green
elseif alarmstate:lower()=="red"then
self.DefaultAlarmState=SUPPRESSION.AlarmState.Red
else
self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto
end
end
function SUPPRESSION:SetDefaultROE(roe)
self:F(roe)
if roe:lower()=="free"then
self.DefaultROE=SUPPRESSION.ROE.Free
elseif roe:lower()=="hold"then
self.DefaultROE=SUPPRESSION.ROE.Hold
elseif roe:lower()=="return"then
self.DefaultROE=SUPPRESSION.ROE.Return
else
self.DefaultROE=SUPPRESSION.ROE.Free
end
end
function SUPPRESSION:MenuOn(switch)
self:F(switch)
if switch==nil then
switch=true
end
self.MenuON=switch
end
function SUPPRESSION:_CreateMenuGroup()
local SubMenuName=self.Controllable:GetName()
local MenuGroup=MENU_MISSION:New(SubMenuName,SUPPRESSION.MenuF10)
MENU_MISSION_COMMAND:New("Fallback!",MenuGroup,self.OrderFallBack,self)
MENU_MISSION_COMMAND:New("Take Cover!",MenuGroup,self.OrderTakeCover,self)
MENU_MISSION_COMMAND:New("Retreat!",MenuGroup,self.OrderRetreat,self)
MENU_MISSION_COMMAND:New("Report Status",MenuGroup,self.Status,self,true)
end
function SUPPRESSION:OrderFallBack()
local group=self.Controllable
local vicinity=group:GetCoordinate():GetRandomVec2InRadius(150,100)
local coord=COORDINATE:NewFromVec2(vicinity)
self:FallBack(self.Controllable)
end
function SUPPRESSION:OrderTakeCover()
local Hideout=self.hideout
if self.hideout==nil then
Hideout=self:_SearchHideout()
end
self:TakeCover(Hideout)
end
function SUPPRESSION:OrderRetreat()
self:Retreat()
end
function SUPPRESSION:StatusReport(message)
local group=self.Controllable
local nunits=group:CountAliveUnits()
local roe=self.CurrentROE
local state=self.CurrentAlarmState
local life_min,life_max,life_ave,life_ave0,groupstrength=self:_GetLife()
local ammotot=group:GetAmmunition()
local detectedG=group:GetDetectedGroupSet():CountAlive()
local detectedU=group:GetDetectedUnitSet():Count()
local text=string.format("State %s, Units=%d/%d, ROE=%s, AlarmState=%s, Hits=%d, Life(min/max/ave/ave0)=%d/%d/%d/%d, Total Ammo=%d, Detected=%d/%d",
self:GetState(),nunits,self.IniGroupStrength,self.CurrentROE,self.CurrentAlarmState,self.Nhit,life_min,life_max,life_ave,life_ave0,ammotot,detectedG,detectedU)
MESSAGE:New(text,10):ToAllIf(message or self.Debug)
self:I(self.lid..text)
end
function SUPPRESSION:onafterStart(Controllable,From,Event,To)
self:_EventFromTo("onafterStart",Event,From,To)
local text=string.format("Started SUPPRESSION for group %s.",Controllable:GetName())
self:I(self.lid..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
local rzone="not defined"
if self.RetreatZone then
rzone=self.RetreatZone:GetName()
end
if self.RetreatDamage==nil then
if self.RetreatZone then
if self.IniGroupStrength==1 then
self.RetreatDamage=60.0
elseif self.IniGroupStrength==2 then
self.RetreatDamage=50.0
else
self.RetreatDamage=66.5
end
else
self.RetreatDamage=100
end
end
if self.MenuON then
if not SUPPRESSION.MenuF10 then
SUPPRESSION.MenuF10=MENU_MISSION:New("Suppression")
end
self:_CreateMenuGroup()
end
self:_SetAlarmState(self.DefaultAlarmState)
self:_SetROE(self.DefaultROE)
local text=string.format("\n******************************************************\n")
text=text..string.format("Suppressed group   = %s\n",Controllable:GetName())
text=text..string.format("Type               = %s\n",self.Type)
text=text..string.format("IsInfantry         = %s\n",tostring(self.IsInfantry))
text=text..string.format("Group strength     = %d\n",self.IniGroupStrength)
text=text..string.format("Average time       = %5.1f seconds\n",self.Tsuppress_ave)
text=text..string.format("Minimum time       = %5.1f seconds\n",self.Tsuppress_min)
text=text..string.format("Maximum time       = %5.1f seconds\n",self.Tsuppress_max)
text=text..string.format("Default ROE        = %s\n",self.DefaultROE)
text=text..string.format("Default AlarmState = %s\n",self.DefaultAlarmState)
text=text..string.format("Fall back ON       = %s\n",tostring(self.FallbackON))
text=text..string.format("Fall back distance = %5.1f m\n",self.FallbackDist)
text=text..string.format("Fall back wait     = %5.1f seconds\n",self.FallbackWait)
text=text..string.format("Fall back heading  = %s degrees\n",tostring(self.FallbackHeading))
text=text..string.format("Take cover ON      = %s\n",tostring(self.TakecoverON))
text=text..string.format("Take cover search  = %5.1f m\n",self.TakecoverRange)
text=text..string.format("Take cover wait    = %5.1f seconds\n",self.TakecoverWait)
text=text..string.format("Min flee probability = %5.1f\n",self.PminFlee)
text=text..string.format("Max flee probability = %5.1f\n",self.PmaxFlee)
text=text..string.format("Retreat zone       = %s\n",rzone)
text=text..string.format("Retreat damage     = %5.1f %%\n",self.RetreatDamage)
text=text..string.format("Retreat wait       = %5.1f seconds\n",self.RetreatWait)
text=text..string.format("Speed              = %5.1f km/h\n",self.Speed)
text=text..string.format("Speed max          = %5.1f km/h\n",self.SpeedMax)
text=text..string.format("Formation          = %s\n",self.Formation)
text=text..string.format("******************************************************\n")
self:T(self.lid..text)
if self.eventmoose then
self:HandleEvent(EVENTS.Hit,self._OnEventHit)
self:HandleEvent(EVENTS.Dead,self._OnEventDead)
else
world.addEventHandler(self)
end
self:__Status(-1)
end
function SUPPRESSION:onafterStatus(Controllable,From,Event,To)
local group=self.Controllable
if group then
local nunits=group:CountAliveUnits()
if nunits>0 then
local nammo=group:GetAmmunition()
if nammo==0 then
self:OutOfAmmo()
end
self:StatusReport(false)
if self:GetState()~="Stopped"then
self:__Status(-30)
end
else
self:Stop()
end
else
self:Stop()
end
end
function SUPPRESSION:onafterHit(Controllable,From,Event,To,Unit,AttackUnit)
self:_EventFromTo("onafterHit",Event,From,To)
if From=="CombatReady"or From=="Suppressed"then
self:_Suppress()
end
local life_min,life_max,life_ave,life_ave0,groupstrength=self:_GetLife()
local Damage=100-life_ave0
local RetreatCondition=Damage>=self.RetreatDamage-0.01 and self.RetreatZone
local Pflee=(self.PmaxFlee-self.PminFlee)/self.RetreatDamage*math.min(Damage,self.RetreatDamage)+self.PminFlee
local P=math.random(0,100)
local FleeCondition=P<Pflee
local text
text=string.format("\nGroup %s: Life min=%5.1f, max=%5.1f, ave=%5.1f, ave0=%5.1f group=%5.1f\n",Controllable:GetName(),life_min,life_max,life_ave,life_ave0,groupstrength)
text=string.format("Group %s: Damage = %8.4f (%8.4f retreat threshold).\n",Controllable:GetName(),Damage,self.RetreatDamage)
text=string.format("Group %s: P_Flee = %5.1f %5.1f=P_rand (P_Flee > Prand ==> Flee)\n",Controllable:GetName(),Pflee,P)
self:T(self.lid..text)
if Damage>=99.9 then
return
end
if RetreatCondition then
self:Retreat()
elseif FleeCondition then
if self.FallbackON and AttackUnit:IsGround()then
self:FallBack(AttackUnit)
elseif self.TakecoverON then
local Hideout=self.hideout
if self.hideout==nil then
Hideout=self:_SearchHideout()
end
self:TakeCover(Hideout)
end
end
end
function SUPPRESSION:onbeforeRecovered(Controllable,From,Event,To)
self:_EventFromTo("onbeforeRecovered",Event,From,To)
local Tnow=timer.getTime()
self:T(self.lid..string.format("onbeforeRecovered: Time now: %d  - Time over: %d",Tnow,self.TsuppressionOver))
if Tnow>=self.TsuppressionOver then
return true
else
return false
end
end
function SUPPRESSION:onafterRecovered(Controllable,From,Event,To)
self:_EventFromTo("onafterRecovered",Event,From,To)
if Controllable and Controllable:IsAlive()then
local text=string.format("Group %s has recovered!",Controllable:GetName())
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:T(self.lid..text)
self:_SetROE()
if self.flare or self.Debug then
Controllable:FlareGreen()
end
end
end
function SUPPRESSION:onafterFightBack(Controllable,From,Event,To)
self:_EventFromTo("onafterFightBack",Event,From,To)
self:_SetROE()
self:_SetAlarmState()
local group=Controllable
local Waypoints=group:GetTemplateRoutePoints()
group:Route(Waypoints,5)
end
function SUPPRESSION:onbeforeFallBack(Controllable,From,Event,To,AttackUnit)
self:_EventFromTo("onbeforeFallBack",Event,From,To)
if From=="FallingBack"then
return false
else
return true
end
end
function SUPPRESSION:onafterFallBack(Controllable,From,Event,To,AttackUnit)
self:_EventFromTo("onafterFallback",Event,From,To)
self:T(self.lid..string.format("Group %s is falling back after %d hits.",Controllable:GetName(),self.Nhit))
local ACoord=AttackUnit:GetCoordinate()
local DCoord=Controllable:GetCoordinate()
local heading=self:_Heading(ACoord,DCoord)
if self.FallbackHeading then
heading=self.FallbackHeading
end
local Coord=DCoord:Translate(self.FallbackDist,heading)
if self.Debug then
local MarkerID=Coord:MarkToAll("Fall back position for group "..Controllable:GetName())
end
if self.smoke or self.Debug then
Coord:SmokeBlue()
end
self:_SetROE(SUPPRESSION.ROE.Hold)
self:_SetAlarmState(SUPPRESSION.AlarmState.Auto)
self:_Run(Coord,self.Speed,self.Formation,self.FallbackWait)
end
function SUPPRESSION:onbeforeTakeCover(Controllable,From,Event,To,Hideout)
self:_EventFromTo("onbeforeTakeCover",Event,From,To)
if From=="TakingCover"then
return false
end
if Hideout~=nil then
return true
else
return false
end
end
function SUPPRESSION:onafterTakeCover(Controllable,From,Event,To,Hideout)
self:_EventFromTo("onafterTakeCover",Event,From,To)
if self.Debug then
local MarkerID=Hideout:MarkToAll(string.format("Hideout for group %s",Controllable:GetName()))
end
if self.smoke or self.Debug then
Hideout:SmokeBlue()
end
self:_SetROE(SUPPRESSION.ROE.Hold)
self:_SetAlarmState(SUPPRESSION.AlarmState.Green)
self:_Run(Hideout,self.Speed,self.Formation,self.TakecoverWait)
end
function SUPPRESSION:onafterOutOfAmmo(Controllable,From,Event,To)
self:_EventFromTo("onafterOutOfAmmo",Event,From,To)
self:I(self.lid..string.format("Out of ammo!"))
if self.RetreatZone then
self:Retreat()
end
end
function SUPPRESSION:onbeforeRetreat(Controllable,From,Event,To)
self:_EventFromTo("onbeforeRetreat",Event,From,To)
if From=="Retreating"then
local text=string.format("Group %s is already retreating.",tostring(Controllable:GetName()))
self:T2(self.lid..text)
return false
else
return true
end
end
function SUPPRESSION:onafterRetreat(Controllable,From,Event,To)
self:_EventFromTo("onafterRetreat",Event,From,To)
local text=string.format("Group %s is retreating! Alarm state green.",Controllable:GetName())
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:T(self.lid..text)
local ZoneCoord=self.RetreatZone:GetRandomCoordinate()
local ZoneVec2=ZoneCoord:GetVec2()
if self.smoke or self.Debug then
ZoneCoord:SmokeBlue()
end
if self.Debug then
self.RetreatZone:SmokeZone(SMOKECOLOR.Red,12)
end
self:_SetROE(SUPPRESSION.ROE.Hold)
self:_SetAlarmState(SUPPRESSION.AlarmState.Green)
self:_Run(ZoneCoord,self.Speed,self.Formation,self.RetreatWait)
end
function SUPPRESSION:onbeforeRetreated(Controllable,From,Event,To)
self:_EventFromTo("onbeforeRetreated",Event,From,To)
local inzone=self.RetreatZone:IsVec3InZone(Controllable:GetVec3())
return inzone
end
function SUPPRESSION:onafterRetreated(Controllable,From,Event,To)
self:_EventFromTo("onafterRetreated",Event,From,To)
self:_SetROE(SUPPRESSION.ROE.Return)
self:_SetAlarmState(SUPPRESSION.AlarmState.Auto)
end
function SUPPRESSION:onafterDead(Controllable,From,Event,To)
self:_EventFromTo("onafterDead",Event,From,To)
local group=self.Controllable
if group then
local nunits=group:CountAliveUnits()
local text=string.format("Group %s: One of our units just died! %d units left.",self.Controllable:GetName(),nunits)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:T(self.lid..text)
if nunits==0 then
self:Stop()
end
else
self:Stop()
end
end
function SUPPRESSION:onafterStop(Controllable,From,Event,To)
self:_EventFromTo("onafterStop",Event,From,To)
local text=string.format("Stopping SUPPRESSION for group %s",self.Controllable:GetName())
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:I(self.lid..text)
self.CallScheduler:Clear()
if self.mooseevents then
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Hit)
else
world.removeEventHandler(self)
end
end
function SUPPRESSION:onEvent(Event)
if Event==nil or Event.initiator==nil or Unit.getByName(Event.initiator:getName())==nil then
return true
end
local EventData={}
if Event.initiator then
EventData.IniDCSUnit=Event.initiator
EventData.IniUnitName=Event.initiator:getName()
EventData.IniDCSGroup=Event.initiator:getGroup()
EventData.IniGroupName=Event.initiator:getGroup():getName()
EventData.IniGroup=GROUP:FindByName(EventData.IniGroupName)
EventData.IniUnit=UNIT:FindByName(EventData.IniUnitName)
end
if Event.target then
EventData.TgtDCSUnit=Event.target
EventData.TgtUnitName=Event.target:getName()
EventData.TgtDCSGroup=Event.target:getGroup()
EventData.TgtGroupName=Event.target:getGroup():getName()
EventData.TgtGroup=GROUP:FindByName(EventData.TgtGroupName)
EventData.TgtUnit=UNIT:FindByName(EventData.TgtUnitName)
end
if Event.id==world.event.S_EVENT_HIT then
self:_OnEventHit(EventData)
end
if Event.id==world.event.S_EVENT_DEAD then
self:_OnEventDead(EventData)
end
end
function SUPPRESSION:_OnEventHit(EventData)
self:F3(EventData)
local GroupNameSelf=self.Controllable:GetName()
local GroupNameTgt=EventData.TgtGroupName
local TgtUnit=EventData.TgtUnit
local tgt=EventData.TgtDCSUnit
local IniUnit=EventData.IniUnit
if GroupNameTgt==GroupNameSelf then
self:T(self.lid..string.format("Hit event at t = %5.1f",timer.getTime()))
if self.flare or self.Debug then
TgtUnit:FlareRed()
end
self.Nhit=self.Nhit+1
self:T(self.lid..string.format("Group %s has just been hit %d times.",self.Controllable:GetName(),self.Nhit))
local life=tgt:getLife()/(tgt:getLife0()+1)*100
self:T2(self.lid..string.format("Target unit life = %5.1f",life))
self:__Hit(3,TgtUnit,IniUnit)
end
end
function SUPPRESSION:_OnEventDead(EventData)
local GroupNameSelf=self.Controllable:GetName()
local GroupNameIni=EventData.IniGroupName
if GroupNameIni==GroupNameSelf then
local IniUnit=EventData.IniUnit
local IniUnitName=EventData.IniUnitName
if EventData.IniUnit then
self:T2(self.lid..string.format("Group %s: Dead MOOSE unit DOES exist! Unit name %s.",GroupNameIni,IniUnitName))
else
self:T2(self.lid..string.format("Group %s: Dead MOOSE unit DOES NOT not exist! Unit name %s.",GroupNameIni,IniUnitName))
end
if EventData.IniDCSUnit then
self:T2(self.lid..string.format("Group %s: Dead DCS unit DOES exist! Unit name %s.",GroupNameIni,IniUnitName))
else
self:T2(self.lid..string.format("Group %s: Dead DCS unit DOES NOT exist! Unit name %s.",GroupNameIni,IniUnitName))
end
if IniUnit and(self.flare or self.Debug)then
IniUnit:FlareWhite()
self:T(self.lid..string.format("Flare Dead MOOSE unit."))
end
if EventData.IniDCSUnit and(self.flare or self.Debug)then
local p=EventData.IniDCSUnit:getPosition().p
trigger.action.signalFlare(p,trigger.flareColor.Yellow,0)
self:T(self.lid..string.format("Flare Dead DCS unit."))
end
self:Status()
self:__Dead(0.1)
end
end
function SUPPRESSION:_Suppress()
local Tnow=timer.getTime()
local Controllable=self.Controllable
self:_SetROE(SUPPRESSION.ROE.Hold)
local sigma=(self.Tsuppress_max-self.Tsuppress_min)/4
local Tsuppress=self:_Random_Gaussian(self.Tsuppress_ave,sigma,self.Tsuppress_min,self.Tsuppress_max)
local renew=true
if self.TsuppressionOver~=nil then
if Tsuppress+Tnow>self.TsuppressionOver then
self.TsuppressionOver=Tnow+Tsuppress
else
renew=false
end
else
self.TsuppressionOver=Tnow+Tsuppress
end
if renew then
self:__Recovered(self.TsuppressionOver-Tnow)
end
local text=string.format("Group %s is suppressed for %d seconds. Suppression ends at %d:%02d.",Controllable:GetName(),Tsuppress,self.TsuppressionOver/60,self.TsuppressionOver%60)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:T(self.lid..text)
end
function SUPPRESSION:_Run(fin,speed,formation,wait)
speed=speed or 20
formation=formation or ENUMS.Formation.Vehicle.OffRoad
wait=wait or 30
local group=self.Controllable
if group and group:IsAlive()then
local ini=group:GetCoordinate()
local dist=ini:Get2DDistance(fin)
local heading=self:_Heading(ini,fin)
local wp={}
local tasks={}
wp[1]=ini:WaypointGround(speed,formation)
if self.Debug then
local MarkerID=ini:MarkToAll(string.format("Waypoing %d of group %s (initial)",#wp,self.Controllable:GetName()))
end
local ConditionWait=group:TaskCondition(nil,nil,nil,nil,wait,nil)
local TaskHold=group:TaskHold()
local TaskComboFin={}
TaskComboFin[#TaskComboFin+1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint",self,#wp,true)
TaskComboFin[#TaskComboFin+1]=group:TaskControlled(TaskHold,ConditionWait)
wp[#wp+1]=fin:WaypointGround(speed,formation,TaskComboFin)
if self.Debug then
local MarkerID=fin:MarkToAll(string.format("Waypoing %d of group %s (final)",#wp,self.Controllable:GetName()))
end
group:Route(wp)
else
self:E(self.lid..string.format("ERROR: Group is not alive!"))
end
end
function SUPPRESSION._Passing_Waypoint(group,Fsm,i,final)
local text=string.format("Group %s passing waypoint %d (final=%s)",group:GetName(),i,tostring(final))
MESSAGE:New(text,10):ToAllIf(Fsm.Debug)
if Fsm.Debug then
env.info(Fsm.lid..text)
end
if final then
if Fsm:is("Retreating")then
Fsm:Retreated()
else
Fsm:FightBack()
end
end
end
function SUPPRESSION:_SearchHideout()
local Zone=ZONE_GROUP:New("Zone_Hiding",self.Controllable,self.TakecoverRange)
local gpos=self.Controllable:GetCoordinate()
Zone:Scan(Object.Category.SCENERY)
local hideouts={}
for SceneryTypeName,SceneryData in pairs(Zone:GetScannedScenery())do
for SceneryName,SceneryObject in pairs(SceneryData)do
local SceneryObject=SceneryObject
local spos=SceneryObject:GetCoordinate()
local distance=spos:Get2DDistance(gpos)
if self.Debug then
local MarkerID=SceneryObject:GetCoordinate():MarkToAll(string.format("%s scenery object %s",self.Controllable:GetName(),SceneryObject:GetTypeName()))
local text=string.format("%s scenery: %s, Coord %s",self.Controllable:GetName(),SceneryObject:GetTypeName(),SceneryObject:GetCoordinate():ToStringLLDMS())
self:T2(self.lid..text)
end
table.insert(hideouts,{object=SceneryObject,distance=distance})
end
end
local Hideout=nil
if#hideouts>0 then
self:T(self.lid.."Number of hideouts "..#hideouts)
local _sort=function(a,b)return a.distance<b.distance end
table.sort(hideouts,_sort)
Hideout=hideouts[1].object:GetCoordinate()
else
self:E(self.lid.."No hideouts found!")
end
return Hideout
end
function SUPPRESSION:_GetLife()
local group=self.Controllable
if group and group:IsAlive()then
local units=group:GetUnits()
local life_min=nil
local life_max=nil
local life_ave=0
local life_ave0=0
local n=0
local groupstrength=#units/self.IniGroupStrength*100
self:T2(self.lid..string.format("Group %s _GetLife nunits = %d",self.Controllable:GetName(),#units))
for _,unit in pairs(units)do
local unit=unit
if unit and unit:IsAlive()then
n=n+1
local life=unit:GetLife()/(unit:GetLife0()+1)*100
if life_min==nil or life<life_min then
life_min=life
end
if life_max==nil or life>life_max then
life_max=life
end
life_ave=life_ave+life
if self.Debug then
local text=string.format("n=%02d: Life = %3.1f, Life0 = %3.1f, min=%3.1f, max=%3.1f, ave=%3.1f, group=%3.1f",n,unit:GetLife(),unit:GetLife0(),life_min,life_max,life_ave/n,groupstrength)
self:T2(self.lid..text)
end
end
end
if n==0 then
return 0,0,0,0,0
end
life_ave0=life_ave/self.IniGroupStrength
life_ave=life_ave/n
return life_min,life_max,life_ave,life_ave0,groupstrength
else
return 0,0,0,0,0
end
end
function SUPPRESSION:_Heading(a,b)
local dx=b.x-a.x
local dy=b.z-a.z
local angle=math.deg(math.atan2(dy,dx))
if angle<0 then
angle=360+angle
end
return angle
end
function SUPPRESSION:_Random_Gaussian(x0,sigma,xmin,xmax)
sigma=sigma or 5
local r
local gotit=false
local i=0
while not gotit do
local x1=math.random()
local x2=math.random()
r=math.sqrt(-2*sigma*sigma*math.log(x1))*math.cos(2*math.pi*x2)+x0
i=i+1
if(r>=xmin and r<=xmax)or i>100 then
gotit=true
end
end
return r
end
function SUPPRESSION:_SetROE(roe)
local group=self.Controllable
roe=roe or self.DefaultROE
self.CurrentROE=roe
if roe==SUPPRESSION.ROE.Free then
group:OptionROEOpenFire()
elseif roe==SUPPRESSION.ROE.Hold then
group:OptionROEHoldFire()
elseif roe==SUPPRESSION.ROE.Return then
group:OptionROEReturnFire()
else
self:E(self.lid.."Unknown ROE requested: "..tostring(roe))
group:OptionROEOpenFire()
self.CurrentROE=SUPPRESSION.ROE.Free
end
local text=string.format("Group %s now has ROE %s.",self.Controllable:GetName(),self.CurrentROE)
self:T(self.lid..text)
end
function SUPPRESSION:_SetAlarmState(state)
local group=self.Controllable
state=state or self.DefaultAlarmState
self.CurrentAlarmState=state
if state==SUPPRESSION.AlarmState.Auto then
group:OptionAlarmStateAuto()
elseif state==SUPPRESSION.AlarmState.Green then
group:OptionAlarmStateGreen()
elseif state==SUPPRESSION.AlarmState.Red then
group:OptionAlarmStateRed()
else
self:E(self.lid.."Unknown alarm state requested: "..tostring(state))
group:OptionAlarmStateAuto()
self.CurrentAlarmState=SUPPRESSION.AlarmState.Auto
end
local text=string.format("Group %s now has Alarm State %s.",self.Controllable:GetName(),self.CurrentAlarmState)
self:T(self.lid..text)
end
function SUPPRESSION:_EventFromTo(BA,Event,From,To)
local text=string.format("\n%s: %s EVENT %s: %s --> %s",BA,self.Controllable:GetName(),Event,From,To)
self:T2(self.lid..text)
end
PSEUDOATC={
ClassName="PSEUDOATC",
group={},
Debug=false,
mdur=30,
mrefresh=120,
talt=3,
chatty=true,
eventsmoose=true,
reportplayername=false,
}
PSEUDOATC.id="PseudoATC | "
PSEUDOATC.version="0.9.5"
function PSEUDOATC:New()
local self=BASE:Inherit(self,BASE:New())
self:E(PSEUDOATC.id..string.format("PseudoATC version %s",PSEUDOATC.version))
return self
end
function PSEUDOATC:Start()
self:F()
self:E(PSEUDOATC.id.."Starting PseudoATC")
if self.eventsmoose then
self:T(PSEUDOATC.id.."Events are handled by MOOSE.")
self:HandleEvent(EVENTS.Birth,self._OnBirth)
self:HandleEvent(EVENTS.Land,self._PlayerLanded)
self:HandleEvent(EVENTS.Takeoff,self._PlayerTakeOff)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._PlayerLeft)
self:HandleEvent(EVENTS.Crash,self._PlayerLeft)
else
self:T(PSEUDOATC.id.."Events are handled by DCS.")
world.addEventHandler(self)
end
end
function PSEUDOATC:DebugOn()
self.Debug=true
end
function PSEUDOATC:DebugOff()
self.Debug=false
end
function PSEUDOATC:ChattyOn()
self.chatty=true
end
function PSEUDOATC:ChattyOff()
self.chatty=false
end
function PSEUDOATC:SetMessageDuration(duration)
self.mdur=duration or 30
end
function PSEUDOATC:SetReportPlayername()
self.reportplayername=true
return self
end
function PSEUDOATC:SetMenuRefresh(interval)
self.mrefresh=interval or 120
end
function PSEUDOATC:SetEventsMoose(switch)
self.eventsmoose=switch
end
function PSEUDOATC:SetReportAltInterval(interval)
self.talt=interval or 3
end
function PSEUDOATC:onEvent(Event)
if Event==nil or Event.initiator==nil or Unit.getByName(Event.initiator:getName())==nil then
return true
end
local DCSiniunit=Event.initiator
local DCSplace=Event.place
local DCSsubplace=Event.subplace
local EventData={}
local _playerunit=nil
local _playername=nil
if Event.initiator then
EventData.IniUnitName=Event.initiator:getName()
EventData.IniDCSGroup=Event.initiator:getGroup()
EventData.IniGroupName=Event.initiator:getGroup():getName()
_playerunit,_playername=self:_GetPlayerUnitAndName(EventData.IniUnitName)
end
if Event.place then
EventData.Place=Event.place
EventData.PlaceName=Event.place:getName()
end
if Event.subplace then
EventData.SubPlace=Event.subplace
EventData.SubPlaceName=Event.subplace:getName()
end
self:T3(PSEUDOATC.id..string.format("EVENT: Event in onEvent with ID = %s",tostring(Event.id)))
self:T3(PSEUDOATC.id..string.format("EVENT: Ini unit   = %s",tostring(EventData.IniUnitName)))
self:T3(PSEUDOATC.id..string.format("EVENT: Ini group  = %s",tostring(EventData.IniGroupName)))
self:T3(PSEUDOATC.id..string.format("EVENT: Ini player = %s",tostring(_playername)))
self:T3(PSEUDOATC.id..string.format("EVENT: Place      = %s",tostring(EventData.PlaceName)))
self:T3(PSEUDOATC.id..string.format("EVENT: SubPlace   = %s",tostring(EventData.SubPlaceName)))
if Event.id==world.event.S_EVENT_BIRTH and _playername then
self:_OnBirth(EventData)
end
if Event.id==world.event.S_EVENT_TAKEOFF and _playername and EventData.Place then
self:_PlayerTakeOff(EventData)
end
if Event.id==world.event.S_EVENT_LAND and _playername and EventData.Place then
self:_PlayerLanded(EventData)
end
if Event.id==world.event.S_EVENT_PLAYER_LEAVE_UNIT and _playername then
self:_PlayerLeft(EventData)
end
if Event.id==world.event.S_EVENT_CRASH and _playername then
self:_PlayerLeft(EventData)
end
end
function PSEUDOATC:_OnBirth(EventData)
self:F({EventData=EventData})
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
self:PlayerEntered(_unit)
end
end
function PSEUDOATC:_PlayerLeft(EventData)
self:F({EventData=EventData})
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
self:PlayerLeft(_unit)
end
end
function PSEUDOATC:_PlayerLanded(EventData)
self:F({EventData=EventData})
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
local _base=nil
local _baseName=nil
if EventData.place then
_base=EventData.place
_baseName=EventData.place:getName()
end
if _unit and _playername and _base then
self:PlayerLanded(_unit,_baseName)
end
end
function PSEUDOATC:_PlayerTakeOff(EventData)
self:F({EventData=EventData})
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
local _base=nil
local _baseName=nil
if EventData.place then
_base=EventData.place
_baseName=EventData.place:getName()
end
if _unit and _playername and _base then
self:PlayerTakeOff(_unit,_baseName)
end
end
function PSEUDOATC:PlayerEntered(unit)
self:F2({unit=unit})
local group=unit:GetGroup()
local GID=group:GetID()
local GroupName=group:GetName()
local PlayerName=unit:GetPlayerName()
local UnitName=unit:GetName()
local CallSign=unit:GetCallsign()
local UID=unit:GetDCSObject():getID()
if not self.group[GID]then
self.group[GID]={}
self.group[GID].player={}
end
self.group[GID].player[UID]={}
self.group[GID].player[UID].group=group
self.group[GID].player[UID].unit=unit
self.group[GID].player[UID].groupname=GroupName
self.group[GID].player[UID].unitname=UnitName
self.group[GID].player[UID].playername=PlayerName
self.group[GID].player[UID].callsign=CallSign
self.group[GID].player[UID].waypoints=group:GetTaskRoute()
local text=string.format("Player %s entered unit %s of group %s (id=%d).",PlayerName,UnitName,GroupName,GID)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
local countPlayerInGroup=0
for _ in pairs(self.group[GID].player)do countPlayerInGroup=countPlayerInGroup+1 end
if countPlayerInGroup<=1 then
self.group[GID].menu_main=missionCommands.addSubMenuForGroup(GID,"Pseudo ATC")
end
self:MenuCreatePlayer(GID,UID)
self:LocalAirports(GID,UID)
self:MenuAirports(GID,UID)
self:MenuWaypoints(GID,UID)
self.group[GID].player[UID].scheduler,self.group[GID].player[UID].schedulerid=SCHEDULER:New(nil,self.MenuRefresh,{self,GID,UID},self.mrefresh,self.mrefresh)
end
function PSEUDOATC:PlayerLanded(unit,place)
self:F2({unit=unit,place=place})
local group=unit:GetGroup()
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
local PlayerName=unit:GetPlayerName()or"Ghost"
local UnitName=unit:GetName()or"Ghostplane"
local GroupName=group:GetName()or"Ghostgroup"
if self.Debug then
local text=string.format("Player %s in unit %s of group %s landed at %s.",PlayerName,UnitName,GroupName,place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
end
self:AltitudeTimerStop(GID,UID)
if place and self.chatty then
local text=string.format("Touchdown! Welcome to %s pilot %s. Have a nice day!",place,PlayerName)
MESSAGE:New(text,self.mdur):ToGroup(group)
end
end
function PSEUDOATC:PlayerTakeOff(unit,place)
self:F2({unit=unit,place=place})
local group=unit:GetGroup()
local PlayerName=unit:GetPlayerName()or"Ghost"
local UnitName=unit:GetName()or"Ghostplane"
local GroupName=group:GetName()or"Ghostgroup"
local CallSign=unit:GetCallsign()or"Ghost11"
if self.Debug then
local text=string.format("Player %s in unit %s of group %s took off at %s.",PlayerName,UnitName,GroupName,place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
end
if place and self.chatty then
local text=string.format("%s, %s, you are airborne. Have a safe trip!",place,CallSign)
if self.reportplayername then
text=string.format("%s, %s, you are airborne. Have a safe trip!",place,PlayerName)
end
MESSAGE:New(text,self.mdur):ToGroup(group)
end
end
function PSEUDOATC:PlayerLeft(unit)
self:F({unit=unit})
local group=unit:GetGroup()
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
if self.group[GID]and self.group[GID].player and self.group[GID].player[UID]then
local PlayerName=self.group[GID].player[UID].playername
local CallSign=self.group[GID].player[UID].callsign
local UnitName=self.group[GID].player[UID].unitname
local GroupName=self.group[GID].player[UID].groupname
local text=string.format("Player %s (callsign %s) of group %s just left unit %s.",PlayerName,CallSign,GroupName,UnitName)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
if self.group[GID].player[UID].schedulerid then
self.group[GID].player[UID].scheduler:Stop(self.group[GID].player[UID].schedulerid)
end
self:AltitudeTimerStop(GID,UID)
if self.group[GID].player[UID].menu_own then
missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_own)
end
local countPlayerInGroup=0
for _ in pairs(self.group[GID].player)do countPlayerInGroup=countPlayerInGroup+1 end
if self.group[GID].menu_main and countPlayerInGroup==1 then
missionCommands.removeItemForGroup(GID,self.group[GID].menu_main)
end
self.group[GID].player[UID]=nil
end
end
function PSEUDOATC:MenuRefresh(GID,UID)
self:F({GID=GID,UID=UID})
local text=string.format("Refreshing menues for player %s in group %s.",self.group[GID].player[UID].playername,self.group[GID].player[UID].groupname)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
self:MenuClear(GID,UID)
self:LocalAirports(GID,UID)
self:MenuAirports(GID,UID)
self:MenuWaypoints(GID,UID)
end
function PSEUDOATC:MenuCreatePlayer(GID,UID)
self:F({GID=GID,UID=UID})
local PlayerName=self.group[GID].player[UID].playername
self.group[GID].player[UID].menu_own=missionCommands.addSubMenuForGroup(GID,PlayerName,self.group[GID].menu_main)
end
function PSEUDOATC:MenuClear(GID,UID)
self:F({GID=GID,UID=UID})
local text=string.format("Clearing menus for player %s in group %s.",self.group[GID].player[UID].playername,self.group[GID].player[UID].groupname)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
if self.group[GID].player[UID].menu_airports then
missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_airports)
self.group[GID].player[UID].menu_airports=nil
else
self:T2(PSEUDOATC.id.."No airports to clear menus.")
end
if self.group[GID].player[UID].menu_waypoints then
missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_waypoints)
self.group[GID].player[UID].menu_waypoints=nil
end
if self.group[GID].player[UID].menu_reportalt then
missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_reportalt)
self.group[GID].player[UID].menu_reportalt=nil
end
if self.group[GID].player[UID].menu_requestalt then
missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_requestalt)
self.group[GID].player[UID].menu_requestalt=nil
end
end
function PSEUDOATC:MenuAirports(GID,UID)
self:F({GID=GID,UID=UID})
self.group[GID].player[UID].menu_airports=missionCommands.addSubMenuForGroup(GID,"Local Airports",self.group[GID].player[UID].menu_own)
local i=0
for _,airport in pairs(self.group[GID].player[UID].airports)do
i=i+1
if i>10 then
break
end
local name=airport.name
local d=airport.distance
local pos=AIRBASE:FindByName(name):GetCoordinate()
local submenu=missionCommands.addSubMenuForGroup(GID,name,self.group[GID].player[UID].menu_airports)
missionCommands.addCommandForGroup(GID,"Weather Report",submenu,self.ReportWeather,self,GID,UID,pos,name)
missionCommands.addCommandForGroup(GID,"Request BR",submenu,self.ReportBR,self,GID,UID,pos,name)
self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d",name,GID))
end
end
function PSEUDOATC:MenuWaypoints(GID,UID)
self:F({GID=GID,UID=UID})
local callsign=self.group[GID].player[UID].callsign
self:T(PSEUDOATC.id..string.format("Creating waypoint menu for %s (ID %d).",callsign,GID))
if#self.group[GID].player[UID].waypoints>0 then
self.group[GID].player[UID].menu_waypoints=missionCommands.addSubMenuForGroup(GID,"Waypoints",self.group[GID].player[UID].menu_own)
local j=0
for i,wp in pairs(self.group[GID].player[UID].waypoints)do
j=j+1
if j>10 then
break
end
local pos=COORDINATE:New(wp.x,wp.alt,wp.y)
local name=string.format("Waypoint %d",i-1)
if wp.name and wp.name~=""then
name=string.format("Waypoint %s",wp.name)
end
local submenu=missionCommands.addSubMenuForGroup(GID,name,self.group[GID].player[UID].menu_waypoints)
missionCommands.addCommandForGroup(GID,"Weather Report",submenu,self.ReportWeather,self,GID,UID,pos,name)
missionCommands.addCommandForGroup(GID,"Request BR",submenu,self.ReportBR,self,GID,UID,pos,name)
end
end
self.group[GID].player[UID].menu_reportalt=missionCommands.addCommandForGroup(GID,"Talk me down",self.group[GID].player[UID].menu_own,self.AltidudeTimerToggle,self,GID,UID)
self.group[GID].player[UID].menu_requestalt=missionCommands.addCommandForGroup(GID,"Request altitude",self.group[GID].player[UID].menu_own,self.ReportHeight,self,GID,UID)
end
function PSEUDOATC:ReportWeather(GID,UID,position,location)
self:F({GID=GID,UID=UID,position=position,location=location})
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername)or _SETTINGS
local text=string.format("Local weather at %s:\n",location)
local Pqnh=position:GetPressure(0)
local Pqfe=position:GetPressure()
local hPa2inHg=0.0295299830714
local hPa2mmHg=0.7500615613030
local _Pqnh=string.format("%.2f inHg",Pqnh*hPa2inHg)
local _Pqfe=string.format("%.2f inHg",Pqfe*hPa2inHg)
if settings:IsMetric()then
_Pqnh=string.format("%.1f mmHg",Pqnh*hPa2mmHg)
_Pqfe=string.format("%.1f mmHg",Pqfe*hPa2mmHg)
end
text=text..string.format("QFE %.1f hPa = %s.\n",Pqfe,_Pqfe)
text=text..string.format("QNH %.1f hPa = %s.\n",Pqnh,_Pqnh)
local T=position:GetTemperature()
local _T=string.format('%d°F',UTILS.CelsiusToFahrenheit(T))
if settings:IsMetric()then
_T=string.format('%d°C',T)
end
local text=text..string.format("Temperature %s\n",_T)
local Dir,Vel=position:GetWind()
local Bn,Bd=UTILS.BeaufortScale(Vel)
local Ds=string.format('%03d°',Dir)
local Vs=string.format("%.1f knots",UTILS.MpsToKnots(Vel))
if settings:IsMetric()then
Vs=string.format('%.1f m/s',Vel)
end
local text=text..string.format("%s, Wind from %s at %s (%s).",self.group[GID].player[UID].playername,Ds,Vs,Bd)
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,text,self.mdur,true)
end
function PSEUDOATC:ReportBR(GID,UID,position,location)
self:F({GID=GID,UID=UID,position=position,location=location})
local unit=self.group[GID].player[UID].unit
local coord=unit:GetCoordinate()
local angle=coord:HeadingTo(position)
local range=coord:Get2DDistance(position)
local Bs=string.format('%03d°',angle)
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername)or _SETTINGS
local Rs=string.format("%.1f NM",UTILS.MetersToNM(range))
if settings:IsMetric()then
Rs=string.format("%.1f km",range/1000)
end
local text=string.format("%s: Bearing %s, Range %s.",location,Bs,Rs)
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,text,self.mdur,true)
end
function PSEUDOATC:ReportHeight(GID,UID,dt,_clear)
self:F({GID=GID,UID=UID,dt=dt})
local dt=dt or self.mdur
if _clear==nil then
_clear=false
end
local function get_AGL(p)
local agl=0
local vec2={x=p.x,y=p.z}
local ground=land.getHeight(vec2)
local agl=p.y-ground
return agl
end
local unit=self.group[GID].player[UID].unit
if unit and unit:IsAlive()then
local position=unit:GetCoordinate()
local height=get_AGL(position)
local callsign=unit:GetCallsign()
local PlayerName=self.group[GID].player[UID].playername
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername)or _SETTINGS
local Hs=string.format("%d ft",UTILS.MetersToFeet(height))
if settings:IsMetric()then
Hs=string.format("%d m",height)
end
local _text=string.format("%s, your altitude is %s AGL.",callsign,Hs)
if self.reportplayername then
_text=string.format("%s, your altitude is %s AGL.",PlayerName,Hs)
end
if _clear==false then
_text=_text..string.format(" FL%03d.",position.y/30.48)
end
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,_text,dt,_clear)
return height
end
return 0
end
function PSEUDOATC:AltidudeTimerToggle(GID,UID)
self:F({GID=GID,UID=UID})
if self.group[GID].player[UID].altimerid then
self:AltitudeTimerStop(GID,UID)
else
self:AltitudeTimeStart(GID,UID)
end
end
function PSEUDOATC:AltitudeTimeStart(GID,UID)
self:F({GID=GID,UID=UID})
self:T(PSEUDOATC.id..string.format("Starting altitude report timer for player ID %d.",UID))
self.group[GID].player[UID].altimer,self.group[GID].player[UID].altimerid=SCHEDULER:New(nil,self.ReportHeight,{self,GID,UID,0.1,true},1,3)
end
function PSEUDOATC:AltitudeTimerStop(GID,UID)
self:F({GID=GID,UID=UID})
self:T(PSEUDOATC.id..string.format("Stopping altitude report timer for player ID %d.",UID))
if self.group[GID].player[UID].altimerid then
self.group[GID].player[UID].altimer:Stop(self.group[GID].player[UID].altimerid)
end
self.group[GID].player[UID].altimer=nil
self.group[GID].player[UID].altimerid=nil
end
function PSEUDOATC:LocalAirports(GID,UID)
self:F({GID=GID,UID=UID})
self.group[GID].player[UID].airports=nil
self.group[GID].player[UID].airports={}
local pos=self.group[GID].player[UID].unit:GetCoordinate()
for i=0,2 do
local airports=coalition.getAirbases(i)
for _,airbase in pairs(airports)do
local name=airbase:getName()
local a=AIRBASE:FindByName(name)
if a then
local q=a:GetCoordinate()
local d=q:Get2DDistance(pos)
table.insert(self.group[GID].player[UID].airports,{distance=d,name=name})
end
end
end
local function compare(a,b)
return a.distance<b.distance
end
table.sort(self.group[GID].player[UID].airports,compare)
end
function PSEUDOATC:_GetPlayerUnitAndName(_unitName)
self:F(_unitName)
if _unitName~=nil then
local DCSunit=Unit.getByName(_unitName)
if DCSunit then
local playername=DCSunit:getPlayerName()
local unit=UNIT:Find(DCSunit)
self:T2({DCSunit=DCSunit,unit=unit,playername=playername})
if unit and playername then
return unit,playername
end
end
end
return nil,nil
end
function PSEUDOATC:_DisplayMessageToGroup(_unit,_text,_time,_clear)
self:F({unit=_unit,text=_text,time=_time,clear=_clear})
_time=_time or self.Tmsg
if _clear==nil then
_clear=false
end
local _gid=_unit:GetGroup():GetID()
if _gid then
if _clear==true then
trigger.action.outTextForGroup(_gid,_text,_time,_clear)
else
trigger.action.outTextForGroup(_gid,_text,_time)
end
end
end
function PSEUDOATC:_myname(unitname)
self:F2(unitname)
local unit=UNIT:FindByName(unitname)
local pname=unit:GetPlayerName()
local csign=unit:GetCallsign()
return string.format("%s (%s)",csign,pname)
end
WAREHOUSE={
ClassName="WAREHOUSE",
Debug=false,
verbosity=0,
lid=nil,
Report=true,
warehouse=nil,
alias=nil,
zone=nil,
airbase=nil,
airbasename=nil,
road=nil,
rail=nil,
spawnzone=nil,
uid=nil,
dTstatus=30,
queueid=0,
stock={},
queue={},
pending={},
transporting={},
delivered={},
defending={},
portzone=nil,
harborzone=nil,
shippinglanes={},
offroadpaths={},
autodefence=false,
spawnzonemaxdist=5000,
autosave=false,
autosavepath=nil,
autosavefile=nil,
saveparking=false,
isUnit=false,
isShip=false,
lowfuelthresh=0.15,
respawnafterdestroyed=false,
respawndelay=nil,
}
WAREHOUSE.Descriptor={
GROUPNAME="templatename",
UNITTYPE="unittype",
ATTRIBUTE="attribute",
CATEGORY="category",
ASSIGNMENT="assignment",
ASSETLIST="assetlist,"
}
WAREHOUSE.Attribute={
AIR_TRANSPORTPLANE="Air_TransportPlane",
AIR_AWACS="Air_AWACS",
AIR_FIGHTER="Air_Fighter",
AIR_BOMBER="Air_Bomber",
AIR_TANKER="Air_Tanker",
AIR_TRANSPORTHELO="Air_TransportHelo",
AIR_ATTACKHELO="Air_AttackHelo",
AIR_UAV="Air_UAV",
AIR_OTHER="Air_OtherAir",
GROUND_APC="Ground_APC",
GROUND_TRUCK="Ground_Truck",
GROUND_INFANTRY="Ground_Infantry",
GROUND_IFV="Ground_IFV",
GROUND_ARTILLERY="Ground_Artillery",
GROUND_TANK="Ground_Tank",
GROUND_TRAIN="Ground_Train",
GROUND_EWR="Ground_EWR",
GROUND_AAA="Ground_AAA",
GROUND_SAM="Ground_SAM",
GROUND_OTHER="Ground_OtherGround",
NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier",
NAVAL_WARSHIP="Naval_WarShip",
NAVAL_ARMEDSHIP="Naval_ArmedShip",
NAVAL_UNARMEDSHIP="Naval_UnarmedShip",
NAVAL_OTHER="Naval_OtherNaval",
OTHER_UNKNOWN="Other_Unknown",
}
WAREHOUSE.TransportType={
AIRPLANE="Air_TransportPlane",
HELICOPTER="Air_TransportHelo",
APC="Ground_APC",
TRAIN="Ground_Train",
SHIP="Naval_UnarmedShip",
AIRCRAFTCARRIER="Naval_AircraftCarrier",
WARSHIP="Naval_WarShip",
ARMEDSHIP="Naval_ArmedShip",
SELFPROPELLED="Selfpropelled",
}
WAREHOUSE.Quantity={
ALL="all",
THREEQUARTERS="3/4",
HALF="1/2",
THIRD="1/3",
QUARTER="1/4",
}
_WAREHOUSEDB={
AssetID=0,
Assets={},
WarehouseID=0,
Warehouses={}
}
WAREHOUSE.version="1.0.2"
function WAREHOUSE:New(warehouse,alias)
local self=BASE:Inherit(self,FSM:New())
if type(warehouse)=="string"then
local warehousename=warehouse
warehouse=UNIT:FindByName(warehousename)
if warehouse==nil then
warehouse=STATIC:FindByName(warehousename,true)
end
end
if warehouse==nil then
env.error("ERROR: Warehouse does not exist!")
return nil
end
if warehouse:IsInstanceOf("STATIC")then
self.isUnit=false
elseif warehouse:IsInstanceOf("UNIT")then
self.isUnit=true
if warehouse:IsShip()then
self.isShip=true
end
else
env.error("ERROR: Warehouse is neither STATIC nor UNIT object!")
return nil
end
self.alias=alias or warehouse:GetName()
self.lid=string.format("WAREHOUSE %s | ",self.alias)
self:I(self.lid..string.format("Adding warehouse v%s for structure %s [isUnit=%s, isShip=%s]",WAREHOUSE.version,warehouse:GetName(),tostring(self:IsUnit()),tostring(self:IsShip())))
self.warehouse=warehouse
_WAREHOUSEDB.WarehouseID=_WAREHOUSEDB.WarehouseID+1
self.uid=_WAREHOUSEDB.WarehouseID
self.coalition=self.warehouse:GetCoalition()
self.countryid=self.warehouse:GetCountry()
local _airbase=self:GetCoordinate():GetClosestAirbase(nil,self:GetCoalition())
if _airbase and _airbase:GetCoordinate():Get2DDistance(self:GetCoordinate())<=5000 then
self:SetAirbase(_airbase)
end
if self.isShip then
self.zone=ZONE_AIRBASE:New(self.warehouse:GetName(),1000)
self.spawnzone=ZONE_AIRBASE:New(self.warehouse:GetName(),1000)
else
self.zone=ZONE_RADIUS:New(string.format("Warehouse zone %s",self.warehouse:GetName()),warehouse:GetVec2(),500)
self.spawnzone=ZONE_RADIUS:New(string.format("Warehouse %s spawn zone",self.warehouse:GetName()),warehouse:GetVec2(),250)
end
self:SetMarker(true)
self:SetReportOff()
self:SetRunwayRepairtime()
self.allowSpawnOnClientSpots=false
_WAREHOUSEDB.Warehouses[self.uid]=self
self:SetStartState("NotReadyYet")
self:AddTransition("NotReadyYet","Load","Loaded")
self:AddTransition("Stopped","Load","Loaded")
self:AddTransition("NotReadyYet","Start","Running")
self:AddTransition("Loaded","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","AddAsset","*")
self:AddTransition("*","NewAsset","*")
self:AddTransition("*","AddRequest","*")
self:AddTransition("Running","Request","*")
self:AddTransition("Running","RequestSpawned","*")
self:AddTransition("Attacked","Request","*")
self:AddTransition("*","Unloaded","*")
self:AddTransition("*","AssetSpawned","*")
self:AddTransition("*","AssetLowFuel","*")
self:AddTransition("*","Arrived","*")
self:AddTransition("*","Delivered","*")
self:AddTransition("Running","SelfRequest","*")
self:AddTransition("Attacked","SelfRequest","*")
self:AddTransition("Running","Pause","Paused")
self:AddTransition("Paused","Unpause","Running")
self:AddTransition("*","Stop","Stopped")
self:AddTransition("Stopped","Restart","Running")
self:AddTransition("Loaded","Restart","Running")
self:AddTransition("*","Save","*")
self:AddTransition("*","Attacked","Attacked")
self:AddTransition("Attacked","Defeated","Running")
self:AddTransition("*","ChangeCountry","*")
self:AddTransition("Attacked","Captured","Running")
self:AddTransition("*","AirbaseCaptured","*")
self:AddTransition("*","AirbaseRecaptured","*")
self:AddTransition("*","RunwayDestroyed","*")
self:AddTransition("*","RunwayRepaired","*")
self:AddTransition("*","AssetDead","*")
self:AddTransition("*","Destroyed","Destroyed")
self:AddTransition("Destroyed","Respawn","Running")
return self
end
function WAREHOUSE:SetDebugOn()
self.Debug=true
return self
end
function WAREHOUSE:SetDebugOff()
self.Debug=false
return self
end
function WAREHOUSE:SetReportOn()
self.Report=true
return self
end
function WAREHOUSE:SetReportOff()
self.Report=false
return self
end
function WAREHOUSE:SetSafeParkingOn()
self.safeparking=true
return self
end
function WAREHOUSE:SetSafeParkingOff()
self.safeparking=false
return self
end
function WAREHOUSE:SetAllowSpawnOnClientParking()
self.allowSpawnOnClientSpots=true
return self
end
function WAREHOUSE:SetLowFuelThreshold(threshold)
self.lowfuelthresh=threshold or 0.15
return self
end
function WAREHOUSE:SetStatusUpdate(timeinterval)
self.dTstatus=timeinterval
return self
end
function WAREHOUSE:SetVerbosityLevel(VerbosityLevel)
self.verbosity=VerbosityLevel or 0
return self
end
function WAREHOUSE:SetSpawnZone(zone,maxdist)
self.spawnzone=zone
self.spawnzonemaxdist=maxdist or 5000
return self
end
function WAREHOUSE:GetSpawnZone()
return self.spawnzone
end
function WAREHOUSE:SetWarehouseZone(zone)
self.zone=zone
return self
end
function WAREHOUSE:GetWarehouseZone()
return self.zone
end
function WAREHOUSE:SetAutoDefenceOn()
self.autodefence=true
return self
end
function WAREHOUSE:SetAutoDefenceOff()
self.autodefence=false
return self
end
function WAREHOUSE:SetParkingIDs(ParkingIDs)
if type(ParkingIDs)~="table"then
ParkingIDs={ParkingIDs}
end
self.parkingIDs=ParkingIDs
return self
end
function WAREHOUSE:_CheckParkingValid(spot)
if self.parkingIDs==nil then
return true
end
for _,id in pairs(self.parkingIDs or{})do
if spot.TerminalID==id then
return true
end
end
return false
end
function WAREHOUSE:_CheckParkingAsset(spot,asset)
if asset.parkingIDs==nil then
return true
end
for _,id in pairs(asset.parkingIDs or{})do
if spot.TerminalID==id then
return true
end
end
return false
end
function WAREHOUSE:SetSaveOnMissionEnd(path,filename)
self.autosave=true
self.autosavepath=path
self.autosavefile=filename
return self
end
function WAREHOUSE:SetMarker(switch)
if switch==false then
self.markerOn=false
else
self.markerOn=true
end
return self
end
function WAREHOUSE:SetRespawnAfterDestroyed(delay)
self.respawnafterdestroyed=true
self.respawndelay=delay
return self
end
function WAREHOUSE:SetAirbase(airbase)
self.airbase=airbase
if airbase~=nil then
self.airbasename=airbase:GetName()
else
self.airbasename=nil
end
return self
end
function WAREHOUSE:SetRoadConnection(coordinate)
if coordinate then
self.road=coordinate:GetClosestPointToRoad()
else
self.road=false
end
return self
end
function WAREHOUSE:SetRailConnection(coordinate)
if coordinate then
self.rail=coordinate:GetClosestPointToRoad(true)
else
self.rail=false
end
return self
end
function WAREHOUSE:SetPortZone(zone)
self.portzone=zone
return self
end
function WAREHOUSE:SetHarborZone(zone)
self.harborzone=zone
return self
end
function WAREHOUSE:AddShippingLane(remotewarehouse,group,oneway)
if self.portzone==nil or remotewarehouse.portzone==nil then
local text=string.format("ERROR: Sending or receiving warehouse does not have a port zone defined. Adding shipping lane not possible!")
self:_ErrorMessage(text,5)
return self
end
local startcoord=self.portzone:GetRandomCoordinate()
local finalcoord=remotewarehouse.portzone:GetRandomCoordinate()
local lane=self:_NewLane(group,startcoord,finalcoord)
if self.Debug then
for i=1,#lane do
local coord=lane[i]
local text=string.format("Shipping lane %s to %s. Point %d.",self.alias,remotewarehouse.alias,i)
coord:MarkToCoalition(text,self:GetCoalition())
end
end
local remotename=remotewarehouse.warehouse:GetName()
if self.shippinglanes[remotename]==nil then
self.shippinglanes[remotename]={}
end
table.insert(self.shippinglanes[remotename],lane)
if not oneway then
remotewarehouse:AddShippingLane(self,group,true)
end
return self
end
function WAREHOUSE:AddOffRoadPath(remotewarehouse,group,oneway)
local startcoord=self.spawnzone:GetRandomCoordinate()
local finalcoord=remotewarehouse.spawnzone:GetRandomCoordinate()
local path=self:_NewLane(group,startcoord,finalcoord)
if path==nil then
self:E(self.lid.."ERROR: Offroad path could not be added. Group present in ME?")
return
end
if path and self.Debug then
for i=1,#path do
local coord=path[i]
local text=string.format("Off road path from %s to %s. Point %d.",self.alias,remotewarehouse.alias,i)
coord:MarkToCoalition(text,self:GetCoalition())
end
end
local remotename=remotewarehouse.warehouse:GetName()
if self.offroadpaths[remotename]==nil then
self.offroadpaths[remotename]={}
end
table.insert(self.offroadpaths[remotename],path)
if not oneway then
remotewarehouse:AddOffRoadPath(self,group,true)
end
return self
end
function WAREHOUSE:_NewLane(group,startcoord,finalcoord)
local lane=nil
if group then
local lanepoints=group:GetTemplateRoutePoints()
local laneF=lanepoints[1]
local laneL=lanepoints[#lanepoints]
local coordF=COORDINATE:New(laneF.x,0,laneF.y)
local coordL=COORDINATE:New(laneL.x,0,laneL.y)
local distF=startcoord:Get2DDistance(coordF)
local distL=startcoord:Get2DDistance(coordL)
lane={}
if distF<distL then
for i=1,#lanepoints do
local point=lanepoints[i]
local coord=COORDINATE:New(point.x,0,point.y)
table.insert(lane,coord)
end
else
for i=#lanepoints,1,-1 do
local point=lanepoints[i]
local coord=COORDINATE:New(point.x,0,point.y)
table.insert(lane,coord)
end
end
table.insert(lane,#lane,finalcoord)
end
return lane
end
function WAREHOUSE:IsNotReadyYet()
return self:is("NotReadyYet")
end
function WAREHOUSE:IsLoaded()
return self:is("Loaded")
end
function WAREHOUSE:IsRunning()
return self:is("Running")
end
function WAREHOUSE:IsPaused()
return self:is("Paused")
end
function WAREHOUSE:IsAttacked()
return self:is("Attacked")
end
function WAREHOUSE:IsDestroyed()
return self:is("Destroyed")
end
function WAREHOUSE:IsStopped()
return self:is("Stopped")
end
function WAREHOUSE:HasConnectionRoad(warehouse,markpath,smokepath)
if warehouse then
if self.road and warehouse.road then
local _,length,gotpath=self.road:GetPathOnRoad(warehouse.road,false,false,markpath,smokepath)
return gotpath,length or-1
else
return false,-1
end
end
return nil,-1
end
function WAREHOUSE:HasConnectionRail(warehouse,markpath,smokepath)
if warehouse then
if self.rail and warehouse.rail then
local _,length,gotpath=self.road:GetPathOnRoad(warehouse.road,false,true,markpath,smokepath)
return gotpath,length or-1
else
return false,-1
end
end
return nil,-1
end
function WAREHOUSE:HasConnectionNaval(warehouse,markpath,smokepath)
if warehouse then
if warehouse.warehouse:GetName()==self.warehouse:GetName()then
return true,1
end
local shippinglane=self.shippinglanes[warehouse.warehouse:GetName()]
if shippinglane then
return true,1
else
self:T2(string.format("No shipping lane defined between warehouse %s and %s!",self.alias,warehouse.alias))
end
end
return nil,-1
end
function WAREHOUSE:HasConnectionOffRoad(warehouse,markpath,smokepath)
if warehouse then
if warehouse.warehouse:GetName()==self.warehouse:GetName()then
return true,1
end
local offroadpath=self.offroadpaths[warehouse.warehouse:GetName()]
if offroadpath~=nil then
return true,1
else
self:T2(string.format("No off-road path defined between warehouse %s and %s!",self.alias,warehouse.alias))
end
end
return nil,-1
end
function WAREHOUSE:GetNumberOfAssets(Descriptor,DescriptorValue,OnlyMobile)
if Descriptor==nil or DescriptorValue==nil then
return#self.stock
else
local _stock,_nstock=self:_FilterStock(self.stock,Descriptor,DescriptorValue,nil,OnlyMobile)
return _nstock
end
end
function WAREHOUSE:GetCoordinate()
return self.warehouse:GetCoordinate()
end
function WAREHOUSE:GetVec3()
local vec3=self.warehouse:GetVec3()
return vec3
end
function WAREHOUSE:GetVec2()
local vec2=self.warehouse:GetVec2()
return vec2
end
function WAREHOUSE:GetCoalition()
return self.warehouse:GetCoalition()
end
function WAREHOUSE:GetCoalitionName()
return self.warehouse:GetCoalitionName()
end
function WAREHOUSE:GetCountry()
return self.warehouse:GetCountry()
end
function WAREHOUSE:GetCountryName()
return self.warehouse:GetCountryName()
end
function WAREHOUSE:GetAirbase()
return self.airbase
end
function WAREHOUSE:GetAirbaseName()
local name="none"
if self.airbase then
name=self.airbase:GetName()
end
return name
end
function WAREHOUSE:GetAirbaseCategory()
local category=-1
if self.airbase then
category=self.airbase:GetAirbaseCategory()
end
return category
end
function WAREHOUSE:GetAssignment(request)
return tostring(request.assignment)
end
function WAREHOUSE:FindWarehouseInDB(uid)
return _WAREHOUSEDB.Warehouses[uid]
end
function WAREHOUSE:FindNearestWarehouse(MinAssets,Descriptor,DescriptorValue,Coalition,RefCoordinate)
if Descriptor~=nil and DescriptorValue~=nil then
MinAssets=MinAssets or 1
else
MinAssets=MinAssets or 0
end
local anycoalition=nil
if Coalition~=nil then
if Coalition==false then
anycoalition=true
else
end
else
if self~=nil then
Coalition=self:GetCoalition()
else
anycoalition=true
end
end
local coord=RefCoordinate or self:GetCoordinate()
local nearest=nil
local distmin=nil
for wid,warehouse in pairs(_WAREHOUSEDB.Warehouses)do
local warehouse=warehouse
local dist=coord:Get2DDistance(warehouse:GetCoordinate())
if dist>0 then
local samecoalition=anycoalition or Coalition==warehouse:GetCoalition()
if samecoalition and not(warehouse:IsNotReadyYet()or warehouse:IsStopped()or warehouse:IsDestroyed())then
local nassets=warehouse:GetNumberOfAssets(Descriptor,DescriptorValue)
local enough=true
if Descriptor and DescriptorValue then
enough=nassets>=MinAssets
end
if enough and(distmin==nil or dist<distmin)then
distmin=dist
nearest=warehouse
end
end
end
end
return nearest,distmin
end
function WAREHOUSE:FindAssetInDB(group)
local wid,aid,rid=self:_GetIDsFromGroup(group)
if aid~=nil then
local asset=_WAREHOUSEDB.Assets[aid]
self:T2({asset=asset})
if asset==nil then
self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!",group:GetName()),0)
end
return asset
end
self:_ErrorMessage(string.format("ERROR: Group %s does not contain an asset ID in its name!",group:GetName()),0)
return nil
end
function WAREHOUSE:IsRunwayOperational()
if self.airbase then
if self.runwaydestroyed then
return false
else
return true
end
end
return nil
end
function WAREHOUSE:SetRunwayRepairtime(RepairTime)
self.runwayrepairtime=RepairTime or 3600
return self
end
function WAREHOUSE:GetRunwayRepairtime()
if self.runwaydestroyed then
local Tnow=timer.getAbsTime()
local Tsince=Tnow-self.runwaydestroyed
local Trepair=math.max(self.runwayrepairtime-Tsince,0)
return Trepair
end
return 0
end
function WAREHOUSE:IsUnit()
return self.isUnit
end
function WAREHOUSE:IsStatic()
return not self.isUnit
end
function WAREHOUSE:IsShip()
return self.isShip
end
function WAREHOUSE:onafterStart(From,Event,To)
local text=string.format("Starting warehouse %s alias %s:\n",self.warehouse:GetName(),self.alias)
text=text..string.format("Coalition = %s\n",self:GetCoalitionName())
text=text..string.format("Country  = %s\n",self:GetCountryName())
text=text..string.format("Airbase  = %s (category=%d)\n",self:GetAirbaseName(),self:GetAirbaseCategory())
env.info(text)
self.warehouse:SetState(self.warehouse,"WAREHOUSE",self)
local _road=self.spawnzone:GetCoordinate():GetClosestPointToRoad()
if _road and self.road==nil then
local _Droad=_road:Get2DDistance(self.spawnzone:GetCoordinate())
if _Droad<3000 then
self.road=_road
end
end
if self.road and self.markerOn then
self.markroad=self.road:MarkToCoalition(string.format("%s road connection.",self.alias),self:GetCoalition(),true)
end
local _rail=self.spawnzone:GetCoordinate():GetClosestPointToRoad(true)
if _rail and self.rail==nil then
local _Drail=_rail:Get2DDistance(self.spawnzone:GetCoordinate())
if _Drail<3000 then
self.rail=_rail
end
end
if self.rail and self.markerOn then
self.markrail=self.rail:MarkToCoalition(string.format("%s rail connection.",self.alias),self:GetCoalition(),true)
end
self:HandleEvent(EVENTS.Birth,self._OnEventBirth)
self:HandleEvent(EVENTS.EngineStartup,self._OnEventEngineStartup)
self:HandleEvent(EVENTS.Takeoff,self._OnEventTakeOff)
self:HandleEvent(EVENTS.Land,self._OnEventLanding)
self:HandleEvent(EVENTS.EngineShutdown,self._OnEventEngineShutdown)
self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrDead)
self:HandleEvent(EVENTS.Dead,self._OnEventCrashOrDead)
self:HandleEvent(EVENTS.BaseCaptured,self._OnEventBaseCaptured)
self:HandleEvent(EVENTS.MissionEnd,self._OnEventMissionEnd)
self:HandleEvent(EVENTS.EngineShutdown,self._OnEventArrived)
self:__Status(-1)
end
function WAREHOUSE:onafterRestart(From,Event,To)
self:I(self.lid..string.format("Restarting Warehouse %s.",self.alias))
self:HandleEvent(EVENTS.Birth,self._OnEventBirth)
self:HandleEvent(EVENTS.EngineStartup,self._OnEventEngineStartup)
self:HandleEvent(EVENTS.Takeoff,self._OnEventTakeOff)
self:HandleEvent(EVENTS.Land,self._OnEventLanding)
self:HandleEvent(EVENTS.EngineShutdown,self._OnEventEngineShutdown)
self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrDead)
self:HandleEvent(EVENTS.Dead,self._OnEventCrashOrDead)
self:HandleEvent(EVENTS.BaseCaptured,self._OnEventBaseCaptured)
self:HandleEvent(EVENTS.EngineShutdown,self._OnEventArrived)
self:__Status(-1)
end
function WAREHOUSE:onafterStop(From,Event,To)
self:_InfoMessage(string.format("Warehouse %s stopped!",self.alias))
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.EngineStartup)
self:UnHandleEvent(EVENTS.Takeoff)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.BaseCaptured)
self.pending=nil
self.pending={}
self.queue=nil
self.queue={}
self.stock=nil
self.stock={}
self:_UpdateWarehouseMarkText()
self.CallScheduler:Clear()
end
function WAREHOUSE:onafterPause(From,Event,To)
self:I(self.lid..string.format("Warehouse %s paused! Queued requests are not processed in this state.",self.alias))
end
function WAREHOUSE:onafterUnpause(From,Event,To)
self:I(self.lid..string.format("Warehouse %s unpaused! Processing of requests is resumed.",self.alias))
end
function WAREHOUSE:onafterStatus(From,Event,To)
if self.verbosity>=1 then
local FSMstate=self:GetState()
local coalition=self:GetCoalitionName()
local country=self:GetCountryName()
self:I(self.lid..string.format("State=%s %s [%s]: Assets=%d,  Requests: waiting=%d, pending=%d",FSMstate,country,coalition,#self.stock,#self.queue,#self.pending))
end
self:_JobDone()
self:_DisplayStatus()
self:_CheckConquered()
if self:IsRunwayOperational()==false then
local Trepair=self:GetRunwayRepairtime()
self:I(self.lid..string.format("Runway destroyed! Will be repaired in %d sec",Trepair))
if Trepair==0 then
self:RunwayRepaired()
end
end
self:_CheckRequestConsistancy(self.queue)
if self:IsRunning()or self:IsAttacked()then
local request=self:_CheckQueue()
if request then
self:Request(request)
end
end
self:_PrintQueue(self.queue,"Queue waiting")
self:_PrintQueue(self.pending,"Queue pending")
self:_UpdateWarehouseMarkText()
if self.Debug then
self:_DisplayStockItems(self.stock)
end
self:__Status(-self.dTstatus)
end
function WAREHOUSE:_JobDone()
local done={}
for _,request in pairs(self.pending)do
local request=request
if request.born then
local ncargo=0
if request.cargogroupset then
ncargo=request.cargogroupset:Count()
end
local ntransport=0
if request.transportgroupset then
ntransport=request.transportgroupset:Count()
end
local ncargotot=request.nasset
local ncargodelivered=request.ndelivered
local ncargodead=ncargotot-ncargodelivered-ncargo
local ntransporttot=request.ntransport
local ntransporthome=request.ntransporthome
local ntransportdead=ntransporttot-ntransporthome-ntransport
local text=string.format("Request id=%d: Cargo: Ntot=%d, Nalive=%d, Ndelivered=%d, Ndead=%d  |  Transport: Ntot=%d, Nalive=%d, Nhome=%d, Ndead=%d",
request.uid,ncargotot,ncargo,ncargodelivered,ncargodead,ntransporttot,ntransport,ntransporthome,ntransportdead)
self:T(self.lid..text)
if ncargo==0 then
if not self.delivered[request.uid]then
self:Delivered(request)
end
if ntransport==0 then
if self.verbosity>=1 then
local text=string.format("Warehouse %s: Job on request id=%d for warehouse %s done!\n",self.alias,request.uid,request.warehouse.alias)
text=text..string.format("- %d of %d assets delivered. Casualties %d.",ncargodelivered,ncargotot,ncargodead)
if request.ntransport>0 then
text=text..string.format("\n- %d of %d transports returned home. Casualties %d.",ntransporthome,ntransporttot,ntransportdead)
end
self:_InfoMessage(text,20)
end
table.insert(done,request)
else
for _,_group in pairs(request.transportgroupset:GetSetObjects())do
local group=_group
if group and group:IsAlive()then
local category=group:GetCategory()
local speed=group:GetVelocityKMH()
local notmoving=speed<1
local airbase=group:GetCoordinate():GetClosestAirbase():GetName()
local athomebase=self.airbase and self.airbase:GetName()==airbase
local onground=not group:InAir()
local inspawnzone=group:IsPartlyOrCompletelyInZone(self.spawnzone)
local ishome=false
if category==Group.Category.GROUND or category==Group.Category.HELICOPTER then
ishome=inspawnzone and onground and notmoving
elseif category==Group.Category.AIRPLANE then
ishome=athomebase and onground and notmoving
end
local text=string.format("Group %s: speed=%d km/h, onground=%s , airbase=%s, spawnzone=%s ==> ishome=%s",group:GetName(),speed,tostring(onground),airbase,tostring(inspawnzone),tostring(ishome))
self:T(self.lid..text)
if ishome then
local text=string.format("Warehouse %s: Transport group arrived back home and no cargo left for request id=%d.\nSending transport group %s back to stock.",self.alias,request.uid,group:GetName())
self:T(self.lid..text)
if self.Debug then
group:SmokeRed()
end
self:Arrived(group)
end
end
end
end
else
if ntransport==0 and request.ntransport>0 then
local ncargoalive=0
for _,_group in pairs(request.cargogroupset:GetSetObjects())do
local groupname=_group:GetName()
local group=GROUP:FindByName(groupname.."#CARGO")
if group and group:IsAlive()then
if group:IsPartlyOrCompletelyInZone(self.spawnzone)then
if self.Debug then
group:SmokeBlue()
end
self:AddAsset(group)
ncargoalive=ncargoalive+1
end
end
end
self:_InfoMessage(string.format("Warehouse %s: All transports of request id=%s dead! Putting remaining %s cargo assets back into warehouse!",self.alias,request.uid,ncargoalive))
end
end
end
end
for _,request in pairs(done)do
self:_DeleteQueueItem(request,self.pending)
end
end
function WAREHOUSE:_CheckAssetStatus()
local function _CheckGroup(_request,_group)
local request=_request
local group=_group
if group and group:IsAlive()then
local category=group:GetCategory()
for _,_unit in pairs(group:GetUnits())do
local unit=_unit
if unit and unit:IsAlive()then
local unitid=unit:GetID()
local life9=unit:GetLife()
local life0=unit:GetLife0()
local life=life9/life0*100
local speed=unit:GetVelocityMPS()
local onground=unit:InAir()
local problem=false
if life<10 then
self:T(string.format("Unit %s is heavily damaged!",unit:GetName()))
end
if speed<1 and unit:GetSpeedMax()>1 and onground then
self:T(string.format("Unit %s is not moving!",unit:GetName()))
problem=true
end
if problem then
if request.assetproblem[unitid]then
local deltaT=timer.getAbsTime()-request.assetproblem[unitid]
if deltaT>300 then
unit:Destroy()
end
else
request.assetproblem[unitid]=timer.getAbsTime()
end
end
end
end
end
end
for _,request in pairs(self.pending)do
local request=request
if request.cargogroupset then
for _,_group in pairs(request.cargogroupset:GetSet())do
local group=_group
_CheckGroup(request,group)
end
end
if request.transportgroupset then
for _,group in pairs(request.transportgroupset:GetSet())do
_CheckGroup(request,group)
end
end
end
end
function WAREHOUSE:onafterAddAsset(From,Event,To,group,ngroups,forceattribute,forcecargobay,forceweight,loadradius,skill,liveries,assignment,other)
self:T({group=group,ngroups=ngroups,forceattribute=forceattribute,forcecargobay=forcecargobay,forceweight=forceweight})
local n=ngroups or 1
if type(group)=="string"then
group=GROUP:FindByName(group)
end
if liveries and type(liveries)=="string"then
liveries={liveries}
end
if group then
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid and aid and rid then
local warehouse=self:FindWarehouseInDB(wid)
if warehouse then
local request=warehouse:_GetRequestOfGroup(group,warehouse.pending)
if request then
local istransport=warehouse:_GroupIsTransport(group,request)
if istransport==true then
request.ntransporthome=request.ntransporthome+1
request.transportgroupset:Remove(group:GetName(),true)
local ntrans=request.transportgroupset:Count()
self:T2(warehouse.lid..string.format("Transport %d of %s returned home. TransportSet=%d",request.ntransporthome,tostring(request.ntransport),ntrans))
elseif istransport==false then
request.ndelivered=request.ndelivered+1
local namewo=self:_GetNameWithOut(group)
request.cargogroupset:Remove(namewo,true)
local ncargo=request.cargogroupset:Count()
self:T2(warehouse.lid..string.format("Cargo %s: %d of %s delivered. CargoSet=%d",namewo,request.ndelivered,tostring(request.nasset),ncargo))
else
self:E(warehouse.lid..string.format("WARNING: Group %s is neither cargo nor transport! Need to investigate...",group:GetName()))
end
if assignment==nil and request.assignment~=nil then
assignment=request.assignment
end
end
end
local asset=self:FindAssetInDB(group)
if asset~=nil then
self:_DebugMessage(string.format("Warehouse %s: Adding KNOWN asset uid=%d with attribute=%s to stock.",self.alias,asset.uid,asset.attribute),5)
if liveries then
if type(liveries)=="table"then
asset.livery=liveries[math.random(#liveries)]
else
asset.livery=liveries
end
end
asset.skill=skill or asset.skill
asset.wid=self.uid
asset.rid=nil
asset.spawned=false
asset.requested=false
asset.isReserved=false
asset.iscargo=nil
asset.arrived=nil
if group:IsAlive()==true then
asset.damage=asset.life0-group:GetLife()
end
table.insert(self.stock,asset)
self:__NewAsset(0.1,asset,assignment or"")
else
self:_ErrorMessage(string.format("ERROR: Known asset could not be found in global warehouse db!"),0)
end
else
self:_DebugMessage(string.format("Warehouse %s: Adding %d NEW assets of group %s to stock",self.alias,n,tostring(group:GetName())),5)
local assets=self:_RegisterAsset(group,n,forceattribute,forcecargobay,forceweight,loadradius,liveries,skill,assignment)
for _,asset in pairs(assets)do
asset.wid=self.uid
asset.rid=nil
table.insert(self.stock,asset)
self:__NewAsset(0.1,asset,assignment or"")
end
end
if group:IsAlive()==true then
self:_DebugMessage(string.format("Removing group %s",group:GetName()),5)
local opsgroup=_DATABASE:GetOpsGroup(group:GetName())
if opsgroup then
opsgroup:Despawn(0,true)
opsgroup:__Stop(-0.01)
else
group:Destroy()
end
else
local opsgroup=_DATABASE:GetOpsGroup(group:GetName())
if opsgroup then
opsgroup:Stop()
end
end
else
self:E(self.lid.."ERROR: Unknown group added as asset!")
self:E({unknowngroup=group})
end
end
function WAREHOUSE:_RegisterAsset(group,ngroups,forceattribute,forcecargobay,forceweight,loadradius,liveries,skill,assignment)
self:F({groupname=group:GetName(),ngroups=ngroups,forceattribute=forceattribute,forcecargobay=forcecargobay,forceweight=forceweight})
local n=ngroups or 1
local function _GetObjectSize(DCSdesc)
if DCSdesc.box then
local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x)
local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y)
local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z)
return math.max(x,z),x,y,z
end
return 0,0,0,0
end
local templategroupname=group:GetName()
local Descriptors=group:GetUnit(1):GetDesc()
local Category=group:GetCategory()
local TypeName=group:GetTypeName()
local SpeedMax=group:GetSpeedMax()
local RangeMin=group:GetRange()
local smax,sx,sy,sz=_GetObjectSize(Descriptors)
local weight=0
local cargobay={}
local cargobaytot=0
local cargobaymax=0
local weights={}
for _i,_unit in pairs(group:GetUnits())do
local unit=_unit
local Desc=unit:GetDesc()
local unitweight=forceweight or Desc.massEmpty
if unitweight then
weight=weight+unitweight
weights[_i]=unitweight
end
local cargomax=0
local massfuel=Desc.fuelMassMax or 0
local massempty=Desc.massEmpty or 0
local massmax=Desc.massMax or 0
cargomax=massmax-massfuel-massempty
self:T3(self.lid..string.format("Unit name=%s: mass empty=%.1f kg, fuel=%.1f kg, max=%.1f kg ==> cargo=%.1f kg",unit:GetName(),unitweight,massfuel,massmax,cargomax))
local bay=forcecargobay or unit:GetCargoBayFreeWeight()
table.insert(cargobay,bay)
cargobaytot=cargobaytot+bay
if bay>cargobaymax then
cargobaymax=bay
end
end
local attribute=forceattribute or self:_GetAttribute(group)
local assets={}
for i=1,n do
local asset={}
_WAREHOUSEDB.AssetID=_WAREHOUSEDB.AssetID+1
asset.uid=_WAREHOUSEDB.AssetID
asset.templatename=templategroupname
asset.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template)
asset.category=Category
asset.unittype=TypeName
asset.nunits=#asset.template.units
asset.range=RangeMin
asset.speedmax=SpeedMax
asset.size=smax
asset.weight=weight
asset.weights=weights
asset.DCSdesc=Descriptors
asset.attribute=attribute
asset.cargobay=cargobay
asset.cargobaytot=cargobaytot
asset.cargobaymax=cargobaymax
asset.loadradius=loadradius
if liveries then
asset.livery=liveries[math.random(#liveries)]
end
asset.skill=skill
asset.assignment=assignment
asset.spawned=false
asset.requested=false
asset.isReserved=false
asset.life0=group:GetLife0()
asset.damage=0
asset.spawngroupname=string.format("%s_AID-%d",templategroupname,asset.uid)
if i==1 then
self:_AssetItemInfo(asset)
end
_WAREHOUSEDB.Assets[asset.uid]=asset
table.insert(assets,asset)
end
return assets
end
function WAREHOUSE:_AssetItemInfo(asset)
local text=string.format("\nNew asset with id=%d for warehouse %s:\n",asset.uid,self.alias)
text=text..string.format("Spawngroup name= %s\n",asset.spawngroupname)
text=text..string.format("Template name  = %s\n",asset.templatename)
text=text..string.format("Unit type      = %s\n",asset.unittype)
text=text..string.format("Attribute      = %s\n",asset.attribute)
text=text..string.format("Category       = %d\n",asset.category)
text=text..string.format("Units #        = %d\n",asset.nunits)
text=text..string.format("Speed max      = %5.2f km/h\n",asset.speedmax)
text=text..string.format("Range max      = %5.2f km\n",asset.range/1000)
text=text..string.format("Size  max      = %5.2f m\n",asset.size)
text=text..string.format("Weight total   = %5.2f kg\n",asset.weight)
text=text..string.format("Cargo bay tot  = %5.2f kg\n",asset.cargobaytot)
text=text..string.format("Cargo bay max  = %5.2f kg\n",asset.cargobaymax)
text=text..string.format("Load radius    = %s m\n",tostring(asset.loadradius))
text=text..string.format("Skill          = %s\n",tostring(asset.skill))
text=text..string.format("Livery         = %s",tostring(asset.livery))
self:I(self.lid..text)
self:T({DCSdesc=asset.DCSdesc})
self:T3({Template=asset.template})
end
function WAREHOUSE:onafterNewAsset(From,Event,To,asset,assignment)
self:T(self.lid..string.format("New asset %s id=%d with assignment %s.",tostring(asset.templatename),asset.uid,tostring(assignment)))
end
function WAREHOUSE:onbeforeAddRequest(From,Event,To,warehouse,AssetDescriptor,AssetDescriptorValue,nAsset,TransportType,nTransport,Assignment,Prio)
local okay=true
if AssetDescriptor==WAREHOUSE.Descriptor.ATTRIBUTE then
local gotit=false
for _,attribute in pairs(WAREHOUSE.Attribute)do
if AssetDescriptorValue==attribute then
gotit=true
end
end
if not gotit then
self:_ErrorMessage("ERROR: Invalid request. Asset attribute is unknown!",5)
okay=false
end
elseif AssetDescriptor==WAREHOUSE.Descriptor.CATEGORY then
local gotit=false
for _,category in pairs(Group.Category)do
if AssetDescriptorValue==category then
gotit=true
end
end
if not gotit then
self:_ErrorMessage("ERROR: Invalid request. Asset category is unknown!",5)
okay=false
end
elseif AssetDescriptor==WAREHOUSE.Descriptor.GROUPNAME then
if type(AssetDescriptorValue)~="string"then
self:_ErrorMessage("ERROR: Invalid request. Asset template name must be passed as a string!",5)
okay=false
end
elseif AssetDescriptor==WAREHOUSE.Descriptor.UNITTYPE then
if type(AssetDescriptorValue)~="string"then
self:_ErrorMessage("ERROR: Invalid request. Asset unit type must be passed as a string!",5)
okay=false
end
elseif AssetDescriptor==WAREHOUSE.Descriptor.ASSIGNMENT then
if type(AssetDescriptorValue)~="string"then
self:_ErrorMessage("ERROR: Invalid request. Asset assignment type must be passed as a string!",5)
okay=false
end
elseif AssetDescriptor==WAREHOUSE.Descriptor.ASSETLIST then
if type(AssetDescriptorValue)~="table"then
self:_ErrorMessage("ERROR: Invalid request. Asset assignment type must be passed as a table!",5)
okay=false
end
else
self:_ErrorMessage("ERROR: Invalid request. Asset descriptor is not ATTRIBUTE, CATEGORY, GROUPNAME, UNITTYPE or ASSIGNMENT!",5)
okay=false
end
if self:IsStopped()then
self:_ErrorMessage("ERROR: Invalid request. Warehouse is stopped!",0)
okay=false
end
if self:IsDestroyed()and not self.respawnafterdestroyed then
self:_ErrorMessage("ERROR: Invalid request. Warehouse is destroyed!",0)
okay=false
end
return okay
end
function WAREHOUSE:onafterAddRequest(From,Event,To,warehouse,AssetDescriptor,AssetDescriptorValue,nAsset,TransportType,nTransport,Prio,Assignment)
nAsset=nAsset or 1
TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED
Prio=Prio or 50
if nTransport==nil then
if TransportType==WAREHOUSE.TransportType.SELFPROPELLED then
nTransport=0
else
nTransport=1
end
end
local toself=false
if self.warehouse:GetName()==warehouse.warehouse:GetName()then
toself=true
end
self.queueid=self.queueid+1
local request={
uid=self.queueid,
prio=Prio,
warehouse=warehouse,
assetdesc=AssetDescriptor,
assetdescval=AssetDescriptorValue,
nasset=nAsset,
transporttype=TransportType,
ntransport=nTransport,
assignment=tostring(Assignment),
airbase=warehouse:GetAirbase(),
category=warehouse:GetAirbaseCategory(),
ndelivered=0,
ntransporthome=0,
assets={},
toself=toself,
}
table.insert(self.queue,request)
local descval="assetlist"
if request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST then
else
descval=tostring(request.assetdescval)
end
local text=string.format("Warehouse %s: New request from warehouse %s.\nDescriptor %s=%s, #assets=%s; Transport=%s, #transports=%s.",
self.alias,warehouse.alias,request.assetdesc,descval,tostring(request.nasset),request.transporttype,tostring(request.ntransport))
self:_DebugMessage(text,5)
end
function WAREHOUSE:onbeforeRequest(From,Event,To,Request)
self:T3({warehouse=self.alias,request=Request})
local distance=self:GetCoordinate():Get2DDistance(Request.warehouse:GetCoordinate())
local _assets=Request.cargoassets
if Request.nasset==0 then
local text=string.format("Warehouse %s: Request denied! Zero assets were requested.",self.alias)
self:_InfoMessage(text,10)
return false
end
for _,_asset in pairs(_assets)do
local asset=_asset
if asset.range<distance then
local text=string.format("Request denied! Destination %s is out of range for asset %s.",Request.airbase:GetName(),asset.templatename)
self:_InfoMessage(text,10)
if not(self.isUnit or Request.warehouse.isUnit)then
self:_DeleteQueueItem(Request,self.queue)
end
return false
end
end
return true
end
function WAREHOUSE:onafterRequest(From,Event,To,Request)
if self.verbosity>=1 then
local text=string.format("Warehouse %s: Processing request id=%d from warehouse %s.\n",self.alias,Request.uid,Request.warehouse.alias)
text=text..string.format("Requested %s assets of %s=%s.\n",tostring(Request.nasset),Request.assetdesc,Request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST and"Asset list"or Request.assetdescval)
text=text..string.format("Transports %s of type %s.",tostring(Request.ntransport),tostring(Request.transporttype))
self:_InfoMessage(text,5)
end
Request.timestamp=timer.getAbsTime()
self:_SpawnAssetRequest(Request)
local _assetstock=Request.transportassets
local Parking={}
if Request.transportcategory==Group.Category.AIRPLANE or Request.transportcategory==Group.Category.HELICOPTER then
Parking=self:_FindParkingForAssets(self.airbase,_assetstock)
end
local _transportassets={}
for i=1,Request.ntransport do
local _assetitem=_assetstock[i]
local _alias=_assetitem.spawngroupname
_assetitem.rid=Request.uid
_assetitem.spawned=false
_assetitem.iscargo=false
_assetitem.arrived=false
local spawngroup=nil
Request.assets[_assetitem.uid]=_assetitem
if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem,Request,Parking[_assetitem.uid],true)
elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem,Request,Parking[_assetitem.uid],false)
elseif Request.transporttype==WAREHOUSE.TransportType.APC then
spawngroup=self:_SpawnAssetGroundNaval(_alias,_assetitem,Request,self.spawnzone)
elseif Request.transporttype==WAREHOUSE.TransportType.TRAIN then
self:_ErrorMessage("ERROR: Cargo transport by train not supported yet!")
return
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.NAVALCARRIER then
spawngroup=self:_SpawnAssetGroundNaval(_alias,_assetitem,Request,self.portzone)
elseif Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
self:_ErrorMessage("ERROR: Transport type selfpropelled was already handled above. We should not get here!")
return
else
self:_ErrorMessage("ERROR: Unknown transport type!")
return
end
if spawngroup then
self:__AssetSpawned(0.01,spawngroup,_assetitem,Request)
end
end
Request.assetproblem={}
table.insert(self.pending,Request)
self:_DeleteQueueItem(Request,self.queue)
end
function WAREHOUSE:onafterRequestSpawned(From,Event,To,Request,CargoGroupSet,TransportGroupSet)
local _cargotype=Request.cargoattribute
local _cargocategory=Request.cargocategory
if Request.toself then
self:_DebugMessage(string.format("Selfrequest! Current status %s",self:GetState()))
self:__SelfRequest(1,CargoGroupSet,Request)
return
end
if Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
self:T2(self.lid..string.format("Got selfpropelled request for %d assets.",CargoGroupSet:Count()))
for _,_group in pairs(CargoGroupSet:GetSetObjects())do
local group=_group
if _cargocategory==Group.Category.GROUND then
self:T2(self.lid..string.format("Route ground group %s.",group:GetName()))
local ToCoordinate=Request.warehouse.spawnzone:GetRandomCoordinate()
if self.Debug then
ToCoordinate:MarkToAll(string.format("Destination of group %s",group:GetName()))
end
self:_RouteGround(group,Request)
elseif _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then
self:T2(self.lid..string.format("Route airborne group %s.",group:GetName()))
self:_RouteAir(group)
elseif _cargocategory==Group.Category.SHIP then
self:T2(self.lid..string.format("Route naval group %s.",group:GetName()))
self:_RouteNaval(group,Request)
elseif _cargocategory==Group.Category.TRAIN then
self:T2(self.lid..string.format("Route train group %s.",group:GetName()))
self:_RouteTrain(group,Request.warehouse.rail)
else
self:E(self.lid..string.format("ERROR: unknown category %s for self propelled cargo %s!",tostring(_cargocategory),tostring(group:GetName())))
end
end
Request.transportgroupset=TransportGroupSet
return
end
local _boardradius=500
if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
_boardradius=5000
elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
elseif Request.transporttype==WAREHOUSE.TransportType.APC then
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
_boardradius=6000
end
local CargoGroups=SET_CARGO:New()
for _,_group in pairs(CargoGroupSet:GetSetObjects())do
local asset=self:FindAssetInDB(_group)
local cargogroup=CARGO_GROUP:New(_group,_cargotype,_group:GetName(),_boardradius,asset.loadradius)
cargogroup:SetWeight(asset.weight)
CargoGroups:AddCargo(cargogroup)
end
local CargoTransport
if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
local PickupAirbaseSet=SET_ZONE:New():AddZone(ZONE_AIRBASE:New(self.airbase:GetName()))
local DeployAirbaseSet=SET_ZONE:New():AddZone(ZONE_AIRBASE:New(Request.airbase:GetName()))
CargoTransport=AI_CARGO_DISPATCHER_AIRPLANE:New(TransportGroupSet,CargoGroups,PickupAirbaseSet,DeployAirbaseSet)
CargoTransport:SetHomeZone(ZONE_AIRBASE:New(self.airbase:GetName()))
elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
local PickupZoneSet=SET_ZONE:New():AddZone(self.spawnzone)
local DeployZoneSet=SET_ZONE:New():AddZone(Request.warehouse.spawnzone)
CargoTransport=AI_CARGO_DISPATCHER_HELICOPTER:New(TransportGroupSet,CargoGroups,PickupZoneSet,DeployZoneSet)
CargoTransport:SetHomeZone(self.spawnzone)
elseif Request.transporttype==WAREHOUSE.TransportType.APC then
local PickupZoneSet=SET_ZONE:New():AddZone(self.spawnzone)
local DeployZoneSet=SET_ZONE:New():AddZone(Request.warehouse.spawnzone)
CargoTransport=AI_CARGO_DISPATCHER_APC:New(TransportGroupSet,CargoGroups,PickupZoneSet,DeployZoneSet,0)
CargoTransport:SetHomeZone(self.spawnzone)
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
local PickupZoneSet=SET_ZONE:New():AddZone(self.portzone)
PickupZoneSet:AddZone(self.harborzone)
local DeployZoneSet=SET_ZONE:New():AddZone(Request.warehouse.harborzone)
local remotename=Request.warehouse.warehouse:GetName()
local ShippingLane=self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])]
CargoTransport=AI_CARGO_DISPATCHER_SHIP:New(TransportGroupSet,CargoGroups,PickupZoneSet,DeployZoneSet,ShippingLane)
CargoTransport:SetHomeZone(self.portzone)
else
self:E(self.lid.."ERROR: Unknown transporttype!")
end
local pickupouter=200
local pickupinner=0
local deployouter=200
local deployinner=0
if Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
pickupouter=1000
pickupinner=20
deployouter=1000
deployinner=0
else
pickupouter=200
pickupinner=0
if self.spawnzone.Radius~=nil then
pickupouter=self.spawnzone.Radius
pickupinner=20
end
deployouter=200
deployinner=0
if self.spawnzone.Radius~=nil then
deployouter=Request.warehouse.spawnzone.Radius
deployinner=20
end
end
CargoTransport:SetPickupRadius(pickupouter,pickupinner)
CargoTransport:SetDeployRadius(deployouter,deployinner)
Request.carriercargo={}
for _,carriergroup in pairs(TransportGroupSet:GetSetObjects())do
local asset=self:FindAssetInDB(carriergroup)
for _i,_carrierunit in pairs(carriergroup:GetUnits())do
local carrierunit=_carrierunit
Request.carriercargo[carrierunit:GetName()]={}
local cargobay=asset.cargobay[_i]
carrierunit:SetCargoBayWeightLimit(cargobay)
self:T2(self.lid..string.format("Cargo bay weight limit of carrier unit %s: %.1f kg.",carrierunit:GetName(),carrierunit:GetCargoBayFreeWeight()))
end
end
function CargoTransport:OnAfterPickedUp(From,Event,To,Carrier,PickupZone)
local warehouse=Carrier:GetState(Carrier,"WAREHOUSE")
local text=string.format("Carrier group %s picked up at pickup zone %s.",Carrier:GetName(),PickupZone:GetName())
warehouse:T(warehouse.lid..text)
end
function CargoTransport:OnAfterDeployed(From,Event,To,Carrier,DeployZone)
local warehouse=Carrier:GetState(Carrier,"WAREHOUSE")
end
function CargoTransport:OnAfterHome(From,Event,To,Carrier,Coordinate,Speed,Height,HomeZone)
local warehouse=Carrier:GetState(Carrier,"WAREHOUSE")
local text=string.format("Carrier group %s going home to zone %s.",Carrier:GetName(),HomeZone:GetName())
warehouse:T(warehouse.lid..text)
end
function CargoTransport:OnAfterLoaded(From,Event,To,Carrier,Cargo,CarrierUnit,PickupZone)
local warehouse=Carrier:GetState(Carrier,"WAREHOUSE")
local text=string.format("Carrier group %s loaded cargo %s into unit %s in pickup zone %s",Carrier:GetName(),Cargo:GetName(),CarrierUnit:GetName(),PickupZone:GetName())
warehouse:T(warehouse.lid..text)
local group=Cargo:GetObject()
local request=warehouse:_GetRequestOfGroup(group,warehouse.pending)
table.insert(request.carriercargo[CarrierUnit:GetName()],warehouse:_GetNameWithOut(Cargo:GetName()))
end
function CargoTransport:OnAfterUnloaded(From,Event,To,Carrier,Cargo,CarrierUnit,DeployZone)
local warehouse=Carrier:GetState(Carrier,"WAREHOUSE")
local group=Cargo:GetObject()
local text=string.format("Cargo group %s was unloaded from carrier unit %s.",tostring(group:GetName()),tostring(CarrierUnit:GetName()))
warehouse:T(warehouse.lid..text)
warehouse:Arrived(group)
end
function CargoTransport:OnAfterBackHome(From,Event,To,Carrier)
local carrier=Carrier
local warehouse=carrier:GetState(carrier,"WAREHOUSE")
carrier:SmokeWhite()
local text=string.format("Carrier %s is back home at warehouse %s.",tostring(Carrier:GetName()),tostring(warehouse.warehouse:GetName()))
MESSAGE:New(text,5):ToAllIf(warehouse.Debug)
warehouse:I(warehouse.lid..text)
warehouse:__Arrived(1,Carrier)
end
CargoTransport:__Start(5)
end
function WAREHOUSE:onafterUnloaded(From,Event,To,group)
self:_DebugMessage(string.format("Cargo %s unloaded!",tostring(group:GetName())),5)
if group and group:IsAlive()then
if self.Debug then
group:SmokeWhite()
end
local speedmax=group:GetSpeedMax()
if group:IsGround()then
if speedmax>1 then
group:RouteGroundTo(self.spawnzone:GetRandomCoordinate(),speedmax*0.5,AI.Task.VehicleFormation.RANK,3)
else
self:Arrived(group)
end
elseif group:IsAir()then
self:Arrived(group)
elseif group:IsShip()then
self:Arrived(group)
end
else
self:E(self.lid..string.format("ERROR unloaded Cargo group is not alive!"))
end
end
function WAREHOUSE:onbeforeArrived(From,Event,To,group)
local asset=self:FindAssetInDB(group)
if asset then
if asset.flightgroup and not asset.arrived then
asset.arrived=true
return false
end
if asset.arrived==true then
return false
else
asset.arrived=true
return true
end
end
end
function WAREHOUSE:onafterArrived(From,Event,To,group)
if self.Debug then
group:SmokeOrange()
end
local request=self:_GetRequestOfGroup(group,self.pending)
if request then
local warehouse=request.warehouse
local istransport=self:_GroupIsTransport(group,request)
if istransport==true then
warehouse=self
elseif istransport==false then
warehouse=request.warehouse
else
self:E(self.lid..string.format("ERROR: Group %s is neither cargo nor transport",group:GetName()))
return
end
self:_DebugMessage(string.format("Group %s arrived at warehouse %s!",tostring(group:GetName()),warehouse.alias),5)
if group:IsGround()and group:GetSpeedMax()>1 then
group:RouteGroundTo(warehouse:GetCoordinate(),group:GetSpeedMax()*0.3,"Off Road")
end
self:T(self.lid.."Asset arrived at warehouse adding in 60 sec")
warehouse:__AddAsset(60,group)
end
end
function WAREHOUSE:onafterDelivered(From,Event,To,request)
if self.verbosity>=1 then
local text=string.format("Warehouse %s: All assets delivered to warehouse %s!",self.alias,request.warehouse.alias)
self:_InfoMessage(text,5)
end
if self.Debug then
self:_Fireworks(request.warehouse:GetCoordinate())
end
self.delivered[request.uid]=true
end
function WAREHOUSE:onafterSelfRequest(From,Event,To,groupset,request)
self:_DebugMessage(string.format("Assets spawned at warehouse %s after self request!",self.alias))
for _,_group in pairs(groupset:GetSetObjects())do
local group=_group
if self.Debug then
group:FlareGreen()
end
end
if self:IsAttacked()then
if self.autodefence then
for _,_group in pairs(groupset:GetSetObjects())do
local group=_group
local speedmax=group:GetSpeedMax()
if group:IsGround()and speedmax>1 and group:IsNotInZone(self.zone)then
group:RouteGroundTo(self.zone:GetRandomCoordinate(),0.8*speedmax,"Off Road")
end
end
end
table.insert(self.defending,request)
end
end
function WAREHOUSE:onafterAttacked(From,Event,To,Coalition,Country)
local text=string.format("Warehouse %s: We are under attack!",self.alias)
self:_InfoMessage(text)
if self.Debug then
self:GetCoordinate():SmokeOrange()
end
if self.autodefence then
local nground=self:GetNumberOfAssets(WAREHOUSE.Descriptor.CATEGORY,Group.Category.GROUND)
local text=string.format("Warehouse auto defence activated.\n")
if nground>0 then
text=text..string.format("Deploying all %d ground assets.",nground)
self:AddRequest(self,WAREHOUSE.Descriptor.CATEGORY,Group.Category.GROUND,WAREHOUSE.Quantity.ALL,nil,nil,0,"AutoDefence")
else
text=text..string.format("No ground assets currently available.")
end
self:_InfoMessage(text)
else
local text=string.format("Warehouse auto defence inactive.")
self:I(self.lid..text)
end
end
function WAREHOUSE:onafterDefeated(From,Event,To)
local text=string.format("Warehouse %s: Enemy attack was defeated!",self.alias)
self:_InfoMessage(text)
if self.Debug then
self:GetCoordinate():SmokeGreen()
end
if self.autodefence then
for _,request in pairs(self.defending)do
for _,_group in pairs(request.cargogroupset:GetSetObjects())do
local group=_group
local speed=group:GetSpeedMax()
if group:IsGround()and speed>1 then
group:RouteGroundTo(self:GetCoordinate(),speed*0.3)
end
self:__AddAsset(60,group)
end
end
self.defending=nil
self.defending={}
end
end
function WAREHOUSE:onafterRespawn(From,Event,To)
local text=string.format("Respawning warehouse %s",self.alias)
self:_InfoMessage(text)
self.warehouse:ReSpawn()
end
function WAREHOUSE:onbeforeChangeCountry(From,Event,To,Country)
local currentCountry=self:GetCountry()
local text=string.format("Warehouse %s: request to change country %d-->%d",self.alias,currentCountry,Country)
self:_DebugMessage(text,10)
if currentCountry~=Country then
return true
end
return false
end
function WAREHOUSE:onafterChangeCountry(From,Event,To,Country)
local CoalitionOld=self:GetCoalition()
self.warehouse:ReSpawn(Country)
local CoalitionNew=self:GetCoalition()
self.queue=nil
self.queue={}
if self.airbasename then
local airbase=AIRBASE:FindByName(self.airbasename)
local airbaseCoalition=airbase:GetCoalition()
if CoalitionNew==airbaseCoalition then
self.airbase=airbase
else
self.airbase=nil
end
end
if self.Debug then
if CoalitionNew==coalition.side.RED then
self:GetCoordinate():SmokeRed()
elseif CoalitionNew==coalition.side.BLUE then
self:GetCoordinate():SmokeBlue()
end
end
end
function WAREHOUSE:onbeforeCaptured(From,Event,To,Coalition,Country)
self:ChangeCountry(Country)
end
function WAREHOUSE:onafterCaptured(From,Event,To,Coalition,Country)
local text=string.format("Warehouse %s: We were captured by enemy coalition (side=%d)!",self.alias,Coalition)
self:_InfoMessage(text)
end
function WAREHOUSE:onafterAirbaseCaptured(From,Event,To,Coalition)
local text=string.format("Warehouse %s: Our airbase %s was captured by the enemy (coalition=%d)!",self.alias,self.airbasename,Coalition)
self:_InfoMessage(text)
if self.Debug then
if Coalition==coalition.side.RED then
self.airbase:GetCoordinate():SmokeRed()
elseif Coalition==coalition.side.BLUE then
self.airbase:GetCoordinate():SmokeBlue()
end
end
self.airbase=nil
end
function WAREHOUSE:onafterAirbaseRecaptured(From,Event,To,Coalition)
local text=string.format("Warehouse %s: We recaptured our airbase %s from the enemy (coalition=%d)!",self.alias,self.airbasename,Coalition)
self:_InfoMessage(text)
self.airbase=AIRBASE:FindByName(self.airbasename)
if self.Debug then
if Coalition==coalition.side.RED then
self.airbase:GetCoordinate():SmokeRed()
elseif Coalition==coalition.side.BLUE then
self.airbase:GetCoordinate():SmokeBlue()
end
end
end
function WAREHOUSE:onafterRunwayDestroyed(From,Event,To)
local text=string.format("Warehouse %s: Runway %s destroyed!",self.alias,self.airbasename)
self:_InfoMessage(text)
self.runwaydestroyed=timer.getAbsTime()
end
function WAREHOUSE:onafterRunwayRepaired(From,Event,To)
local text=string.format("Warehouse %s: Runway %s repaired!",self.alias,self.airbasename)
self:_InfoMessage(text)
self.runwaydestroyed=nil
end
function WAREHOUSE:onafterAssetSpawned(From,Event,To,group,asset,request)
local text=string.format("Asset %s from request id=%d was spawned!",asset.spawngroupname,request.uid)
self:T(self.lid..text)
asset.spawned=true
asset.spawngroupname=group:GetName()
self:_DeleteStockItem(asset)
if asset.iscargo==true then
request.cargogroupset=request.cargogroupset or SET_GROUP:New()
request.cargogroupset:AddGroup(group)
else
request.transportgroupset=request.transportgroupset or SET_GROUP:New()
request.transportgroupset:AddGroup(group)
end
group:SetState(group,"WAREHOUSE",self)
local n=0
for _,_asset in pairs(request.assets)do
local assetitem=_asset
self:T(self.lid..string.format("Asset %s spawned %s as %s",assetitem.templatename,tostring(assetitem.spawned),tostring(assetitem.spawngroupname)))
if assetitem.spawned then
n=n+1
else
end
end
if n==request.nasset+request.ntransport then
self:T(self.lid..string.format("All assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned. Calling RequestSpawned",n,request.nasset,request.ntransport,request.uid))
self:RequestSpawned(request,request.cargogroupset,request.transportgroupset)
else
self:T(self.lid..string.format("Not all assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned YET",n,request.nasset,request.ntransport,request.uid))
end
end
function WAREHOUSE:onafterAssetDead(From,Event,To,asset,request)
if asset and request then
local text=string.format("Asset %s from request id=%d is dead!",asset.templatename,request.uid)
self:T(self.lid..text)
local groupname=asset.spawngroupname
local NoTriggerEvent=true
if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
request.cargogroupset:Remove(groupname,NoTriggerEvent)
self:T(self.lid..string.format("Removed selfpropelled cargo %s: ncargo=%d.",groupname,request.cargogroupset:Count()))
else
local istransport=not asset.iscargo
if istransport==true then
request.transportgroupset:Remove(groupname,NoTriggerEvent)
self:T(self.lid..string.format("Removed transport %s: ntransport=%d",groupname,request.transportgroupset:Count()))
elseif istransport==false then
request.cargogroupset:Remove(groupname,NoTriggerEvent)
self:T(self.lid..string.format("Removed transported cargo %s outside carrier: ncargo=%d",groupname,request.cargogroupset:Count()))
else
end
end
else
self:E(self.lid.."ERROR: Asset and/or Request is nil in onafterAssetDead")
end
end
function WAREHOUSE:onafterDestroyed(From,Event,To)
local text=string.format("Warehouse %s was destroyed! Assets lost %d. Respawn=%s",self.alias,#self.stock,tostring(self.respawnafterdestroyed))
self:_InfoMessage(text)
if self.respawnafterdestroyed then
if self.respawndelay then
self:Pause()
self:__Respawn(self.respawndelay)
else
self:Respawn()
end
else
for k,_ in pairs(self.queue)do
self.queue[k]=nil
end
for k,_ in pairs(self.stock)do
end
for k=#self.stock,1,-1 do
self.stock[k]=nil
end
end
end
function WAREHOUSE:onafterSave(From,Event,To,path,filename)
local function _savefile(filename,data)
local f=assert(io.open(filename,"wb"))
f:write(data)
f:close()
end
filename=filename or string.format("WAREHOUSE-%d_%s.txt",self.uid,self.alias)
if path~=nil then
filename=path.."\\"..filename
end
local text=string.format("Saving warehouse assets to file %s",filename)
MESSAGE:New(text,30):ToAllIf(self.Debug or self.Report)
self:I(self.lid..text)
local warehouseassets=""
warehouseassets=warehouseassets..string.format("coalition=%d\n",self:GetCoalition())
warehouseassets=warehouseassets..string.format("country=%d\n",self:GetCountry())
for _,_asset in pairs(self.stock)do
local asset=_asset
local assetstring=""
for key,value in pairs(asset)do
if key=="templatename"or key=="attribute"or key=="cargobay"or key=="weight"or key=="loadradius"or key=="livery"or key=="skill"or key=="assignment"then
local name
if type(value)=="table"then
name=string.format("%s=%s;",key,value[1])
else
name=string.format("%s=%s;",key,value)
end
assetstring=assetstring..name
end
self:I(string.format("Loaded asset: %s",assetstring))
end
warehouseassets=warehouseassets..assetstring.."\n"
end
_savefile(filename,warehouseassets)
end
function WAREHOUSE:onbeforeLoad(From,Event,To,path,filename)
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
filename=filename or string.format("WAREHOUSE-%d_%s.txt",self.uid,self.alias)
if path~=nil then
filename=path.."\\"..filename
end
local exists=_fileexists(filename)
if exists then
return true
else
self:_ErrorMessage(string.format("ERROR: file %s does not exist! Cannot load assets.",filename),60)
return false
end
end
function WAREHOUSE:onafterLoad(From,Event,To,path,filename)
local function _loadfile(filename)
local f=assert(io.open(filename,"rb"))
local data=f:read("*all")
f:close()
return data
end
filename=filename or string.format("WAREHOUSE-%d_%s.txt",self.uid,self.alias)
if path~=nil then
filename=path.."\\"..filename
end
local text=string.format("Loading warehouse assets from file %s",filename)
MESSAGE:New(text,30):ToAllIf(self.Debug or self.Report)
self:I(self.lid..text)
local data=_loadfile(filename)
local assetdata=UTILS.Split(data,"\n")
local Coalition
local Country
local assets={}
for _,asset in pairs(assetdata)do
local descriptors=UTILS.Split(asset,";")
local asset={}
local isasset=false
for _,descriptor in pairs(descriptors)do
local keyval=UTILS.Split(descriptor,"=")
if#keyval==2 then
if keyval[1]=="coalition"then
Coalition=tonumber(keyval[2])
elseif keyval[1]=="country"then
Country=tonumber(keyval[2])
else
isasset=true
local key=keyval[1]
local val=keyval[2]
if val=="nil"then
val=nil
end
if key=="cargobay"or key=="weight"or key=="loadradius"then
asset[key]=tonumber(val)
else
asset[key]=val
end
end
end
end
if isasset then
table.insert(assets,asset)
end
end
if Country~=self:GetCountry()then
self:T(self.lid..string.format("Changing warehouse country %d-->%d on loading assets.",self:GetCountry(),Country))
self:ChangeCountry(Country)
end
for _,_asset in pairs(assets)do
local asset=_asset
local group=GROUP:FindByName(asset.templatename)
if group then
self:AddAsset(group,1,asset.attribute,asset.cargobay,asset.weight,asset.loadradius,asset.skill,asset.livery,asset.assignment)
else
self:E(string.format("ERROR: Group %s doest not exit. Cannot be loaded as asset.",tostring(asset.templatename)))
end
end
end
function WAREHOUSE:_SpawnAssetRequest(Request)
self:F2({requestUID=Request.uid})
local cargoassets=Request.cargoassets
local Parking={}
if Request.cargocategory==Group.Category.AIRPLANE or Request.cargocategory==Group.Category.HELICOPTER then
Parking=self:_FindParkingForAssets(self.airbase,cargoassets)or{}
end
local UnControlled=true
for i=1,#cargoassets do
local asset=cargoassets[i]
asset.spawned=false
asset.iscargo=true
asset.rid=Request.uid
local _alias=asset.spawngroupname
Request.assets[asset.uid]=asset
local _group=nil
if asset.category==Group.Category.GROUND then
_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.spawnzone,Request.lateActivation)
elseif asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then
if Parking[asset.uid]then
_group=self:_SpawnAssetAircraft(_alias,asset,Request,Parking[asset.uid],UnControlled,Request.lateActivation)
else
_group=self:_SpawnAssetAircraft(_alias,asset,Request,nil,UnControlled,Request.lateActivation)
end
elseif asset.category==Group.Category.TRAIN then
if self.rail then
_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.spawnzone,Request.lateActivation)
end
elseif asset.category==Group.Category.SHIP then
_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.portzone,Request.lateActivation)
else
self:E(self.lid.."ERROR: Unknown asset category!")
end
if _group then
self:__AssetSpawned(0.01,_group,asset,Request)
end
end
end
function WAREHOUSE:_SpawnAssetGroundNaval(alias,asset,request,spawnzone,lateactivated)
if asset and(asset.category==Group.Category.GROUND or asset.category==Group.Category.SHIP or asset.category==Group.Category.TRAIN)then
local template=self:_SpawnAssetPrepareTemplate(asset,alias)
template.route.points[1]={}
local coord=spawnzone:GetRandomCoordinate()
if asset.category==Group.Category.TRAIN then
coord=self.rail
end
for i=1,#template.units do
local unit=template.units[i]
local SX=unit.x or 0
local SY=unit.y or 0
local BX=asset.template.route.points[1].x
local BY=asset.template.route.points[1].y
local TX=coord.x+(SX-BX)
local TY=coord.z+(SY-BY)
template.units[i].x=TX
template.units[i].y=TY
if asset.livery then
unit.livery_id=asset.livery
end
if asset.skill then
unit.skill=asset.skill
end
end
template.lateActivation=lateactivated
template.route.points[1].x=coord.x
template.route.points[1].y=coord.z
template.x=coord.x
template.y=coord.z
template.alt=coord.y
local group=_DATABASE:Spawn(template)
return group
end
return nil
end
function WAREHOUSE:_SpawnAssetAircraft(alias,asset,request,parking,uncontrolled,lateactivated)
if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then
local template=self:_SpawnAssetPrepareTemplate(asset,alias)
local _type=COORDINATE.WaypointType.TakeOffParking
local _action=COORDINATE.WaypointAction.FromParkingArea
if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then
_type=COORDINATE.WaypointType.TakeOffParkingHot
_action=COORDINATE.WaypointAction.FromParkingAreaHot
uncontrolled=false
end
local airstart=asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TurningPoint or false
if airstart then
_type=COORDINATE.WaypointType.TurningPoint
_action=COORDINATE.WaypointAction.TurningPoint
uncontrolled=false
end
if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
if request.toself then
local coord=self.airbase:GetCoordinate()
if airstart then
coord:SetAltitude(math.random(1000,2000))
end
local wp=coord:WaypointAir("RADIO",_type,_action,0,false,self.airbase,{},"Parking")
template.route.points={wp}
else
template.route.points=self:_GetFlightplan(asset,self.airbase,request.warehouse.airbase)
end
else
template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO",_type,_action,0,true,self.airbase,nil,"Spawnpoint")
end
local AirbaseID=self.airbase:GetID()
local AirbaseCategory=self:GetAirbaseCategory()
if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then
else
if#parking<#template.units and not airstart then
local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.",#parking,#template.units)
self:_DebugMessage(text)
return nil
end
end
for i=1,#template.units do
local unit=template.units[i]
if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then
local coord=self.airbase:GetCoordinate()
unit.x=coord.x
unit.y=coord.z
unit.alt=coord.y
if airstart then
unit.alt=math.random(1000,2000)
end
unit.parking_id=nil
unit.parking=nil
else
local coord=nil
local terminal=nil
if airstart then
coord=self.airbase:GetCoordinate():SetAltitude(math.random(1000,2000))
else
coord=parking[i].Coordinate
terminal=parking[i].TerminalID
end
if self.Debug then
local text=string.format("Spawnplace unit %s terminal %d.",unit.name,terminal)
coord:MarkToAll(text)
env.info(text)
end
unit.x=coord.x
unit.y=coord.z
unit.alt=coord.y
unit.parking_id=nil
unit.parking=terminal
end
if asset.livery then
unit.livery_id=asset.livery
end
if asset.skill then
unit.skill=asset.skill
end
if asset.payload then
unit.payload=asset.payload.pylons
end
if asset.modex then
unit.onboard_num=asset.modex[i]
end
if asset.callsign then
unit.callsign=asset.callsign[i]
end
end
template.x=template.units[1].x
template.y=template.units[1].y
template.uncontrolled=uncontrolled
self:T2({airtemplate=template})
local group=_DATABASE:Spawn(template)
return group
end
return nil
end
function WAREHOUSE:_SpawnAssetPrepareTemplate(asset,alias)
local template=UTILS.DeepCopy(asset.template)
template.name=alias
template.CoalitionID=self:GetCoalition()
template.CountryID=self:GetCountry()
template.groupId=nil
template.lateActivation=false
if asset.missionTask then
self:T(self.lid..string.format("Setting mission task to %s",tostring(asset.missionTask)))
template.task=asset.missionTask
end
template.route={}
template.route.routeRelativeTOT=true
template.route.points={}
for i=1,#template.units do
local unit=template.units[i]
unit.unitId=nil
unit.name=string.format("%s-%02d",template.name,i)
end
return template
end
function WAREHOUSE:_RouteGround(group,request)
if group and group:IsAlive()then
local _speed=group:GetSpeedMax()*0.7
local Waypoints={}
local hasoffroad=self:HasConnectionOffRoad(request.warehouse,self.Debug)
if hasoffroad then
local remotename=request.warehouse.warehouse:GetName()
local path=self.offroadpaths[remotename][math.random(#self.offroadpaths[remotename])]
for i=1,#path do
local coord=path[i]
local Waypoint=coord:WaypointGround(_speed,"Off Road")
table.insert(Waypoints,Waypoint)
end
else
Waypoints=group:TaskGroundOnRoad(request.warehouse.road,_speed,"Off Road",false,self.road)
local FromWP=group:GetCoordinate():WaypointGround(_speed,"Off Road")
table.insert(Waypoints,1,FromWP)
end
for n,wp in ipairs(Waypoints)do
local tf=self:_SimpleTaskFunctionWP("warehouse:_PassingWaypoint",group,n,#Waypoints)
group:SetTaskWaypoint(wp,tf)
end
group:Route(Waypoints,1)
group:OptionROEReturnFire()
group:OptionAlarmStateGreen()
end
end
function WAREHOUSE:_RouteNaval(group,request)
if group and group:IsAlive()then
local _speed=group:GetSpeedMax()*0.8
local remotename=request.warehouse.warehouse:GetName()
local lane=self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])]
if lane then
local Waypoints={}
for i=1,#lane do
local coord=lane[i]
local Waypoint=coord:WaypointGround(_speed)
table.insert(Waypoints,Waypoint)
end
local TaskFunction=self:_SimpleTaskFunction("warehouse:_Arrived",group)
local Waypoint=Waypoints[#Waypoints]
group:SetTaskWaypoint(Waypoint,TaskFunction)
group:Route(Waypoints,1)
group:OptionROEReturnFire()
else
self:E(self.lid..string.format("ERROR: No shipping lane defined for Naval asset!"))
end
end
end
function WAREHOUSE:_RouteAir(aircraft)
if aircraft and aircraft:IsAlive()~=nil then
self:T2(self.lid..string.format("RouteAir aircraft group %s alive=%s",aircraft:GetName(),tostring(aircraft:IsAlive())))
if self.flightcontrol then
local fg=FLIGHTGROUP:New(aircraft)
fg:SetReadyForTakeoff(true)
else
aircraft:StartUncontrolled(math.random(60))
end
self:T2(self.lid..string.format("RouteAir aircraft group %s alive=%s (after start command)",aircraft:GetName(),tostring(aircraft:IsAlive())))
aircraft:OptionROEReturnFire()
aircraft:OptionROTPassiveDefense()
else
self:E(string.format("ERROR: aircraft %s cannot be routed since it does not exist or is not alive %s!",tostring(aircraft:GetName()),tostring(aircraft:IsAlive())))
end
end
function WAREHOUSE:_RouteTrain(Group,Coordinate,Speed)
if Group and Group:IsAlive()then
local _speed=Speed or Group:GetSpeedMax()*0.6
local Waypoints=Group:TaskGroundOnRailRoads(Coordinate,Speed)
local TaskFunction=self:_SimpleTaskFunction("warehouse:_Arrived",Group)
local Waypoint=Waypoints[#Waypoints]
Group:SetTaskWaypoint(Waypoint,TaskFunction)
Group:Route(Waypoints,1)
end
end
function WAREHOUSE:_Arrived(group)
self:_DebugMessage(string.format("Group %s arrived!",tostring(group:GetName())))
if group then
self:__Arrived(1,group)
end
end
function WAREHOUSE:_PassingWaypoint(group,n,N)
self:T(self.lid..string.format("Group %s passing waypoint %d of %d!",tostring(group:GetName()),n,N))
if n==N then
self:__Arrived(1,group)
end
end
function WAREHOUSE:GetAssetByID(id)
if id then
return _WAREHOUSEDB.Assets[id]
else
return nil
end
end
function WAREHOUSE:GetAssetByName(GroupName)
local name=self:_GetNameWithOut(GroupName)
local _,aid,_=self:_GetIDsFromGroup(GROUP:FindByName(name))
if aid then
return _WAREHOUSEDB.Assets[aid]
else
return nil
end
end
function WAREHOUSE:GetRequestByID(id)
if id then
for _,_request in pairs(self.queue)do
local request=_request
if request.uid==id then
return request,true
end
end
for _,_request in pairs(self.pending)do
local request=_request
if request.uid==id then
return request,false
end
end
end
return nil,nil
end
function WAREHOUSE:_OnEventBirth(EventData)
self:T3(self.lid..string.format("Warehouse %s (id=%s) captured event birth!",self.alias,self.uid))
if EventData and EventData.IniGroup then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid==self.uid then
local asset=self:GetAssetByID(aid)
local request=self:GetRequestByID(rid)
if asset and request then
self:T(self.lid..string.format("Warehouse %s captured event birth of request ID=%d, asset ID=%d, unit %s spawned=%s",self.alias,request.uid,asset.uid,EventData.IniUnitName,tostring(asset.spawned)))
request.born=true
else
self:E(self.lid..string.format("ERROR: Either asset AID=%s or request RID=%s are nil in event birth of unit %s",tostring(aid),tostring(rid),tostring(EventData.IniUnitName)))
end
else
end
end
end
function WAREHOUSE:_OnEventEngineStartup(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event engine startup!",self.alias))
if EventData and EventData.IniGroup then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid==self.uid then
self:T(self.lid..string.format("Warehouse %s captured event engine startup of its asset unit %s.",self.alias,EventData.IniUnitName))
end
end
end
function WAREHOUSE:_OnEventTakeOff(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event takeoff!",self.alias))
if EventData and EventData.IniGroup then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid==self.uid then
self:T(self.lid..string.format("Warehouse %s captured event takeoff of its asset unit %s.",self.alias,EventData.IniUnitName))
end
end
end
function WAREHOUSE:_OnEventLanding(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event landing!",self.alias))
if EventData and EventData.IniGroup then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid~=nil and wid==self.uid then
self:T(self.lid..string.format("Warehouse %s captured event landing of its asset unit %s.",self.alias,EventData.IniUnitName))
end
end
end
function WAREHOUSE:_OnEventEngineShutdown(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event engine shutdown!",self.alias))
if EventData and EventData.IniGroup then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid==self.uid then
self:T(self.lid..string.format("Warehouse %s captured event engine shutdown of its asset unit %s.",self.alias,EventData.IniUnitName))
end
end
end
function WAREHOUSE:_OnEventArrived(EventData)
if EventData and EventData.IniUnit then
local unit=EventData.IniUnit
if unit and unit:IsAlive()==true and unit:InAir()==false then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid~=nil and aid~=nil and rid~=nil then
if self.uid==wid then
local request=self:_GetRequestOfGroup(group,self.pending)
if request then
local istransport=self:_GroupIsTransport(group,request)
local closest=group:GetCoordinate():GetClosestAirbase()
local rightairbase=closest:GetName()==request.warehouse:GetAirbase():GetName()
if istransport==false and rightairbase then
local nunits=#group:GetUnits()
local dt=10*(nunits-1)+1
if self.verbosity>=1 then
local text=string.format("Air asset group %s from warehouse %s arrived at its destination. Trigger Arrived event in %d sec",group:GetName(),self.alias,dt)
self:_InfoMessage(text)
end
self:__Arrived(dt,group)
end
end
end
else
self:T3(string.format("Group that arrived did not belong to a warehouse. Warehouse ID=%s, Asset ID=%s, Request ID=%s.",tostring(wid),tostring(aid),tostring(rid)))
end
end
end
end
function WAREHOUSE:_OnEventCrashOrDead(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event dead or crash!",self.alias))
if EventData then
if EventData.IniUnitName then
local warehousename=self.warehouse:GetName()
if EventData.IniUnitName==warehousename then
self:_DebugMessage(string.format("Warehouse %s alias %s was destroyed!",warehousename,self.alias))
self:Destroyed()
end
if self.airbase and self.airbasename and self.airbasename==EventData.IniUnitName then
self:RunwayDestroyed()
end
end
self:T2(self.lid..string.format("Warehouse %s captured event dead or crash or unit %s",self.alias,tostring(EventData.IniUnitName)))
if EventData.IniGroup then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid==self.uid then
self:T(self.lid..string.format("Warehouse %s captured event dead or crash of its asset unit %s",self.alias,EventData.IniUnitName))
for _,request in pairs(self.pending)do
local request=request
if request.uid==rid then
self:_UnitDead(EventData.IniUnit,EventData.IniGroup,request)
end
end
end
end
end
end
function WAREHOUSE:_UnitDead(deadunit,deadgroup,request)
self:F(self.lid.."FF unit dead "..deadunit:GetName())
local opsgroup=_DATABASE:FindOpsGroup(deadgroup)
if opsgroup then
return nil
end
local nalive=deadgroup:CountAliveUnits()
local groupdead=false
if nalive>0 then
groupdead=false
else
groupdead=true
end
local asset=self:FindAssetInDB(deadgroup)
local unitname=self:_GetNameWithOut(deadunit)
local groupname=self:_GetNameWithOut(deadgroup)
if groupdead then
self:T(self.lid..string.format("Group %s (transport=%s) is dead!",groupname,tostring(self:_GroupIsTransport(deadgroup,request))))
if self.Debug then
deadgroup:SmokeWhite()
end
self:AssetDead(asset,request)
end
local NoTriggerEvent=true
if not request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
if not asset.iscargo then
local cargogroupnames=request.carriercargo[unitname]
if cargogroupnames then
for _,cargoname in pairs(cargogroupnames)do
request.cargogroupset:Remove(cargoname,NoTriggerEvent)
self:T(self.lid..string.format("Removed transported cargo %s inside dead carrier %s: ncargo=%d",cargoname,unitname,request.cargogroupset:Count()))
end
end
else
self:E(self.lid..string.format("ERROR: Group %s is neither cargo nor transport!",deadgroup:GetName()))
end
end
end
function WAREHOUSE:_OnEventBaseCaptured(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event base captured!",self.alias))
if self.airbasename==nil then
return
end
if EventData and EventData.Place then
local airbase=EventData.Place
if EventData.PlaceName==self.airbasename then
local NewCoalitionAirbase=airbase:GetCoalition()
self:T(self.lid..string.format("Airbase of warehouse %s (coalition ID=%d) was captured! New owner coalition ID=%d.",self.alias,self:GetCoalition(),NewCoalitionAirbase))
if self.airbase==nil then
if NewCoalitionAirbase==self:GetCoalition()then
self:AirbaseRecaptured(NewCoalitionAirbase)
end
else
if NewCoalitionAirbase~=self:GetCoalition()then
self:AirbaseCaptured(NewCoalitionAirbase)
end
end
end
end
end
function WAREHOUSE:_OnEventMissionEnd(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event mission end!",self.alias))
if self.autosave then
self:Save(self.autosavepath,self.autosavefile)
end
end
function WAREHOUSE:_CheckConquered()
local coord=self.zone:GetCoordinate()
local radius=self.zone:GetRadius()
local gotunits,_,_,units,_,_=coord:ScanObjects(radius,true,false,false)
local Nblue=0
local Nred=0
local Nneutral=0
local CountryBlue=nil
local CountryRed=nil
local CountryNeutral=nil
if gotunits then
for _,_unit in pairs(units)do
local unit=_unit
local distance=coord:Get2DDistance(unit:GetCoordinate())
if unit:IsGround()and unit:IsAlive()and distance<=radius then
local _coalition=unit:GetCoalition()
local _country=unit:GetCountry()
self:T2(self.lid..string.format("Unit %s in warehouse zone of radius=%d m. Coalition=%d, country=%d. Distance = %d m.",unit:GetName(),radius,_coalition,_country,distance))
if _coalition==coalition.side.BLUE then
Nblue=Nblue+1
CountryBlue=_country
elseif _coalition==coalition.side.RED then
Nred=Nred+1
CountryRed=_country
else
Nneutral=Nneutral+1
CountryNeutral=_country
end
end
end
end
self:T(self.lid..string.format("Ground troops in warehouse zone: blue=%d, red=%d, neutral=%d",Nblue,Nred,Nneutral))
local newcoalition=self:GetCoalition()
local newcountry=self:GetCountry()
if Nblue>0 and Nred==0 and Nneutral==0 then
newcoalition=coalition.side.BLUE
newcountry=CountryBlue
elseif Nblue==0 and Nred>0 and Nneutral==0 then
newcoalition=coalition.side.RED
newcountry=CountryRed
elseif Nblue==0 and Nred==0 and Nneutral>0 then
end
if self:IsAttacked()and newcoalition~=self:GetCoalition()then
self:Captured(newcoalition,newcountry)
return
end
if self:GetCoalition()==coalition.side.BLUE then
if self:IsRunning()and Nred>0 then
self:Attacked(coalition.side.RED,CountryRed)
end
if self:IsAttacked()and Nred==0 then
self:Defeated()
end
elseif self:GetCoalition()==coalition.side.RED then
if self:IsRunning()and Nblue>0 then
self:Attacked(coalition.side.BLUE,CountryBlue)
end
if self:IsAttacked()and Nblue==0 then
self:Defeated()
end
elseif self:GetCoalition()==coalition.side.NEUTRAL then
if self:IsRunning()and Nred>0 then
self:Attacked(coalition.side.RED,CountryRed)
elseif self:IsRunning()and Nblue>0 then
self:Attacked(coalition.side.BLUE,CountryBlue)
end
end
end
function WAREHOUSE:_CheckAirbaseOwner()
if self.airbasename then
local airbase=AIRBASE:FindByName(self.airbasename)
local airbasecurrentcoalition=airbase:GetCoalition()
if self.airbase then
if self:GetCoalition()~=airbasecurrentcoalition then
self.airbase=nil
end
else
if self:GetCoalition()==airbasecurrentcoalition then
self.airbase=airbase
end
end
end
end
function WAREHOUSE:_CheckRequestConsistancy(queue)
self:T3(self.lid..string.format("Number of queued requests = %d",#queue))
local invalid={}
for _,_request in pairs(queue)do
local request=_request
self:T2(self.lid..string.format("Checking request id=%d.",request.uid))
local valid=true
if request.nasset==0 then
self:E(self.lid..string.format("ERROR: INVALID request. Request for zero assets not possible. Can happen when, e.g. \"all\" ground assets are requests but none in stock."))
valid=false
end
if self:GetCoalition()~=request.warehouse:GetCoalition()then
self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is of wrong coalition! Own coalition %s != %s of requesting warehouse.",self:GetCoalitionName(),request.warehouse:GetCoalitionName()))
valid=false
end
if request.warehouse:IsStopped()then
self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is stopped!"))
valid=false
end
if request.warehouse:IsDestroyed()and not self.respawnafterdestroyed then
self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is destroyed!"))
valid=false
end
if valid==false then
self:E(self.lid..string.format("Got invalid request id=%d.",request.uid))
table.insert(invalid,request)
else
self:T3(self.lid..string.format("Got valid request id=%d.",request.uid))
end
end
for _,_request in pairs(invalid)do
self:E(self.lid..string.format("Deleting INVALID request id=%d.",_request.uid))
self:_DeleteQueueItem(_request,self.queue)
end
end
function WAREHOUSE:_CheckRequestValid(request)
local _assets,_nassets,_enough=self:_FilterStock(self.stock,request.assetdesc,request.assetdescval,request.nasset)
if#_assets==0 then
return true
end
local nasset=request.nasset
if type(request.nasset)=="string"then
nasset=self:_QuantityRel2Abs(request.nasset,_nassets)
end
local text=string.format("Request valid? Number of assets: requested=%s=%d, selected=%d, total=%d, enough=%s.",tostring(request.nasset),nasset,#_assets,_nassets,tostring(_enough))
self:T(text)
local asset=_assets[1]
local asset_plane=asset.category==Group.Category.AIRPLANE
local asset_helo=asset.category==Group.Category.HELICOPTER
local asset_ground=asset.category==Group.Category.GROUND
local asset_train=asset.category==Group.Category.TRAIN
local asset_naval=asset.category==Group.Category.SHIP
local asset_air=asset_helo or asset_plane
local valid=true
local requestcategory=request.warehouse:GetAirbaseCategory()
if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
if asset_air then
if asset_plane then
if requestcategory==Airbase.Category.HELIPAD or self:GetAirbaseCategory()==Airbase.Category.HELIPAD then
self:E("ERROR: Incorrect request. Asset airplane requested but warehouse or requestor is HELIPAD/FARP!")
valid=false
end
elseif asset_helo then
if self:GetAirbaseCategory()==-1 or requestcategory==-1 then
self:E("ERROR: Incorrect request. Helos need a AIRBASE/HELIPAD/SHIP as home/destination base!")
valid=false
end
end
if self.airbase==nil or request.airbase==nil then
self:E("ERROR: Incorrect request. Either warehouse or requesting warehouse does not have any kind of airbase!")
valid=false
else
local termtype_dep=asset.terminalType or self:_GetTerminal(asset.attribute,self:GetAirbaseCategory())
local termtype_des=asset.terminalType or self:_GetTerminal(asset.attribute,request.warehouse:GetAirbaseCategory())
local np_departure=self.airbase:GetParkingSpotsNumber(termtype_dep)
local np_destination=request.airbase:GetParkingSpotsNumber(termtype_des)
self:T(string.format("Asset attribute = %s, DEPARTURE: terminal type = %d, spots = %d, DESTINATION: terminal type = %d, spots = %d",asset.attribute,termtype_dep,np_departure,termtype_des,np_destination))
if np_departure<nasset then
self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots %d < %d necessary.",termtype_dep,np_departure,nasset))
valid=false
end
if np_destination==0 then
self:E(string.format("ERROR: Incorrect request. No parking spots of terminal type %d at requesting warehouse. Available spots = %d!",termtype_des,np_destination))
valid=false
end
end
elseif asset_ground then
local inwater=self.spawnzone:GetCoordinate():IsSurfaceTypeWater()or request.warehouse.spawnzone:GetCoordinate():IsSurfaceTypeWater()
if inwater and not request.lateActivation then
self:E("ERROR: Incorrect request. Ground asset requested but at least one spawn zone is in water!")
return false
end
if asset_train then
local hasrail=self:HasConnectionRail(request.warehouse)
if not hasrail then
self:E("ERROR: Incorrect request. No valid path on rail for train assets!")
valid=false
end
else
if self.warehouse:GetName()~=request.warehouse.warehouse:GetName()then
local hasroad=self:HasConnectionRoad(request.warehouse)
local hasoffroad=self:HasConnectionOffRoad(request.warehouse)
if not(hasroad or hasoffroad)then
self:E("ERROR: Incorrect request. No valid path on or off road for ground assets!")
valid=false
end
end
end
elseif asset_naval then
local shippinglane=self:HasConnectionNaval(request.warehouse)
if not shippinglane then
self:E("ERROR: Incorrect request. No shipping lane has been defined between warehouses!")
valid=false
end
end
else
if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME or requestcategory~=Airbase.Category.AIRDROME then
self:E("ERROR: Incorrect request. Warehouse or requestor does not have an airdrome. No transport by plane possible!")
valid=false
end
elseif request.transporttype==WAREHOUSE.TransportType.APC then
if self:GetAirbaseCategory()==Airbase.Category.SHIP or requestcategory==Airbase.Category.SHIP then
self:E("ERROR: Incorrect request. Warehouse or requestor is SHIP. No transport by APC possible!")
valid=false
end
local hasroad=self:HasConnectionRoad(request.warehouse)
if not hasroad then
self:E("ERROR: Incorrect request. No valid path on road for ground transport assets!")
valid=false
end
elseif request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
if self:GetAirbaseCategory()==-1 then
self:E("ERROR: Incorrect request. Warehouse has no airbase. Transport by helicopter not possible!")
valid=false
end
elseif request.transporttype==WAREHOUSE.TransportType.SHIP or request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or request.transporttype==WAREHOUSE.TransportType.WARSHIP then
local shippinglane=self:HasConnectionNaval(request.warehouse)
if not shippinglane then
self:E("ERROR: Incorrect request. No shipping lane has been defined between warehouses!")
valid=false
end
elseif request.transporttype==WAREHOUSE.TransportType.TRAIN then
self:E("ERROR: Incorrect request. Transport by TRAIN not implemented yet!")
valid=false
else
self:E("ERROR: Incorrect request. Transport type unknown!")
valid=false
end
if request.transporttype==WAREHOUSE.TransportType.AIRPLANE or request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
local _assets,_nassets,_enough=self:_FilterStock(self.stock,WAREHOUSE.Descriptor.ATTRIBUTE,request.transporttype,request.ntransport,true)
local nasset=request.ntransport
if type(request.ntransport)=="string"then
nasset=self:_QuantityRel2Abs(request.ntransport,_nassets)
end
local text=string.format("Request valid? Number of transports: requested=%s=%d, selected=%d, total=%d, enough=%s.",tostring(request.ntransport),nasset,#_assets,_nassets,tostring(_enough))
self:T(text)
local termtype=self:_GetTerminal(request.transporttype,self:GetAirbaseCategory())
local np_departure=self.airbase:GetParkingSpotsNumber(termtype)
self:T(self.lid..string.format("Transport attribute = %s, terminal type = %d, spots at departure = %d.",request.transporttype,termtype,np_departure))
if np_departure<nasset then
self:E(self.lid..string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots %d < %d necessary.",termtype,np_departure,nasset))
valid=false
end
if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
termtype=self:_GetTerminal(request.transporttype,request.warehouse:GetAirbaseCategory())
local np_destination=request.airbase:GetParkingSpotsNumber(termtype)
self:T(self.lid..string.format("Transport attribute = %s: total # of spots (type=%d) at destination = %d.",asset.attribute,termtype,np_destination))
if np_destination==0 then
self:E(string.format("ERROR: Incorrect request. No parking spots of terminal type %d at requesting warehouse for transports. Available spots = %d!",termtype,np_destination))
valid=false
end
end
end
end
if valid==false then
self:E(self.lid..string.format("ERROR: Got invalid request id=%d.",request.uid))
else
self:T3(self.lid..string.format("Request id=%d valid :)",request.uid))
end
return valid
end
function WAREHOUSE:_CheckRequestNow(request)
if(request.warehouse:IsRunning()==false)and not(request.toself and self:IsAttacked())then
local text=string.format("Warehouse %s: Request denied! Receiving warehouse %s is not running. Current state %s.",self.alias,request.warehouse.alias,request.warehouse:GetState())
self:_InfoMessage(text,5)
return false
end
local onlymobile=false
if type(request.ntransport)=="number"and request.ntransport==0 and not request.toself then
onlymobile=true
end
local _assets,_nassets,_enough=self:_FilterStock(self.stock,request.assetdesc,request.assetdescval,request.nasset,onlymobile)
if not _enough then
local text=string.format("Warehouse %s: Request ID=%d denied! Not enough (cargo) assets currently available.",self.alias,request.uid)
self:_InfoMessage(text,5)
text=string.format("Enough=%s, #assets=%d, nassets=%d, request.nasset=%s",tostring(_enough),#_assets,_nassets,tostring(request.nasset))
self:T(self.lid..text)
return false
end
local _transports
local _assetattribute
local _assetcategory
local _assetairstart=false
if _nassets>0 then
_assetattribute=_assets[1].attribute
_assetcategory=_assets[1].category
_assetairstart=_assets[1].takeoffType and _assets[1].takeoffType==COORDINATE.WaypointType.TurningPoint or false
if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
if self.airbase and self.airbase:GetCoalition()==self:GetCoalition()then
if self:IsRunwayOperational()or _assetairstart then
if _assetairstart then
else
local Parking=self:_FindParkingForAssets(self.airbase,_assets)
if Parking==nil then
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.",self.alias)
self:_InfoMessage(text,5)
return false
end
end
else
local text=string.format("Warehouse %s: Request denied! Runway is still destroyed",self.alias)
self:_InfoMessage(text,5)
return false
end
else
local text=string.format("Warehouse %s: Request denied! No airbase",self.alias)
self:_InfoMessage(text,5)
return false
end
end
request.cargoassets=_assets
end
if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then
_transports=self:_GetTransportsForAssets(request)
if#_transports>0 then
local _transportattribute=_transports[1].attribute
local _transportcategory=_transports[1].category
if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then
if self.airbase and self.airbase:GetCoalition()==self:GetCoalition()then
if self:IsRunwayOperational()then
local Parking=self:_FindParkingForAssets(self.airbase,_transports)
if Parking==nil then
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all transports at the moment.",self.alias)
self:_InfoMessage(text,5)
return false
end
else
local text=string.format("Warehouse %s: Request denied! Runway is still destroyed",self.alias)
self:_InfoMessage(text,5)
return false
end
else
local text=string.format("Warehouse %s: Request denied! No airbase currently!",self.alias)
self:_InfoMessage(text,5)
return false
end
end
else
local text=string.format("Warehouse %s: Request denied! Not enough transport carriers available at the moment.",self.alias)
self:_InfoMessage(text,5)
return false
end
else
if _assetcategory==Group.Category.GROUND then
local dist=self.warehouse:GetCoordinate():Get2DDistance(self.spawnzone:GetCoordinate())
if dist>self.spawnzonemaxdist then
local text=string.format("Warehouse %s: Request denied! Not close enough to spawn zone. Distance = %d m. We need to be at least within %d m range to spawn.",self.alias,dist,self.spawnzonemaxdist)
self:_InfoMessage(text,5)
return false
end
end
end
request.cargoassets=_assets
request.cargoattribute=_assets[1].attribute
request.cargocategory=_assets[1].category
request.nasset=#_assets
local text=string.format("Selected cargo assets, attibute=%s, category=%d:\n",request.cargoattribute,request.cargocategory)
for _i,_asset in pairs(_assets)do
local asset=_asset
text=text..string.format("%d) name=%s, type=%s, category=%d, #units=%d",_i,asset.templatename,asset.unittype,asset.category,asset.nunits)
end
self:T(self.lid..text)
if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then
request.transportassets=_transports
request.transportattribute=_transports[1].attribute
request.transportcategory=_transports[1].category
request.ntransport=#_transports
local text=string.format("Selected transport assets, attibute=%s, category=%d:\n",request.transportattribute,request.transportcategory)
for _i,_asset in pairs(_transports)do
local asset=_asset
text=text..string.format("%d) name=%s, type=%s, category=%d, #units=%d\n",_i,asset.templatename,asset.unittype,asset.category,asset.nunits)
end
self:T(self.lid..text)
end
return true
end
function WAREHOUSE:_GetTransportsForAssets(request)
local transports=self:_FilterStock(self.stock,WAREHOUSE.Descriptor.ATTRIBUTE,request.transporttype,nil,true)
local cargoassets=UTILS.DeepCopy(request.cargoassets)
local cargoset=request.transportcargoset
local function sort_transports(a,b)
return a.cargobaymax>b.cargobaymax
end
local function sort_cargoassets(a,b)
return a.weight>b.weight
end
table.sort(transports,sort_transports)
table.sort(cargoassets,sort_cargoassets)
self:T2(self.lid.."Transport capability:")
local totalbay=0
for i=1,#transports do
local transport=transports[i]
for j=1,transport.nunits do
totalbay=totalbay+transport.cargobay[j]
self:T2(self.lid..string.format("Cargo bay = %d  (unit=%d)",transport.cargobay[j],j))
end
end
self:T2(self.lid..string.format("Total capacity = %d",totalbay))
self:T2(self.lid.."Cargo weight:")
local totalcargoweight=0
for i=1,#cargoassets do
local asset=cargoassets[i]
totalcargoweight=totalcargoweight+asset.weight
self:T2(self.lid..string.format("weight = %d",asset.weight))
end
self:T2(self.lid..string.format("Total weight = %d",totalcargoweight))
local used_transports={}
for i=1,#transports do
local transport=transports[i]
local putintocarrier={}
local used=false
for k=1,transport.nunits do
local cargobay=transport.cargobay[k]
for j,asset in pairs(cargoassets)do
local asset=asset
local delta=cargobay-asset.weight
if delta>=0 then
cargobay=cargobay-asset.weight
self:T3(self.lid..string.format("%s unit %d loads cargo uid=%d: bayempty=%02d, bayloaded = %02d - weight=%02d",transport.templatename,k,asset.uid,transport.cargobay[k],cargobay,asset.weight))
table.insert(putintocarrier,j)
used=true
else
self:T2(self.lid..string.format("Carrier unit %s too small for cargo asset %s ==> cannot be used! Cargo bay - asset weight = %d kg",transport.templatename,asset.templatename,delta))
end
end
end
for j=#putintocarrier,1,-1 do
local nput=putintocarrier[j]
local cargo=cargoassets[nput]
if cargo then
self:T2(self.lid..string.format("Cargo id=%d assigned for carrier id=%d",cargo.uid,transport.uid))
table.remove(cargoassets,nput)
end
end
if used then
table.insert(used_transports,transport)
end
local ntrans=self:_QuantityRel2Abs(request.ntransport,#transports)
if#used_transports>=ntrans then
request.ntransport=#used_transports
break
end
end
local text=string.format("Used Transports for request %d to warehouse %s:\n",request.uid,request.warehouse.alias)
local totalcargobay=0
for _i,_transport in pairs(used_transports)do
local transport=_transport
text=text..string.format("%d) %s: cargobay tot = %d kg, cargobay max = %d kg, nunits=%d\n",_i,transport.unittype,transport.cargobaytot,transport.cargobaymax,transport.nunits)
totalcargobay=totalcargobay+transport.cargobaytot
end
text=text..string.format("Total cargo bay capacity = %.1f kg\n",totalcargobay)
text=text..string.format("Total cargo weight       = %.1f kg\n",totalcargoweight)
text=text..string.format("Minimum number of runs   = %.1f",totalcargoweight/totalcargobay)
self:_DebugMessage(text)
return used_transports
end
function WAREHOUSE:_QuantityRel2Abs(relative,ntot)
local nabs=0
if type(relative)=="string"then
if relative==WAREHOUSE.Quantity.ALL then
nabs=ntot
elseif relative==WAREHOUSE.Quantity.THREEQUARTERS then
nabs=UTILS.Round(ntot*3/4)
elseif relative==WAREHOUSE.Quantity.HALF then
nabs=UTILS.Round(ntot/2)
elseif relative==WAREHOUSE.Quantity.THIRD then
nabs=UTILS.Round(ntot/3)
elseif relative==WAREHOUSE.Quantity.QUARTER then
nabs=UTILS.Round(ntot/4)
else
nabs=math.min(1,ntot)
end
else
nabs=relative
end
self:T2(self.lid..string.format("Relative %s: tot=%d, abs=%.2f",tostring(relative),ntot,nabs))
return nabs
end
function WAREHOUSE:_CheckQueue()
self:_SortQueue()
local request=nil
local invalid={}
local gotit=false
for _,_qitem in ipairs(self.queue)do
local qitem=_qitem
local valid=self:_CheckRequestValid(qitem)
local okay=false
if valid then
okay=self:_CheckRequestNow(qitem)
else
table.insert(invalid,qitem)
end
if okay and valid and not gotit then
request=qitem
gotit=true
break
end
end
for _,_request in pairs(invalid)do
self:T(self.lid..string.format("Deleting invalid request id=%d.",_request.uid))
self:_DeleteQueueItem(_request,self.queue)
end
return request
end
function WAREHOUSE:_SimpleTaskFunction(Function,group)
self:F2({Function})
local warehouse=self.warehouse:GetName()
local groupname=group:GetName()
local DCSScript={}
DCSScript[#DCSScript+1]=string.format('local mygroup     = GROUP:FindByName(\"%s\") ',groupname)
if self.isUnit then
DCSScript[#DCSScript+1]=string.format("local mywarehouse = UNIT:FindByName(\"%s\") ",warehouse)
else
DCSScript[#DCSScript+1]=string.format("local mywarehouse = STATIC:FindByName(\"%s\") ",warehouse)
end
DCSScript[#DCSScript+1]=string.format('local warehouse   = mywarehouse:GetState(mywarehouse, \"WAREHOUSE\") ')
DCSScript[#DCSScript+1]=string.format('%s(mygroup)',Function)
local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript)))
return DCSTask
end
function WAREHOUSE:_SimpleTaskFunctionWP(Function,group,n,N)
self:F2({Function})
local warehouse=self.warehouse:GetName()
local groupname=group:GetName()
local DCSScript={}
DCSScript[#DCSScript+1]=string.format('local mygroup     = GROUP:FindByName(\"%s\") ',groupname)
if self.isUnit then
DCSScript[#DCSScript+1]=string.format("local mywarehouse = UNIT:FindByName(\"%s\") ",warehouse)
else
DCSScript[#DCSScript+1]=string.format("local mywarehouse = STATIC:FindByName(\"%s\") ",warehouse)
end
DCSScript[#DCSScript+1]=string.format('local warehouse   = mywarehouse:GetState(mywarehouse, \"WAREHOUSE\") ')
DCSScript[#DCSScript+1]=string.format('%s(mygroup, %d, %d)',Function,n,N)
local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript)))
return DCSTask
end
function WAREHOUSE:_GetTerminal(_attribute,_category)
local _terminal=AIRBASE.TerminalType.OpenBig
if _attribute==WAREHOUSE.Attribute.AIR_FIGHTER then
_terminal=AIRBASE.TerminalType.FighterAircraft
elseif _attribute==WAREHOUSE.Attribute.AIR_BOMBER or _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTPLANE or _attribute==WAREHOUSE.Attribute.AIR_TANKER or _attribute==WAREHOUSE.Attribute.AIR_AWACS then
_terminal=AIRBASE.TerminalType.OpenBig
elseif _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO then
_terminal=AIRBASE.TerminalType.HelicopterUsable
else
end
if _category==Airbase.Category.SHIP then
if not(_attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO)then
_terminal=AIRBASE.TerminalType.OpenMedOrBig
end
end
return _terminal
end
function WAREHOUSE:_FindParkingForAssets(airbase,assets)
local scanradius=25
local scanunits=true
local scanstatics=true
local scanscenery=false
local verysafe=false
local function _overlap(l1,l2,dist)
local safedist=(l1/2+l2/2)*1.05
local safe=(dist>safedist)
self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s",l1,l2,safedist,dist,tostring(safe)))
return safe
end
local function _clients()
local coords={}
if not self.allowSpawnOnClientSpots then
local clients=_DATABASE.CLIENTS
for clientname,client in pairs(clients)do
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
local units=template.units
for i,unit in pairs(units)do
local coord=COORDINATE:New(unit.x,unit.alt,unit.y)
coords[unit.name]=coord
end
end
end
return coords
end
local parkingdata=airbase.parking
local obstacles={}
self.clientcoords=self.clientcoords or _clients()
for clientname,_coord in pairs(self.clientcoords)do
table.insert(obstacles,{coord=_coord,size=15,name=clientname,type="client"})
end
for _,parkingspot in pairs(parkingdata)do
local _spot=parkingspot.Coordinate
local _termid=parkingspot.TerminalID
local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius,scanunits,scanstatics,scanscenery)
for _,_unit in pairs(_units)do
local unit=_unit
local _coord=unit:GetVec3()
local _size=self:_GetObjectSize(unit:GetDCSObject())
local _name=unit:GetName()
if unit and unit:IsAlive()then
table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="unit"})
end
end
for _,static in pairs(_statics)do
local _coord=static:getPoint()
local _name=static:getName()
local _size=self:_GetObjectSize(static)
table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="static"})
end
for _,scenery in pairs(_sceneries)do
local _coord=scenery:getPoint()
local _name=scenery:getTypeName()
local _size=self:_GetObjectSize(scenery)
table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="scenery"})
end
end
local parking={}
for _,asset in pairs(assets)do
local _asset=asset
if not _asset.spawned then
local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute,self:GetAirbaseCategory())
parking[_asset.uid]={}
for i=1,_asset.nunits do
local assetname=_asset.spawngroupname.."-"..tostring(i)
local gotit=false
for _,_parkingspot in pairs(parkingdata)do
local parkingspot=_parkingspot
local valid=true
if asset.parkingIDs then
valid=self:_CheckParkingAsset(parkingspot,asset)
else
local validTerminal=AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype)
local validParking=self:_CheckParkingValid(parkingspot)
local validBWlist=airbase:_CheckParkingLists(parkingspot.TerminalID)
valid=validTerminal and validParking and validBWlist
end
if valid then
local _spot=parkingspot.Coordinate
local _termid=parkingspot.TerminalID
local free=true
local problem=nil
for _,obstacle in pairs(obstacles)do
local dist=_spot:Get2DDistance(obstacle.coord)
local safe=_overlap(_asset.size,obstacle.size,dist)
if not safe then
self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE",assetname,_asset.uid,_termid,dist))
free=false
problem=obstacle
problem.dist=dist
break
else
end
end
if free then
table.insert(parking[_asset.uid],parkingspot)
self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!",_termid,assetname,_asset.uid))
table.insert(obstacles,{coord=_spot,size=_asset.size,name=assetname,type="asset"})
gotit=true
break
else
if self.Debug then
local coord=problem.coord
local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.",problem.name,problem.type,_termid,problem.size,problem.dist)
self:I(self.lid..text)
coord:MarkToAll(string.format(text))
else
self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!",_termid))
end
end
else
self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported",parkingspot.TerminalID,parkingspot.TerminalType))
end
end
if not gotit then
self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]",assetname,_asset.uid))
return nil
end
end
end
end
return parking
end
function WAREHOUSE:_GetRequestOfGroup(group,queue)
local wid,aid,rid=self:_GetIDsFromGroup(group)
for _,_request in pairs(queue)do
local request=_request
if request.uid==rid then
return request
end
end
end
function WAREHOUSE:_GroupIsTransport(group,request)
local asset=self:FindAssetInDB(group)
if asset and asset.iscargo~=nil then
return not asset.iscargo
else
local groupname=self:_GetNameWithOut(group)
if request.transportgroupset then
local transporters=request.transportgroupset:GetSetObjects()
for _,transport in pairs(transporters)do
if transport:GetName()==groupname then
return true
end
end
end
if request.cargogroupset then
local cargos=request.cargogroupset:GetSetObjects()
for _,cargo in pairs(cargos)do
if self:_GetNameWithOut(cargo)==groupname then
return false
end
end
end
end
return nil
end
function WAREHOUSE:_GetNameWithOut(group)
local groupname=type(group)=="string"and group or group:GetName()
if groupname:find("CARGO")then
local name=groupname:gsub("#CARGO","")
return name
else
return groupname
end
end
function WAREHOUSE:_GetIDsFromGroup(group)
if group then
local groupname=group:GetName()
local wid,aid,rid=self:_GetIDsFromGroupName(groupname)
return wid,aid,rid
else
self:E("WARNING: Group not found in GetIDsFromGroup() function!")
end
end
function WAREHOUSE:_GetIDsFromGroupName(groupname)
local function analyse(text)
local unspawned=UTILS.Split(text,"#")[1]
local keywords=UTILS.Split(unspawned,"_")
local _wid=nil
local _aid=nil
local _rid=nil
for _,keys in pairs(keywords)do
local str=UTILS.Split(keys,"-")
local key=str[1]
local val=str[2]
if key:find("WID")then
_wid=tonumber(val)
elseif key:find("AID")then
_aid=tonumber(val)
elseif key:find("RID")then
_rid=tonumber(val)
end
end
return _wid,_aid,_rid
end
local wid,aid,rid=analyse(groupname)
local asset=self:GetAssetByID(aid)
if asset then
wid=asset.wid
rid=asset.rid
end
self:T3(self.lid..string.format("Group Name   = %s",tostring(groupname)))
self:T3(self.lid..string.format("Warehouse ID = %s",tostring(wid)))
self:T3(self.lid..string.format("Asset     ID = %s",tostring(aid)))
self:T3(self.lid..string.format("Request   ID = %s",tostring(rid)))
return wid,aid,rid
end
function WAREHOUSE:FilterStock(descriptor,attribute,nmax,mobile)
return self:_FilterStock(self.stock,descriptor,attribute,nmax,mobile)
end
function WAREHOUSE:_FilterStock(stock,descriptor,attribute,nmax,mobile)
nmax=nmax or WAREHOUSE.Quantity.ALL
if mobile==nil then
mobile=false
end
local filtered={}
if descriptor==WAREHOUSE.Descriptor.ASSETLIST then
local ntot=0
for _,_rasset in pairs(attribute)do
local rasset=_rasset
for _,_asset in ipairs(stock)do
local asset=_asset
if rasset.uid==asset.uid then
table.insert(filtered,asset)
break
end
end
end
return filtered,#filtered,#filtered>=#attribute
end
local ntot=0
for _,_asset in ipairs(stock)do
local asset=_asset
local ismobile=asset.speedmax>0
if asset[descriptor]==attribute then
if(mobile==true and ismobile)or mobile==false then
ntot=ntot+1
end
end
end
if ntot==0 then
return filtered,ntot,false
end
nmax=self:_QuantityRel2Abs(nmax,ntot)
for _i,_asset in ipairs(stock)do
local asset=_asset
if asset[descriptor]==attribute then
if(mobile and asset.speedmax>0)or(not mobile)then
table.insert(filtered,asset)
if nmax~=nil and#filtered>=nmax then
return filtered,ntot,true
end
end
end
end
return filtered,ntot,ntot>=nmax
end
function WAREHOUSE:_HasAttribute(group,attribute)
if group then
local groupattribute=self:_GetAttribute(group)
return groupattribute==attribute
end
return false
end
function WAREHOUSE:_GetAttribute(group)
local attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN
if group then
local transportplane=group:HasAttribute("Transports")and group:HasAttribute("Planes")
local awacs=group:HasAttribute("AWACS")
local fighter=group:HasAttribute("Fighters")or group:HasAttribute("Interceptors")or group:HasAttribute("Multirole fighters")or(group:HasAttribute("Bombers")and not group:HasAttribute("Strategic bombers"))
local bomber=group:HasAttribute("Strategic bombers")
local tanker=group:HasAttribute("Tankers")
local uav=group:HasAttribute("UAVs")
local transporthelo=group:HasAttribute("Transport helicopters")
local attackhelicopter=group:HasAttribute("Attack helicopters")
local apc=group:HasAttribute("APC")
local truck=group:HasAttribute("Trucks")and group:GetCategory()==Group.Category.GROUND
local infantry=group:HasAttribute("Infantry")
local ifv=group:HasAttribute("IFV")
local artillery=group:HasAttribute("Artillery")
local tank=group:HasAttribute("Old Tanks")or group:HasAttribute("Modern Tanks")
local aaa=group:HasAttribute("AAA")
local ewr=group:HasAttribute("EWR")
local sam=group:HasAttribute("SAM elements")and(not group:HasAttribute("AAA"))
local train=group:GetCategory()==Group.Category.TRAIN
local aircraftcarrier=group:HasAttribute("Aircraft Carriers")
local warship=group:HasAttribute("Heavy armed ships")
local armedship=group:HasAttribute("Armed ships")or group:HasAttribute("Armed Ship")
local unarmedship=group:HasAttribute("Unarmed ships")
if transportplane then
attribute=WAREHOUSE.Attribute.AIR_TRANSPORTPLANE
elseif awacs then
attribute=WAREHOUSE.Attribute.AIR_AWACS
elseif fighter then
attribute=WAREHOUSE.Attribute.AIR_FIGHTER
elseif bomber then
attribute=WAREHOUSE.Attribute.AIR_BOMBER
elseif tanker then
attribute=WAREHOUSE.Attribute.AIR_TANKER
elseif transporthelo then
attribute=WAREHOUSE.Attribute.AIR_TRANSPORTHELO
elseif attackhelicopter then
attribute=WAREHOUSE.Attribute.AIR_ATTACKHELO
elseif uav then
attribute=WAREHOUSE.Attribute.AIR_UAV
elseif apc then
attribute=WAREHOUSE.Attribute.GROUND_APC
elseif ifv then
attribute=WAREHOUSE.Attribute.GROUND_IFV
elseif infantry then
attribute=WAREHOUSE.Attribute.GROUND_INFANTRY
elseif artillery then
attribute=WAREHOUSE.Attribute.GROUND_ARTILLERY
elseif tank then
attribute=WAREHOUSE.Attribute.GROUND_TANK
elseif aaa then
attribute=WAREHOUSE.Attribute.GROUND_AAA
elseif ewr then
attribute=WAREHOUSE.Attribute.GROUND_EWR
elseif sam then
attribute=WAREHOUSE.Attribute.GROUND_SAM
elseif truck then
attribute=WAREHOUSE.Attribute.GROUND_TRUCK
elseif train then
attribute=WAREHOUSE.Attribute.GROUND_TRAIN
elseif aircraftcarrier then
attribute=WAREHOUSE.Attribute.NAVAL_AIRCRAFTCARRIER
elseif warship then
attribute=WAREHOUSE.Attribute.NAVAL_WARSHIP
elseif armedship then
attribute=WAREHOUSE.Attribute.NAVAL_ARMEDSHIP
elseif unarmedship then
attribute=WAREHOUSE.Attribute.NAVAL_UNARMEDSHIP
else
if group:IsGround()then
attribute=WAREHOUSE.Attribute.GROUND_OTHER
elseif group:IsShip()then
attribute=WAREHOUSE.Attribute.NAVAL_OTHER
elseif group:IsAir()then
attribute=WAREHOUSE.Attribute.AIR_OTHER
else
attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN
end
end
end
return attribute
end
function WAREHOUSE:_GetObjectSize(DCSobject)
local DCSdesc=DCSobject:getDesc()
if DCSdesc.box then
local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x)
local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y)
local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z)
return math.max(x,z),x,y,z
end
return 0,0,0,0
end
function WAREHOUSE:GetStockInfo(stock)
local _data={}
for _j,_attribute in pairs(WAREHOUSE.Attribute)do
local n=0
for _i,_item in pairs(stock)do
local _ite=_item
if _ite.attribute==_attribute then
n=n+1
end
end
_data[_attribute]=n
end
return _data
end
function WAREHOUSE:_DeleteStockItem(stockitem)
for i=1,#self.stock do
local item=self.stock[i]
if item.uid==stockitem.uid then
table.remove(self.stock,i)
break
end
end
end
function WAREHOUSE:_DeleteQueueItem(qitem,queue)
self:F({qitem=qitem,queue=queue})
for i=1,#queue do
local _item=queue[i]
if _item.uid==qitem.uid then
self:T(self.lid..string.format("Deleting queue item id=%d.",qitem.uid))
table.remove(queue,i)
break
end
end
end
function WAREHOUSE:_DeleteQueueItemByID(qitemID,queue)
for i=1,#queue do
local _item=queue[i]
if _item.uid==qitemID then
self:T(self.lid..string.format("Deleting queue item id=%d.",qitemID))
table.remove(queue,i)
break
end
end
end
function WAREHOUSE:_SortQueue()
self:F3()
local function _sort(a,b)
return(a.prio<b.prio)or(a.prio==b.prio and a.uid<b.uid)
end
table.sort(self.queue,_sort)
end
function WAREHOUSE:_CheckFuel()
for i,qitem in ipairs(self.pending)do
local qitem=qitem
if qitem.transportgroupset then
for _,_group in pairs(qitem.transportgroupset:GetSet())do
local group=_group
if group and group:IsAlive()then
local fuel=group:GetFuelMin()
self:T2(self.lid..string.format("Transport group %s min fuel state = %.2f",group:GetName(),fuel))
if fuel<self.lowfuelthresh and not qitem.lowfuel then
self:I(self.lid..string.format("Transport group %s is low on fuel! Min fuel state = %.2f",group:GetName(),fuel))
qitem.lowfuel=true
local asset=self:FindAssetInDB(group)
self:AssetLowFuel(asset,qitem)
break
end
end
end
end
if qitem.cargogroupset then
for _,_group in pairs(qitem.cargogroupset:GetSet())do
local group=_group
if group and group:IsAlive()then
local fuel=group:GetFuelMin()
self:T2(self.lid..string.format("Cargo group %s min fuel state = %.2f. Threshold = %.2f",group:GetName(),fuel,self.lowfuelthresh))
if fuel<self.lowfuelthresh and not qitem.lowfuel then
self:I(self.lid..string.format("Cargo group %s is low on fuel! Min fuel state = %.2f",group:GetName(),fuel))
qitem.lowfuel=true
local asset=self:FindAssetInDB(group)
self:AssetLowFuel(asset,qitem)
break
end
end
end
end
end
end
function WAREHOUSE:_PrintQueue(queue,name)
if self.verbosity>=2 then
local total="Empty"
if#queue>0 then
total=string.format("Total = %d",#queue)
end
local text=string.format("%s at %s: %s",name,self.alias,total)
for i,qitem in ipairs(queue)do
local qitem=qitem
local uid=qitem.uid
local prio=qitem.prio
local clock="N/A"
if qitem.timestamp then
clock=tostring(UTILS.SecondsToClock(qitem.timestamp))
end
local assignment=tostring(qitem.assignment)
local requestor=qitem.warehouse.alias
local airbasename=qitem.warehouse:GetAirbaseName()
local requestorAirbaseCat=qitem.warehouse:GetAirbaseCategory()
local assetdesc=qitem.assetdesc
local assetdescval=qitem.assetdescval
if assetdesc==WAREHOUSE.Descriptor.ASSETLIST then
assetdescval="Asset list"
end
local nasset=tostring(qitem.nasset)
local ndelivered=tostring(qitem.ndelivered)
local ncargogroupset="N/A"
if qitem.cargogroupset then
ncargogroupset=tostring(qitem.cargogroupset:Count())
end
local transporttype="N/A"
if qitem.transporttype then
transporttype=qitem.transporttype
end
local ntransport="N/A"
if qitem.ntransport then
ntransport=tostring(qitem.ntransport)
end
local ntransportalive="N/A"
if qitem.transportgroupset then
ntransportalive=tostring(qitem.transportgroupset:Count())
end
local ntransporthome="N/A"
if qitem.ntransporthome then
ntransporthome=tostring(qitem.ntransporthome)
end
text=text..string.format(
"\n%d) UID=%d, Prio=%d, Clock=%s, Assignment=%s | Requestor=%s [Airbase=%s, category=%d] | Assets(%s)=%s: #requested=%s / #alive=%s / #delivered=%s | Transport=%s: #requested=%s / #alive=%s / #home=%s",
i,uid,prio,clock,assignment,requestor,airbasename,requestorAirbaseCat,assetdesc,assetdescval,nasset,ncargogroupset,ndelivered,transporttype,ntransport,ntransportalive,ntransporthome)
end
if#queue==0 then
self:I(self.lid..text)
else
if total~="Empty"then
self:I(self.lid..text)
end
end
end
end
function WAREHOUSE:_DisplayStatus()
if self.verbosity>=3 then
local text=string.format("\n------------------------------------------------------\n")
text=text..string.format("Warehouse %s status: %s\n",self.alias,self:GetState())
text=text..string.format("------------------------------------------------------\n")
text=text..string.format("Coalition name   = %s\n",self:GetCoalitionName())
text=text..string.format("Country name     = %s\n",self:GetCountryName())
text=text..string.format("Airbase name     = %s (category=%d)\n",self:GetAirbaseName(),self:GetAirbaseCategory())
text=text..string.format("Queued requests  = %d\n",#self.queue)
text=text..string.format("Pending requests = %d\n",#self.pending)
text=text..string.format("------------------------------------------------------\n")
text=text..self:_GetStockAssetsText()
self:I(text)
end
end
function WAREHOUSE:_GetStockAssetsText(messagetoall)
local _data=self:GetStockInfo(self.stock)
local text="Stock:\n"
local total=0
for _attribute,_count in pairs(_data)do
if _count>0 then
local attribute=tostring(UTILS.Split(_attribute,"_")[2])
text=text..string.format("%s = %d\n",attribute,_count)
total=total+_count
end
end
text=text..string.format("===================\n")
text=text..string.format("Total = %d\n",total)
text=text..string.format("------------------------------------------------------\n")
MESSAGE:New(text,10):ToAllIf(messagetoall)
return text
end
function WAREHOUSE:_UpdateWarehouseMarkText()
if self.markerOn then
local text=string.format("Warehouse state: %s\nTotal assets in stock %d:\n",self:GetState(),#self.stock)
for _attribute,_count in pairs(self:GetStockInfo(self.stock)or{})do
if _count>0 then
local attribute=tostring(UTILS.Split(_attribute,"_")[2])
text=text..string.format("%s=%d, ",attribute,_count)
end
end
local coordinate=self:GetCoordinate()
local coalition=self:GetCoalition()
if not self.markerWarehouse then
self.markerWarehouse=MARKER:New(coordinate,text):ToCoalition(coalition)
else
local refresh=false
if self.markerWarehouse.text~=text then
self.markerWarehouse.text=text
refresh=true
end
if self.markerWarehouse.coordinate~=coordinate then
self.markerWarehouse.coordinate=coordinate
refresh=true
end
if self.markerWarehouse.coalition~=coalition then
self.markerWarehouse.coalition=coalition
refresh=true
end
if refresh then
self.markerWarehouse:Refresh()
end
end
end
end
function WAREHOUSE:_DisplayStockItems(stock)
local text=self.lid..string.format("Warehouse %s stock assets:",self.alias)
for _i,_stock in pairs(stock)do
local mystock=_stock
local name=mystock.templatename
local category=mystock.category
local cargobaymax=mystock.cargobaymax
local cargobaytot=mystock.cargobaytot
local nunits=mystock.nunits
local range=mystock.range
local size=mystock.size
local speed=mystock.speedmax
local uid=mystock.uid
local unittype=mystock.unittype
local weight=mystock.weight
local attribute=mystock.attribute
text=text..string.format("\n%02d) uid=%d, name=%s, unittype=%s, category=%d, attribute=%s, nunits=%d, speed=%.1f km/h, range=%.1f km, size=%.1f m, weight=%.1f kg, cargobax max=%.1f kg tot=%.1f kg",
_i,uid,name,unittype,category,attribute,nunits,speed,range/1000,size,weight,cargobaymax,cargobaytot)
end
self:T3(text)
end
function WAREHOUSE:_Fireworks(coord)
coord=coord or self:GetCoordinate()
for i=1,91 do
local color=math.random(0,3)
coord:Flare(color,i-1)
end
end
function WAREHOUSE:_InfoMessage(text,duration)
duration=duration or 20
if duration>0 and self.Debug or self.Report then
MESSAGE:New(text,duration):ToCoalition(self:GetCoalition())
end
self:I(self.lid..text)
end
function WAREHOUSE:_DebugMessage(text,duration)
duration=duration or 20
if self.Debug and duration>0 then
MESSAGE:New(text,duration):ToAllIf(self.Debug)
end
self:T(self.lid..text)
end
function WAREHOUSE:_ErrorMessage(text,duration)
duration=duration or 20
if duration>0 then
MESSAGE:New(text,duration):ToAll()
end
self:E(self.lid..text)
end
function WAREHOUSE:_GetMaxHeight(D,alphaC,alphaD,Hdep,Hdest,Deltahhold)
local Hhold=Hdest+Deltahhold
local hdest=Hdest-Hdep
local hhold=hdest+Deltahhold
local Dp=math.sqrt(D^2+hhold^2)
local alphaS=math.atan(hdest/D)
local alphaH=math.atan(hhold/D)
local alphaCp=alphaC-alphaH
local alphaDp=alphaD+alphaH
local gammap=math.pi-alphaCp-alphaDp
local sCp=Dp*math.sin(alphaDp)/math.sin(gammap)
local sDp=Dp*math.sin(alphaCp)/math.sin(gammap)
local hmax=sCp*math.sin(alphaC)
if self.Debug then
env.info(string.format("Hdep    = %.3f km",Hdep/1000))
env.info(string.format("Hdest   = %.3f km",Hdest/1000))
env.info(string.format("DetaHold= %.3f km",Deltahhold/1000))
env.info()
env.info(string.format("D       = %.3f km",D/1000))
env.info(string.format("Dp      = %.3f km",Dp/1000))
env.info()
env.info(string.format("alphaC  = %.3f Deg",math.deg(alphaC)))
env.info(string.format("alphaCp = %.3f Deg",math.deg(alphaCp)))
env.info()
env.info(string.format("alphaD  = %.3f Deg",math.deg(alphaD)))
env.info(string.format("alphaDp = %.3f Deg",math.deg(alphaDp)))
env.info()
env.info(string.format("alphaS  = %.3f Deg",math.deg(alphaS)))
env.info(string.format("alphaH  = %.3f Deg",math.deg(alphaH)))
env.info()
env.info(string.format("sCp      = %.3f km",sCp/1000))
env.info(string.format("sDp      = %.3f km",sDp/1000))
env.info()
env.info(string.format("hmax     = %.3f km",hmax/1000))
env.info()
local hdescent=hmax-hhold
local dClimb=hmax/math.tan(alphaC)
local dDescent=(hmax-hhold)/math.tan(alphaD)
local dCruise=D-dClimb-dDescent
env.info(string.format("hmax     = %.3f km",hmax/1000))
env.info(string.format("hdescent = %.3f km",hdescent/1000))
env.info(string.format("Dclimb   = %.3f km",dClimb/1000))
env.info(string.format("Dcruise  = %.3f km",dCruise/1000))
env.info(string.format("Ddescent = %.3f km",dDescent/1000))
env.info()
end
return hmax
end
function WAREHOUSE:_GetFlightplan(asset,departure,destination)
local Vmax=asset.speedmax/3.6
local Range=asset.range
local category=asset.category
local ceiling=asset.DCSdesc.Hmax
local Vymax=asset.DCSdesc.VyMax
local VxCruiseMax=0.90*Vmax
local VxCruiseMin=math.min(VxCruiseMax*0.70,166)
local VxCruise=UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin,(VxCruiseMax-VxCruiseMax)/4,VxCruiseMin,VxCruiseMax)
local VxClimb=math.min(Vmax*0.90,200)
local VxDescent=math.min(Vmax*0.60,140)
local VxHolding=VxDescent*0.9
local VxFinal=VxHolding*0.9
local VyClimb=math.min(7.6,Vymax)
local AlphaClimb=math.rad(4)
local AlphaDescent=math.rad(4)
local FLcruise_expect=150*RAT.unit.FL2m
if category==Group.Category.HELICOPTER then
FLcruise_expect=1000
end
local Pdeparture=departure:GetCoordinate()
local H_departure=Pdeparture.y
local Pdestination=destination:GetCoordinate()
local H_destination=Pdestination.y
local Rhmin=5000
local Rhmax=10000
if category==Group.Category.HELICOPTER then
Rhmin=500
Rhmax=1000
end
local Pholding=Pdestination:GetRandomCoordinateInRadius(Rhmax,Rhmin)
local d_holding=Pholding:Get2DDistance(Pdestination)
local H_holding=Pholding.y
local heading=Pdeparture:HeadingTo(Pholding)
local d_total=Pdeparture:Get2DDistance(Pholding)
local h_holding=1200
if category==Group.Category.HELICOPTER then
h_holding=150
end
h_holding=UTILS.Randomize(h_holding,0.2)
local DeltaholdingMax=self:_GetMaxHeight(d_total,AlphaClimb,AlphaDescent,H_departure,H_holding,0)
if h_holding>DeltaholdingMax then
h_holding=math.abs(DeltaholdingMax)
end
local Hh_holding=H_holding+h_holding
local h_max=self:_GetMaxHeight(d_total,AlphaClimb,AlphaDescent,H_departure,H_holding,h_holding)
local FLmax=h_max+H_departure
local FLmin=math.max(H_departure,Hh_holding)
FLmax=math.min(FLmax,ceiling)
if FLmin>FLmax then
FLmin=FLmax
end
if FLcruise_expect<FLmin then
FLcruise_expect=FLmin
end
if FLcruise_expect>FLmax then
FLcruise_expect=FLmax
end
local FLcruise=UTILS.RandomGaussian(FLcruise_expect,math.abs(FLmax-FLmin)/4,FLmin,FLmax)
local h_climb=FLcruise-H_departure
local h_descent=FLcruise-Hh_holding
local d_climb=h_climb/math.tan(AlphaClimb)
local d_descent=h_descent/math.tan(AlphaDescent)
local d_cruise=d_total-d_climb-d_descent
local text=string.format("Flight plan:\n")
text=text..string.format("Vx max        = %.2f km/h\n",Vmax*3.6)
text=text..string.format("Vx climb      = %.2f km/h\n",VxClimb*3.6)
text=text..string.format("Vx cruise     = %.2f km/h\n",VxCruise*3.6)
text=text..string.format("Vx descent    = %.2f km/h\n",VxDescent*3.6)
text=text..string.format("Vx holding    = %.2f km/h\n",VxHolding*3.6)
text=text..string.format("Vx final      = %.2f km/h\n",VxFinal*3.6)
text=text..string.format("Vy max        = %.2f m/s\n",Vymax)
text=text..string.format("Vy climb      = %.2f m/s\n",VyClimb)
text=text..string.format("Alpha Climb   = %.2f Deg\n",math.deg(AlphaClimb))
text=text..string.format("Alpha Descent = %.2f Deg\n",math.deg(AlphaDescent))
text=text..string.format("Dist climb    = %.3f km\n",d_climb/1000)
text=text..string.format("Dist cruise   = %.3f km\n",d_cruise/1000)
text=text..string.format("Dist descent  = %.3f km\n",d_descent/1000)
text=text..string.format("Dist total    = %.3f km\n",d_total/1000)
text=text..string.format("h_climb       = %.3f km\n",h_climb/1000)
text=text..string.format("h_desc        = %.3f km\n",h_descent/1000)
text=text..string.format("h_holding     = %.3f km\n",h_holding/1000)
text=text..string.format("h_max         = %.3f km\n",h_max/1000)
text=text..string.format("FL min        = %.3f km\n",FLmin/1000)
text=text..string.format("FL expect     = %.3f km\n",FLcruise_expect/1000)
text=text..string.format("FL cruise *   = %.3f km\n",FLcruise/1000)
text=text..string.format("FL max        = %.3f km\n",FLmax/1000)
text=text..string.format("Ceiling       = %.3f km\n",ceiling/1000)
text=text..string.format("Max range     = %.3f km\n",Range/1000)
self:T(self.lid..text)
if d_cruise<0 then
d_cruise=100
end
local wp={}
local c={}
local _type=COORDINATE.WaypointType.TakeOffParking
local _action=COORDINATE.WaypointAction.FromParkingArea
if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then
_type=COORDINATE.WaypointType.TakeOffParkingHot
_action=COORDINATE.WaypointAction.FromParkingAreaHot
else
end
c[#c+1]=Pdeparture
wp[#wp+1]=Pdeparture:WaypointAir("RADIO",_type,_action,VxClimb*3.6,true,departure,nil,"Departure")
local Pcruise=Pdeparture:Translate(d_climb,heading)
Pcruise.y=FLcruise
c[#c+1]=Pcruise
wp[#wp+1]=Pcruise:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,VxCruise*3.6,true,nil,nil,"Cruise")
local Pdescent=Pcruise:Translate(d_cruise,heading)
Pdescent.y=FLcruise
c[#c+1]=Pdescent
wp[#wp+1]=Pdescent:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,VxDescent*3.6,true,nil,nil,"Descent")
Pholding.y=H_holding+h_holding
c[#c+1]=Pholding
wp[#wp+1]=Pholding:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,VxHolding*3.6,true,nil,nil,"Holding")
c[#c+1]=Pdestination
wp[#wp+1]=Pdestination:WaypointAir("RADIO",COORDINATE.WaypointType.Land,COORDINATE.WaypointAction.Landing,VxFinal*3.6,true,destination,nil,"Final Destination")
if self.Debug then
for i,coord in pairs(c)do
local coord=coord
local dist=0
if i>1 then
dist=coord:Get2DDistance(c[i-1])
end
coord:MarkToAll(string.format("Waypoint %i, distance = %.2f km",i,dist/1000))
end
end
return wp,c
end
FOX={
ClassName="FOX",
Debug=false,
lid=nil,
menuadded={},
menudisabled=nil,
destroy=nil,
launchalert=nil,
marklaunch=nil,
missiles={},
players={},
safezones={},
launchzones={},
protectedset=nil,
explosionpower=0.1,
explosiondist=200,
explosiondist2=500,
bigmissilemass=50,
destroy=nil,
dt50=5,
dt10=1,
dt05=0.5,
dt01=0.1,
dt00=0.01,
}
FOX.MenuF10={}
FOX.MenuF10Root=nil
FOX.version="0.6.1"
function FOX:New()
self.lid="FOX | "
local self=BASE:Inherit(self,FSM:New())
self:SetDefaultMissileDestruction(true)
self:SetDefaultLaunchAlerts(true)
self:SetDefaultLaunchMarks(true)
self:SetExplosionDistance()
self:SetExplosionDistanceBigMissiles()
self:SetExplosionPower()
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","MissileLaunch","*")
self:AddTransition("*","MissileDestroyed","*")
self:AddTransition("*","EnterSafeZone","*")
self:AddTransition("*","ExitSafeZone","*")
self:AddTransition("Running","Stop","Stopped")
return self
end
function FOX:onafterStart(From,Event,To)
local text=string.format("Starting FOX Missile Trainer %s",FOX.version)
env.info(text)
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Shot)
if self.Debug then
self:HandleEvent(EVENTS.Hit)
end
if self.Debug then
self:TraceClass(self.ClassName)
self:TraceLevel(2)
end
self:__Status(-20)
end
function FOX:onafterStop(From,Event,To)
local text=string.format("Stopping FOX Missile Trainer %s",FOX.version)
env.info(text)
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.Shot)
if self.Debug then
self:UnhandleEvent(EVENTS.Hit)
end
end
function FOX:AddSafeZone(zone)
table.insert(self.safezones,zone)
return self
end
function FOX:AddLaunchZone(zone)
table.insert(self.launchzones,zone)
return self
end
function FOX:SetProtectedGroupSet(groupset)
self.protectedset=groupset
return self
end
function FOX:AddProtectedGroup(group)
if not self.protectedset then
self.protectedset=SET_GROUP:New()
end
self.protectedset:AddGroup(group)
return self
end
function FOX:SetExplosionPower(power)
self.explosionpower=power or 0.1
return self
end
function FOX:SetExplosionDistance(distance)
self.explosiondist=distance or 200
return self
end
function FOX:SetExplosionDistanceBigMissiles(distance,explosivemass)
self.explosiondist2=distance or 500
self.bigmissilemass=explosivemass or 50
return self
end
function FOX:SetDisableF10Menu()
self.menudisabled=true
return self
end
function FOX:SetEnableF10Menu()
self.menudisabled=false
return self
end
function FOX:SetDefaultMissileDestruction(switch)
if switch==nil then
self.destroy=false
else
self.destroy=switch
end
return self
end
function FOX:SetDefaultLaunchAlerts(switch)
if switch==nil then
self.launchalert=false
else
self.launchalert=switch
end
return self
end
function FOX:SetDefaultLaunchMarks(switch)
if switch==nil then
self.marklaunch=false
else
self.marklaunch=switch
end
return self
end
function FOX:SetDebugOnOff(switch)
if switch==nil then
self.Debug=false
else
self.Debug=switch
end
return self
end
function FOX:SetDebugOn()
self:SetDebugOnOff(true)
return self
end
function FOX:SetDebugOff()
self:SetDebugOff(false)
return self
end
function FOX:onafterStatus(From,Event,To)
local fsmstate=self:GetState()
local time=timer.getAbsTime()
local clock=UTILS.SecondsToClock(time)
self:I(self.lid..string.format("Missile trainer status %s: %s",clock,fsmstate))
self:_CheckMissileStatus()
self:_CheckPlayers()
if fsmstate=="Running"then
self:__Status(-10)
end
end
function FOX:_CheckPlayers()
for playername,_playersettings in pairs(self.players)do
local playersettings=_playersettings
local unitname=playersettings.unitname
local unit=UNIT:FindByName(unitname)
if unit and unit:IsAlive()then
local coord=unit:GetCoordinate()
local issafe=self:_CheckCoordSafe(coord)
if issafe then
if not playersettings.inzone then
self:EnterSafeZone(playersettings)
playersettings.inzone=true
end
else
if playersettings.inzone==true then
self:ExitSafeZone(playersettings)
playersettings.inzone=false
end
end
end
end
end
function FOX:_RemoveMissile(missile)
if missile then
for i,_missile in pairs(self.missiles)do
local m=_missile
if missile.missileName==m.missileName then
table.remove(self.missiles,i)
return
end
end
end
end
function FOX:_CheckMissileStatus()
local text="Missiles:"
local inactive={}
for i,_missile in pairs(self.missiles)do
local missile=_missile
local targetname="unkown"
if missile.targetUnit then
targetname=missile.targetUnit:GetName()
end
local playername="none"
if missile.targetPlayer then
playername=missile.targetPlayer.name
end
local active=tostring(missile.active)
local mtype=missile.missileType
local dtype=missile.missileType
local range=UTILS.MetersToNM(missile.missileRange)
if not active then
table.insert(inactive,i)
end
local heading=self:_GetWeapongHeading(missile.weapon)
text=text..string.format("\n[%d] %s: active=%s, range=%.1f NM, heading=%03d, target=%s, player=%s, missilename=%s",i,mtype,active,range,heading,targetname,playername,missile.missileName)
end
if#self.missiles==0 then
text=text.." none"
end
self:I(self.lid..text)
for i=#self.missiles,1,-1 do
local missile=self.missiles[i]
if missile and not missile.active then
table.remove(self.missiles,i)
end
end
end
function FOX:_IsProtected(targetunit)
if not self.protectedset then
return false
end
if targetunit and targetunit:IsAlive()then
local targetgroup=targetunit:GetGroup()
if targetgroup then
local targetname=targetgroup:GetName()
for _,_group in pairs(self.protectedset:GetSetObjects())do
local group=_group
if group then
local groupname=group:GetName()
if targetname==groupname then
return true
end
end
end
end
end
return false
end
function FOX:onafterMissileLaunch(From,Event,To,missile)
local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s",missile.missileType,missile.missileName,tostring(missile.targetName),missile.shooterName)
self:I(FOX.lid..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
for _,_player in pairs(self.players)do
local player=_player
local playerUnit=player.unit
if playerUnit and playerUnit:IsAlive()and player.coalition~=missile.shooterCoalition then
local distance=playerUnit:GetCoordinate():Get3DDistance(missile.shotCoord)
local bearing=playerUnit:GetCoordinate():HeadingTo(missile.shotCoord)
if player.launchalert then
if(missile.targetPlayer and player.unitname==missile.targetPlayer.unitname)or(distance<missile.missileRange)then
local text=string.format("Missile launch detected! Distance %.1f NM, bearing %03d°.",UTILS.MetersToNM(distance),bearing)
self:ScheduleOnce(5,FOX._SayNotchingHeadings,self,player,missile.weapon)
MESSAGE:New(text,5,"ALERT"):ToClient(player.client)
end
end
if player.marklaunch then
local text=string.format("Missile launch coordinates:\n%s\n%s",missile.shotCoord:ToStringLLDMS(),missile.shotCoord:ToStringBULLS(player.coalition))
missile.shotCoord:MarkToGroup(text,player.group)
end
end
end
local _lastBombPos={x=0,y=0,z=0}
local missileCoord=nil
local target=nil
local function trackMissile(_ordnance)
local _status,_bombPos=pcall(
function()
return _ordnance:getPoint()
end)
if _status then
_lastBombPos={x=_bombPos.x,y=_bombPos.y,z=_bombPos.z}
missileCoord=COORDINATE:NewFromVec3(_lastBombPos)
local missileVelocity=UTILS.VecNorm(_ordnance:getVelocity())
self:GetMissileTarget(missile)
if missile.targetUnit then
if missile.targetPlayer then
if missile.targetPlayer.destroy==true then
target=missile.targetUnit
end
else
if self:_IsProtected(missile.targetUnit)then
target=missile.targetUnit
end
end
else
local function _GetTarget(_unit)
local unit=_unit
local playerCoord=unit:GetCoordinate()
local dist=missileCoord:Get3DDistance(playerCoord)
if dist<=self.explosiondist then
return unit
end
end
local mindist=nil
for _,_player in pairs(self.players)do
local player=_player
if player.unitname~=missile.shooterName then
local playerCoord=player.unit:GetCoordinate()
local dist=missileCoord:Get3DDistance(playerCoord)
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
if(mindist==nil or dist<mindist)and(Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist)then
mindist=dist
target=player.unit
end
end
end
if self.protectedset then
mindist=nil
for _,_group in pairs(self.protectedset:GetSet())do
local group=_group
for _,_unit in pairs(group:GetUnits())do
local unit=_unit
if unit and unit:IsAlive()then
if unit:GetName()~=missile.shooterName then
local playerCoord=unit:GetCoordinate()
local dist=missileCoord:Get3DDistance(playerCoord)
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
if(mindist==nil or dist<mindist)and(Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist)then
mindist=dist
target=unit
end
end
end
end
end
end
if target then
self:T(self.lid..string.format("Missile %s with NO explicit target got closest unit to missile as target %s. Dist=%s m",missile.missileType,target:GetName(),tostring(mindist)))
end
end
if target then
local targetCoord=target:GetCoordinate()
local distance=missileCoord:Get3DDistance(targetCoord)
local distShooter=nil
if missile.shooterUnit and missile.shooterUnit:IsAlive()then
distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetCoordinate())
end
if self.Debug then
local bearing=targetCoord:HeadingTo(missileCoord)
local eta=distance/missileVelocity
self:I(self.lid..string.format("Missile %s Target %s: Distance = %.1f m, v=%.1f m/s, bearing=%03d°, ETA=%.1f sec",missile.missileType,target:GetName(),distance,missileVelocity,bearing,eta))
end
local destroymissile=distance<=self.explosiondist
if self.explosiondist2 and distance<=self.explosiondist2 and not destroymissile then
destroymissile=missile.explosive>=self.bigmissilemass
end
if destroymissile and self:_CheckCoordSafe(targetCoord)then
self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m",
missile.missileType,missile.missileName,missile.shooterName,target:GetName(),tostring(missile.targetPlayer~=nil),distance))
_ordnance:destroy()
missile.active=false
if self.Debug then
missileCoord:SmokeRed()
targetCoord:SmokeGreen()
end
self:MissileDestroyed(missile)
if self.explosionpower>0 and distance>50 and(distShooter==nil or(distShooter and distShooter>50))then
missileCoord:Explosion(self.explosionpower)
end
if missile.targetPlayer then
local text=string.format("Destroying missile. %s",self:_DeadText())
MESSAGE:New(text,10):ToGroup(target:GetGroup())
missile.targetPlayer.dead=missile.targetPlayer.dead+1
end
return nil
else
local dt=1.0
if distance>50000 then
dt=self.dt50
elseif distance>10000 then
dt=self.dt10
elseif distance>5000 then
dt=self.dt05
elseif distance>1000 then
dt=self.dt01
else
dt=self.dt00
end
return timer.getTime()+dt
end
else
self:T(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.",missile.missileType,missile.missileName,missile.shooterName))
return timer.getTime()+0.1
end
else
if target then
local player=self:_GetPlayerFromUnit(target)
if player and player.unit:IsAlive()then
local text=string.format("Missile defeated. Well done, %s!",player.name)
MESSAGE:New(text,10):ToClient(player.client)
player.defeated=player.defeated+1
end
end
missile.active=false
self:T(FOX.lid..string.format("Terminating missile track timer."))
return nil
end
end
self:T(FOX.lid..string.format("Tracking of missile starts in 0.0001 seconds."))
timer.scheduleFunction(trackMissile,missile.weapon,timer.getTime()+0.0001)
end
function FOX:OnEventPlayerEnterAircraft(EventData)
end
function FOX:OnEventBirth(EventData)
self:F3({eventbirth=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event BIRTH!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local playerunit,playername=self:_GetPlayerUnitAndName(_unitName)
self:T(self.lid.."BIRTH: unit   = "..tostring(EventData.IniUnitName))
self:T(self.lid.."BIRTH: group  = "..tostring(EventData.IniGroupName))
self:T(self.lid.."BIRTH: player = "..tostring(playername))
if playerunit and playername then
local _uid=playerunit:GetID()
local _group=playerunit:GetGroup()
local _callsign=playerunit:GetCallsign()
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.",playername,_callsign,_unitName,_group:GetName())
self:T(self.lid..text)
MESSAGE:New(text,5):ToAllIf(self.Debug)
if not self.menudisabled then
self:ScheduleOnce(0.1,self._AddF10Commands,self,_unitName)
end
local playerData={}
playerData.unit=playerunit
playerData.unitname=_unitName
playerData.group=_group
playerData.groupname=_group:GetName()
playerData.name=playername
playerData.callsign=playerData.unit:GetCallsign()
playerData.client=CLIENT:FindByName(_unitName,nil,true)
playerData.coalition=_group:GetCoalition()
playerData.destroy=playerData.destroy or self.destroy
playerData.launchalert=playerData.launchalert or self.launchalert
playerData.marklaunch=playerData.marklaunch or self.marklaunch
playerData.defeated=playerData.defeated or 0
playerData.dead=playerData.dead or 0
self.players[playername]=playerData
end
end
function FOX:GetMissileTarget(missile)
local target=nil
local targetName="unknown"
local targetUnit=nil
if missile.weapon and missile.weapon:isExist()then
target=missile.weapon:getTarget()
if target then
self:T2({missiletarget=target})
targetUnit=UNIT:Find(target)
if targetUnit then
targetName=targetUnit:GetName()
missile.targetUnit=targetUnit
missile.targetPlayer=self:_GetPlayerFromUnit(missile.targetUnit)
end
end
end
if missile.targetName and missile.targetName~=targetName then
self:I(self.lid..string.format("Missile %s(%s) changed target to %s. Previous target was %s.",missile.missileType,missile.missileName,targetName,missile.targetName))
end
missile.targetName=targetName
end
function FOX:OnEventShot(EventData)
self:T2({eventshot=EventData})
if EventData.Weapon==nil then
return
end
if EventData.IniDCSUnit==nil then
return
end
local _weapon=EventData.WeaponName
local _target=EventData.Weapon:getTarget()
local _targetName="unknown"
local _targetUnit=nil
local desc=EventData.Weapon:getDesc()
self:T2({desc=desc})
local weaponcategory=desc.category
local missilecategory=desc.missileCategory
local missilerange=nil
if missilecategory then
missilerange=desc.rangeMaxAltMax
end
self:T2(FOX.lid.."EVENT SHOT: FOX")
self:T2(FOX.lid..string.format("EVENT SHOT: Ini unit     = %s",tostring(EventData.IniUnitName)))
self:T2(FOX.lid..string.format("EVENT SHOT: Ini group    = %s",tostring(EventData.IniGroupName)))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon type  = %s",tostring(_weapon)))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon categ = %s",tostring(weaponcategory)))
self:T2(FOX.lid..string.format("EVENT SHOT: Missil categ = %s",tostring(missilecategory)))
self:T2(FOX.lid..string.format("EVENT SHOT: Missil range = %s",tostring(missilerange)))
if not self:_CheckCoordLaunch(EventData.IniUnit:GetCoordinate())then
self:T(self.lid.."Missile was not fired in launch zone. No tracking!")
return
end
local _track=weaponcategory==1 and missilecategory and(missilecategory==1 or missilecategory==2 or missilecategory==6)
if _track then
local missile={}
missile.active=true
missile.weapon=EventData.weapon
missile.missileType=_weapon
missile.missileRange=missilerange
missile.missileName=EventData.weapon:getName()
missile.shooterUnit=EventData.IniUnit
missile.shooterGroup=EventData.IniGroup
missile.shooterCoalition=EventData.IniUnit:GetCoalition()
missile.shooterName=EventData.IniUnitName
missile.shotTime=timer.getAbsTime()
missile.shotCoord=EventData.IniUnit:GetCoordinate()
missile.fuseDist=desc.fuseDist
missile.explosive=desc.warhead.explosiveMass or desc.warhead.shapedExplosiveMass
missile.targetOrig=missile.targetName
self:GetMissileTarget(missile)
self:I(FOX.lid..string.format("EVENT SHOT: Shooter=%s %s(%s) ==> Target=%s, fuse dist=%s, explosive=%s",
tostring(missile.shooterName),tostring(missile.missileType),tostring(missile.missileName),tostring(missile.targetName),tostring(missile.fuseDist),tostring(missile.explosive)))
if missile.targetPlayer or self:_IsProtected(missile.targetUnit)or missile.targetName=="unknown"then
table.insert(self.missiles,missile)
self:__MissileLaunch(0.1,missile)
end
end
end
function FOX:OnEventHit(EventData)
self:T({eventhit=EventData})
if EventData.Weapon==nil then
return
end
if EventData.IniUnit==nil then
return
end
if EventData.TgtUnit==nil then
return
end
local weapon=EventData.Weapon
local weaponname=weapon:getName()
for i,_missile in pairs(self.missiles)do
local missile=_missile
if missile.missileName==weaponname then
self:I(self.lid..string.format("WARNING: Missile %s (%s) hit target %s. Missile trainer target was %s.",missile.missileType,missile.missileName,EventData.TgtUnitName,missile.targetName))
self:I({missile=missile})
return
end
end
end
function FOX:_AddF10Commands(_unitName)
self:F(_unitName)
local _unit,playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and playername then
local group=_unit:GetGroup()
local gid=group:GetID()
if group and gid then
if not self.menuadded[gid]then
self.menuadded[gid]=true
local _rootPath=nil
if FOX.MenuF10Root then
_rootPath=FOX.MenuF10Root
else
if FOX.MenuF10[gid]==nil then
FOX.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid,"FOX")
end
_rootPath=FOX.MenuF10[gid]
end
missionCommands.addCommandForGroup(gid,"Destroy Missiles On/Off",_rootPath,self._ToggleDestroyMissiles,self,_unitName)
missionCommands.addCommandForGroup(gid,"Launch Alerts On/Off",_rootPath,self._ToggleLaunchAlert,self,_unitName)
missionCommands.addCommandForGroup(gid,"Mark Launch On/Off",_rootPath,self._ToggleLaunchMark,self,_unitName)
missionCommands.addCommandForGroup(gid,"My Status",_rootPath,self._MyStatus,self,_unitName)
end
else
self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.",_unitName or"unknown"))
end
else
self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.",_unitName or"unknown"))
end
end
function FOX:_MyStatus(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
local m,mtext=self:_GetTargetMissiles(playerData.name)
local text=string.format("Status of player %s:\n",playerData.name)
local safe=self:_CheckCoordSafe(playerData.unit:GetCoordinate())
text=text..string.format("Destroy missiles? %s\n",tostring(playerData.destroy))
text=text..string.format("Launch alert? %s\n",tostring(playerData.launchalert))
text=text..string.format("Launch marks? %s\n",tostring(playerData.marklaunch))
text=text..string.format("Am I safe? %s\n",tostring(safe))
text=text..string.format("Missiles defeated: %d\n",playerData.defeated)
text=text..string.format("Missiles destroyed: %d\n",playerData.dead)
text=text..string.format("Me target: %d\n%s",m,mtext)
MESSAGE:New(text,10,nil,true):ToClient(playerData.client)
end
end
end
function FOX:_GetTargetMissiles(playername)
local text=""
local n=0
for _,_missile in pairs(self.missiles)do
local missile=_missile
if missile.targetPlayer and missile.targetPlayer.name==playername then
n=n+1
text=text..string.format("Type %s: active %s\n",missile.missileType,tostring(missile.active))
end
end
return n,text
end
function FOX:_ToggleLaunchAlert(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.launchalert=not playerData.launchalert
local text=""
if playerData.launchalert==true then
text=string.format("%s, missile launch alerts are now ENABLED.",playerData.name)
else
text=string.format("%s, missile launch alerts are now DISABLED.",playerData.name)
end
MESSAGE:New(text,5):ToClient(playerData.client)
end
end
end
function FOX:_ToggleLaunchMark(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.marklaunch=not playerData.marklaunch
local text=""
if playerData.marklaunch==true then
text=string.format("%s, missile launch marks are now ENABLED.",playerData.name)
else
text=string.format("%s, missile launch marks are now DISABLED.",playerData.name)
end
MESSAGE:New(text,5):ToClient(playerData.client)
end
end
end
function FOX:_ToggleDestroyMissiles(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.destroy=not playerData.destroy
local text=""
if playerData.destroy==true then
text=string.format("%s, incoming missiles will be DESTROYED.",playerData.name)
else
text=string.format("%s, incoming missiles will NOT be DESTROYED.",playerData.name)
end
MESSAGE:New(text,5):ToClient(playerData.client)
end
end
end
function FOX:_DeadText()
local texts={}
texts[1]="You're dead!"
texts[2]="Meet your maker!"
texts[3]="Time to meet your maker!"
texts[4]="Well, I guess that was it!"
texts[5]="Bye, bye!"
texts[6]="Cheers buddy, was nice knowing you!"
local r=math.random(#texts)
return texts[r]
end
function FOX:_CheckCoordSafe(coord)
if#self.safezones==0 then
return true
end
for _,_zone in pairs(self.safezones)do
local zone=_zone
local inzone=zone:IsCoordinateInZone(coord)
if inzone then
return true
end
end
return false
end
function FOX:_CheckCoordLaunch(coord)
if#self.launchzones==0 then
return true
end
for _,_zone in pairs(self.launchzones)do
local zone=_zone
local inzone=zone:IsCoordinateInZone(coord)
if inzone then
return true
end
end
return false
end
function FOX:_GetWeapongHeading(weapon)
if weapon and weapon:isExist()then
local wp=weapon:getPosition()
local wph=math.atan2(wp.x.z,wp.x.x)
if wph<0 then
wph=wph+2*math.pi
end
wph=math.deg(wph)
return wph
end
return-1
end
function FOX:_SayNotchingHeadings(playerData,weapon)
if playerData and playerData.unit and playerData.unit:IsAlive()then
local nr,nl=self:_GetNotchingHeadings(weapon)
if nr and nl then
local text=string.format("Notching heading %03d° or %03d°",nr,nl)
MESSAGE:New(text,5,"FOX"):ToClient(playerData.client)
end
end
end
function FOX:_GetNotchingHeadings(weapon)
if weapon then
local hdg=self:_GetWeapongHeading(weapon)
local hdg1=hdg+90
if hdg1>360 then
hdg1=hdg1-360
end
local hdg2=hdg-90
if hdg2<0 then
hdg2=hdg2+360
end
return hdg1,hdg2
end
return nil,nil
end
function FOX:_GetPlayerFromUnitname(unitName)
for _,_player in pairs(self.players)do
local player=_player
if player.unitname==unitName then
return player
end
end
return nil
end
function FOX:_GetPlayerFromUnit(unit)
if unit and unit:IsAlive()then
local unitname=unit:GetName()
for _,_player in pairs(self.players)do
local player=_player
if player.unitname==unitname then
return player
end
end
end
return nil
end
function FOX:_GetPlayerUnitAndName(_unitName)
self:F2(_unitName)
if _unitName~=nil then
local DCSunit=Unit.getByName(_unitName)
if DCSunit then
local playername=DCSunit:getPlayerName()
local unit=UNIT:Find(DCSunit)
self:T2({DCSunit=DCSunit,unit=unit,playername=playername})
if DCSunit and unit and playername then
self:T(self.lid..string.format("Found DCS unit %s with player %s.",tostring(_unitName),tostring(playername)))
return unit,playername
end
end
end
return nil,nil
end
MANTIS={
ClassName="MANTIS",
name="mymantis",
SAM_Templates_Prefix="",
SAM_Group=nil,
EWR_Templates_Prefix="",
EWR_Group=nil,
Adv_EWR_Group=nil,
HQ_Template_CC="",
HQ_CC=nil,
SAM_Table={},
SAM_Table_Long={},
SAM_Table_Medium={},
SAM_Table_Short={},
lid="",
Detection=nil,
AWACS_Detection=nil,
debug=false,
checkradius=25000,
grouping=5000,
acceptrange=80000,
detectinterval=30,
engagerange=95,
autorelocate=false,
advanced=false,
adv_ratio=100,
adv_state=0,
AWACS_Prefix="",
advAwacs=false,
verbose=false,
awacsrange=250000,
Shorad=nil,
ShoradLink=false,
ShoradTime=600,
ShoradActDistance=25000,
UseEmOnOff=false,
TimeStamp=0,
state2flag=false,
SamStateTracker={},
DLink=false,
DLTimeStamp=0,
Padding=10,
SuppressedGroups={},
automode=true,
autoshorad=true,
ShoradGroupSet=nil,
}
MANTIS.AdvancedState={
GREEN=0,
AMBER=1,
RED=2,
}
MANTIS.SamType={
SHORT="Short",
MEDIUM="Medium",
LONG="Long",
}
MANTIS.SamData={
["Hawk"]={Range=44,Blindspot=0,Height=9,Type="Medium",Radar="Hawk"},
["NASAMS"]={Range=14,Blindspot=0,Height=3,Type="Short",Radar="NSAMS"},
["Patriot"]={Range=99,Blindspot=0,Height=9,Type="Long",Radar="Patriot"},
["Rapier"]={Range=6,Blindspot=0,Height=3,Type="Short",Radar="rapier"},
["SA-2"]={Range=40,Blindspot=7,Height=25,Type="Medium",Radar="S_75M_Volhov"},
["SA-3"]={Range=18,Blindspot=6,Height=18,Type="Short",Radar="5p73 s-125 ln"},
["SA-5"]={Range=250,Blindspot=7,Height=40,Type="Long",Radar="5N62V"},
["SA-6"]={Range=25,Blindspot=0,Height=8,Type="Medium",Radar="1S91"},
["SA-10"]={Range=119,Blindspot=0,Height=18,Type="Long",Radar="S-300PS 4"},
["SA-11"]={Range=35,Blindspot=0,Height=20,Type="Medium",Radar="SA-11"},
["Roland"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Roland"},
["HQ-7"]={Range=12,Blindspot=0,Height=3,Type="Short",Radar="HQ-7"},
["SA-9"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="Strela"},
["SA-8"]={Range=10,Blindspot=0,Height=5,Type="Short",Radar="Osa 9A33"},
["SA-19"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Tunguska"},
["SA-15"]={Range=11,Blindspot=0,Height=6,Type="Short",Radar="Tor 9A331"},
["SA-13"]={Range=5,Blindspot=0,Height=3,Type="Short",Radar="Strela"},
["Avenger"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="Avenger"},
["Chaparrel"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Chaparral"},
["Linebacker"]={Range=4,Blindspot=0,Height=3,Type="Short",Radar="Linebacker"},
["Silkworm"]={Range=90,Blindspot=1,Height=0.2,Type="Long",Radar="Silkworm"},
["SA-10B"]={Range=75,Blindspot=0,Height=18,Type="Medium",Radar="SA-10B"},
["SA-17"]={Range=50,Blindspot=3,Height=30,Type="Medium",Radar="SA-17"},
["SA-20A"]={Range=150,Blindspot=5,Height=27,Type="Long",Radar="S-300PMU1"},
["SA-20B"]={Range=200,Blindspot=4,Height=27,Type="Long",Radar="S-300PMU2"},
["HQ-2"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"},
}
MANTIS.SamDataHDS={
["SA-2 HDS"]={Range=56,Blindspot=7,Height=30,Type="Medium",Radar="V759"},
["SA-3 HDS"]={Range=20,Blindspot=6,Height=30,Type="Short",Radar="V-601P"},
["SA-10C HDS 2"]={Range=90,Blindspot=5,Height=25,Type="Long",Radar="5P85DE ln"},
["SA-10C HDS 1"]={Range=90,Blindspot=5,Height=25,Type="Long",Radar="5P85CE ln"},
["SA-12 HDS 2"]={Range=100,Blindspot=10,Height=25,Type="Long",Radar="S-300V 9A82 l"},
["SA-12 HDS 1"]={Range=75,Blindspot=1,Height=25,Type="Long",Radar="S-300V 9A83 l"},
["SA-23 HDS 2"]={Range=200,Blindspot=5,Height=37,Type="Long",Radar="S-300VM 9A82ME"},
["SA-23 HDS 1"]={Range=100,Blindspot=1,Height=50,Type="Long",Radar="S-300VM 9A83ME"},
["HQ-2 HDS"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"},
}
MANTIS.SamDataSMA={
["RBS98M SMA"]={Range=20,Blindspot=0,Height=8,Type="Short",Radar="RBS-98"},
["RBS70 SMA"]={Range=8,Blindspot=0,Height=5.5,Type="Short",Radar="RBS-70"},
["RBS70M SMA"]={Range=8,Blindspot=0,Height=5.5,Type="Short",Radar="BV410_RBS70"},
["RBS90 SMA"]={Range=8,Blindspot=0,Height=5.5,Type="Short",Radar="RBS-90"},
["RBS90M SMA"]={Range=8,Blindspot=0,Height=5.5,Type="Short",Radar="BV410_RBS90"},
["RBS103A SMA"]={Range=150,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_Rb103A"},
["RBS103B SMA"]={Range=35,Blindspot=0,Height=36,Type="Medium",Radar="LvS-103_Lavett103_Rb103B"},
["RBS103AM SMA"]={Range=150,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103A"},
["RBS103BM SMA"]={Range=35,Blindspot=0,Height=36,Type="Medium",Radar="LvS-103_Lavett103_HX_Rb103B"},
["Lvkv9040M SMA"]={Range=4,Blindspot=0,Height=2.5,Type="Short",Radar="LvKv9040"},
}
do
function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs,EmOnOff,Padding)
self.SAM_Templates_Prefix=samprefix or"Red SAM"
self.EWR_Templates_Prefix=ewrprefix or"Red EWR"
self.HQ_Template_CC=hq or nil
self.Coalition=coalition or"red"
self.SAM_Table={}
self.SAM_Table_Long={}
self.SAM_Table_Medium={}
self.SAM_Table_Short={}
self.dynamic=dynamic or false
self.checkradius=25000
self.grouping=5000
self.acceptrange=80000
self.detectinterval=30
self.engagerange=95
self.autorelocate=false
self.autorelocateunits={HQ=false,EWR=false}
self.advanced=false
self.adv_ratio=100
self.adv_state=0
self.verbose=false
self.Adv_EWR_Group=nil
self.AWACS_Prefix=awacs or nil
self.awacsrange=250000
self.Shorad=nil
self.ShoradLink=false
self.ShoradTime=600
self.ShoradActDistance=25000
self.TimeStamp=timer.getAbsTime()
self.relointerval=math.random(1800,3600)
self.state2flag=false
self.SamStateTracker={}
self.DLink=false
self.Padding=Padding or 10
self.SuppressedGroups={}
self.automode=true
self.radiusscale={}
self.radiusscale[MANTIS.SamType.LONG]=1.1
self.radiusscale[MANTIS.SamType.MEDIUM]=1.2
self.radiusscale[MANTIS.SamType.SHORT]=1.3
self.usezones=false
self.AcceptZones={}
self.RejectZones={}
self.ConflictZones={}
self.maxlongrange=1
self.maxmidrange=2
self.maxshortrange=2
self.maxclassic=6
self.autoshorad=true
self.ShoradGroupSet=SET_GROUP:New()
self.UseEmOnOff=true
if EmOnOff==false then
self.UseEmOnOff=false
end
if type(awacs)=="string"then
self.advAwacs=true
else
self.advAwacs=false
end
local self=BASE:Inherit(self,FSM:New())
self.lid=string.format("MANTIS %s | ",self.name)
if self.debug then
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
end
self.ewr_templates={}
if type(samprefix)~="table"then
self.SAM_Templates_Prefix={samprefix}
end
if type(ewrprefix)~="table"then
self.EWR_Templates_Prefix={ewrprefix}
end
for _,_group in pairs(self.SAM_Templates_Prefix)do
table.insert(self.ewr_templates,_group)
end
for _,_group in pairs(self.EWR_Templates_Prefix)do
table.insert(self.ewr_templates,_group)
end
if self.advAwacs then
table.insert(self.ewr_templates,awacs)
end
self:T({self.ewr_templates})
if self.dynamic then
self.SAM_Group=SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart()
self.EWR_Group=SET_GROUP:New():FilterPrefixes(self.ewr_templates):FilterCoalitions(self.Coalition):FilterStart()
else
self.SAM_Group=SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce()
self.EWR_Group=SET_GROUP:New():FilterPrefixes(self.ewr_templates):FilterCoalitions(self.Coalition):FilterOnce()
end
if self.HQ_Template_CC then
self.HQ_CC=GROUP:FindByName(self.HQ_Template_CC)
end
self.version="0.8.9"
self:I(string.format("***** Starting MANTIS Version %s *****",self.version))
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","Relocating","*")
self:AddTransition("*","GreenState","*")
self:AddTransition("*","RedState","*")
self:AddTransition("*","AdvStateChange","*")
self:AddTransition("*","ShoradActivated","*")
self:AddTransition("*","SeadSuppressionStart","*")
self:AddTransition("*","SeadSuppressionEnd","*")
self:AddTransition("*","SeadSuppressionPlanned","*")
self:AddTransition("*","Stop","Stopped")
return self
end
function MANTIS:_GetSAMTable()
self:T(self.lid.."GetSAMTable")
return self.SAM_Table
end
function MANTIS:_SetSAMTable(table)
self:T(self.lid.."SetSAMTable")
self.SAM_Table=table
return self
end
function MANTIS:SetEWRGrouping(radius)
self:T(self.lid.."SetEWRGrouping")
local radius=radius or 5000
self.grouping=radius
return self
end
function MANTIS:AddZones(AcceptZones,RejectZones,ConflictZones)
self:T(self.lid.."AddZones")
self.AcceptZones=AcceptZones or{}
self.RejectZones=RejectZones or{}
self.ConflictZones=ConflictZones or{}
if#AcceptZones>0 or#RejectZones>0 or#ConflictZones>0 then
self.usezones=true
end
return self
end
function MANTIS:SetEWRRange(radius)
self:T(self.lid.."SetEWRRange")
return self
end
function MANTIS:SetSAMRadius(radius)
self:T(self.lid.."SetSAMRadius")
local radius=radius or 25000
self.checkradius=radius
return self
end
function MANTIS:SetSAMRange(range)
self:T(self.lid.."SetSAMRange")
local range=range or 95
if range<0 or range>100 then
range=95
end
self.engagerange=range
return self
end
function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic)
self:T(self.lid.."SetMaxActiveSAMs")
self.maxclassic=Classic or 6
self.maxlongrange=Long or 1
self.maxmidrange=Mid or 2
self.maxshortrange=Short or 2
return self
end
function MANTIS:SetNewSAMRangeWhileRunning(range)
self:T(self.lid.."SetNewSAMRangeWhileRunning")
local range=range or 95
if range<0 or range>100 then
range=95
end
self.engagerange=range
self:_RefreshSAMTable()
self.mysead.EngagementRange=range
return self
end
function MANTIS:Debug(onoff)
self:T(self.lid.."SetDebug")
local onoff=onoff or false
self.debug=onoff
if onoff then
BASE:TraceOn()
BASE:TraceClass("MANTIS")
BASE:TraceLevel(1)
else
BASE:TraceOff()
end
return self
end
function MANTIS:GetCommandCenter()
self:T(self.lid.."GetCommandCenter")
if self.HQ_CC then
return self.HQ_CC
else
return nil
end
end
function MANTIS:SetAwacs(prefix)
self:T(self.lid.."SetAwacs")
if prefix~=nil then
if type(prefix)=="string"then
self.AWACS_Prefix=prefix
self.advAwacs=true
end
end
return self
end
function MANTIS:SetAwacsRange(range)
self:T(self.lid.."SetAwacsRange")
local range=range or 250000
self.awacsrange=range
return self
end
function MANTIS:SetCommandCenter(group)
self:T(self.lid.."SetCommandCenter")
local group=group or nil
if group~=nil then
if type(group)=="string"then
self.HQ_CC=GROUP:FindByName(group)
self.HQ_Template_CC=group
else
self.HQ_CC=group
self.HQ_Template_CC=group:GetName()
end
end
return self
end
function MANTIS:SetDetectInterval(interval)
self:T(self.lid.."SetDetectInterval")
local interval=interval or 30
self.detectinterval=interval
return self
end
function MANTIS:SetAdvancedMode(onoff,ratio)
self:T(self.lid.."SetAdvancedMode")
local onoff=onoff or false
local ratio=ratio or 100
if(type(self.HQ_Template_CC)=="string")and onoff and self.dynamic then
self.adv_ratio=ratio
self.advanced=true
self.adv_state=0
self.Adv_EWR_Group=SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart()
self:I(string.format("***** Starting Advanced Mode MANTIS Version %s *****",self.version))
else
local text=self.lid.." Advanced Mode requires a HQ and dynamic to be set. Revisit your MANTIS:New() statement to add both."
local m=MESSAGE:New(text,10,"MANTIS",true):ToAll()
self:E(text)
end
return self
end
function MANTIS:SetUsingEmOnOff(switch)
self:T(self.lid.."SetUsingEmOnOff")
self.UseEmOnOff=switch or false
return self
end
function MANTIS:SetUsingDLink(DLink)
self:T(self.lid.."SetUsingDLink")
self.DLink=true
self.Detection=DLink
self.DLTimeStamp=timer.getAbsTime()
return self
end
function MANTIS:_CheckHQState()
self:T(self.lid.."CheckHQState")
local text=self.lid.." Checking HQ State"
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(text)end
if self.advanced then
local hq=self.HQ_Template_CC
local hqgrp=GROUP:FindByName(hq)
if hqgrp then
if hqgrp:IsAlive()then
return true
else
return false
end
end
end
return self
end
function MANTIS:_CheckEWRState()
self:T(self.lid.."CheckEWRState")
local text=self.lid.." Checking EWR State"
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(text)end
if self.advanced then
local EWR_Group=self.Adv_EWR_Group
local nalive=EWR_Group:CountAlive()
if self.advAwacs then
local awacs=GROUP:FindByName(self.AWACS_Prefix)
if awacs~=nil then
if awacs:IsAlive()then
nalive=nalive+1
end
end
end
if nalive>0 then
return true
else
return false
end
end
return self
end
function MANTIS:_CalcAdvState()
self:T(self.lid.."CalcAdvState")
local m=MESSAGE:New(self.lid.." Calculating Advanced State",10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(self.lid.." Calculating Advanced State")end
local currstate=self.adv_state
local EWR_State=self:_CheckEWRState()
local HQ_State=self:_CheckHQState()
if EWR_State and HQ_State then
self.adv_state=0
elseif EWR_State or HQ_State then
self.adv_state=1
else
self.adv_state=2
end
local interval=self.detectinterval
local ratio=self.adv_ratio/100
ratio=ratio*self.adv_state
local newinterval=interval+(interval*ratio)
if self.debug or self.verbose then
local text=self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d",currstate,self.adv_state,newinterval)
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(text)end
end
return newinterval,currstate
end
function MANTIS:SetAutoRelocate(hq,ewr)
self:T(self.lid.."SetAutoRelocate")
local hqrel=hq or false
local ewrel=ewr or false
if hqrel or ewrel then
self.autorelocate=true
self.autorelocateunits={HQ=hqrel,EWR=ewrel}
end
return self
end
function MANTIS:_RelocateGroups()
self:T(self.lid.."RelocateGroups")
local text=self.lid.." Relocating Groups"
local m=MESSAGE:New(text,10,"MANTIS",true):ToAllIf(self.debug)
if self.verbose then self:I(text)end
if self.autorelocate then
local HQGroup=self.HQ_CC
if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive()then
local _hqgrp=self.HQ_CC
local text=self.lid.." Relocating HQ"
_hqgrp:RelocateGroundRandomInRadius(20,500,true,true)
end
if self.autorelocateunits.EWR then
local EWR_GRP=SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce()
local EWR_Grps=EWR_GRP.Set
for _,_grp in pairs(EWR_Grps)do
if _grp:IsAlive()and _grp:IsGround()then
local text=self.lid.." Relocating EWR ".._grp:GetName()
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(text)end
_grp:RelocateGroundRandomInRadius(20,500,true,true)
end
end
end
end
return self
end
function MANTIS:_CheckCoordinateInZones(coord)
self:T(self.lid.."_CheckCoordinateInZones")
local inzone=false
if#self.AcceptZones>0 then
for _,_zone in pairs(self.AcceptZones)do
local zone=_zone
if zone:IsCoordinateInZone(coord)then
inzone=true
self:T(self.lid.."Target coord in Accept Zone!")
break
end
end
end
if#self.RejectZones>0 and inzone then
for _,_zone in pairs(self.RejectZones)do
local zone=_zone
if zone:IsCoordinateInZone(coord)then
inzone=false
self:T(self.lid.."Target coord in Reject Zone!")
break
end
end
end
if#self.ConflictZones>0 and not inzone then
for _,_zone in pairs(self.ConflictZones)do
local zone=_zone
if zone:IsCoordinateInZone(coord)then
inzone=true
self:T(self.lid.."Target coord in Conflict Zone!")
break
end
end
end
return inzone
end
function MANTIS:_PreFilterHeight(height)
self:T(self.lid.."_PreFilterHeight")
local set={}
local dlink=self.Detection
local detectedgroups=dlink:GetContactTable()
for _,_contact in pairs(detectedgroups)do
local contact=_contact
local grp=contact.group
if grp:IsAlive()then
if grp:GetHeight(true)<height then
local coord=grp:GetCoordinate()
table.insert(set,coord)
end
end
end
return set
end
function MANTIS:_CheckObjectInZone(dectset,samcoordinate,radius,height,dlink)
self:T(self.lid.."_CheckObjectInZone")
local rad=radius or self.checkradius
local set=dectset
if dlink then
set=self:_PreFilterHeight(height)
end
for _,_coord in pairs(set)do
local coord=_coord
local targetdistance=samcoordinate:DistanceFromPointVec2(coord)
if not targetdistance then
targetdistance=samcoordinate:Get2DDistance(coord)
end
local zonecheck=true
if self.usezones then
zonecheck=self:_CheckCoordinateInZones(coord)
end
if self.verbose and self.debug then
local dectstring=coord:ToStringLLDMS()
local samstring=samcoordinate:ToStringLLDMS()
local inrange="false"
if targetdistance<=rad then
inrange="true"
end
local text=string.format("Checking SAM at %s | Targetdist %d | Rad %d | Inrange %s",samstring,targetdistance,rad,inrange)
local m=MESSAGE:New(text,10,"Check"):ToAllIf(self.debug)
self:T(self.lid..text)
end
if targetdistance<=rad and zonecheck then
return true,targetdistance
end
end
return false,0
end
function MANTIS:StartDetection()
self:T(self.lid.."Starting Detection")
local groupset=self.EWR_Group
local grouping=self.grouping or 5000
local interval=self.detectinterval or 20
local MANTISdetection=DETECTION_AREAS:New(groupset,grouping)
MANTISdetection:FilterCategories({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
MANTISdetection:SetRefreshTimeInterval(interval)
MANTISdetection:__Start(2)
return MANTISdetection
end
function MANTIS:StartIntelDetection()
self:T(self.lid.."Starting Intel Detection")
local groupset=self.EWR_Group
local samset=self.SAM_Group
self.intelset={}
local IntelOne=INTEL:New(groupset,self.Coalition,self.name.." IntelOne")
IntelOne:Start()
local IntelTwo=INTEL:New(samset,self.Coalition,self.name.." IntelTwo")
IntelTwo:Start()
local IntelDlink=INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,300)
IntelDlink:__Start(1)
self:SetUsingDLink(IntelDlink)
table.insert(self.intelset,IntelOne)
table.insert(self.intelset,IntelTwo)
return IntelDlink
end
function MANTIS:StartAwacsDetection()
self:T(self.lid.."Starting Awacs Detection")
local group=self.AWACS_Prefix
local groupset=SET_GROUP:New():FilterPrefixes(group):FilterCoalitions(self.Coalition):FilterStart()
local grouping=self.grouping or 5000
local interval=self.detectinterval or 60
local MANTISAwacs=DETECTION_AREAS:New(groupset,grouping)
MANTISAwacs:FilterCategories({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
MANTISAwacs:SetAcceptRange(self.awacsrange)
MANTISAwacs:SetRefreshTimeInterval(interval)
MANTISAwacs:Start()
return MANTISAwacs
end
function MANTIS:_GetSAMDataFromUnits(grpname,mod,sma)
self:T(self.lid.."_GetSAMRangeFromUnits")
local found=false
local range=self.checkradius
local height=3000
local type=MANTIS.SamType.MEDIUM
local radiusscale=self.radiusscale[type]
local blind=0
local group=GROUP:FindByName(grpname)
local units=group:GetUnits()
local SAMData=self.SamData
if mod then
SAMData=self.SamDataHDS
elseif sma then
SAMData=self.SamDataSMA
end
for _,_unit in pairs(units)do
local unit=_unit
local type=string.lower(unit:GetTypeName())
for idx,entry in pairs(SAMData)do
local _entry=entry
local _radar=string.lower(_entry.Radar)
if string.find(type,_radar,1,true)then
type=_entry.Type
radiusscale=self.radiusscale[type]
range=_entry.Range*1000*radiusscale
height=_entry.Height*1000
blind=_entry.Blindspot*100
found=true
break
end
end
if found then break end
end
if not found then
self:E(self.lid..string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
end
return range,height,type,blind
end
function MANTIS:_GetSAMRange(grpname)
self:T(self.lid.."_GetSAMRange")
local range=self.checkradius
local height=3000
local type=MANTIS.SamType.MEDIUM
local radiusscale=self.radiusscale[type]
local blind=0
local found=false
local HDSmod=false
local SMAMod=false
if string.find(grpname,"HDS",1,true)then
HDSmod=true
elseif string.find(grpname,"SMA",1,true)then
SMAMod=true
end
if self.automode then
for idx,entry in pairs(self.SamData)do
if string.find(grpname,idx,1,true)then
local _entry=entry
type=_entry.Type
radiusscale=self.radiusscale[type]
range=_entry.Range*1000*radiusscale
height=_entry.Height*1000
blind=_entry.Blindspot
found=true
break
end
end
end
if(not found and self.automode)or HDSmod or SMAMod then
range,height,type=self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod)
elseif not found then
self:E(self.lid..string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
end
return range,height,type,blind
end
function MANTIS:SetSAMStartState()
self:T(self.lid.."Setting SAM Start States")
local SAM_SET=self.SAM_Group
local SAM_Grps=SAM_SET.Set
local SAM_Tbl={}
local SAM_Tbl_lg={}
local SAM_Tbl_md={}
local SAM_Tbl_sh={}
local SEAD_Grps={}
local engagerange=self.engagerange
for _i,_group in pairs(SAM_Grps)do
if _group:IsGround()and _group:IsAlive()then
local group=_group
if self.UseEmOnOff then
group:OptionAlarmStateRed()
group:EnableEmission(false)
else
group:OptionAlarmStateGreen()
end
group:OptionEngageRange(engagerange)
local grpname=group:GetName()
local grpcoord=group:GetCoordinate()
local grprange,grpheight,type,blind=self:_GetSAMRange(grpname)
table.insert(SAM_Tbl,{grpname,grpcoord,grprange,grpheight,blind})
if type==MANTIS.SamType.LONG then
table.insert(SAM_Tbl_lg,{grpname,grpcoord,grprange,grpheight,blind})
table.insert(SEAD_Grps,grpname)
elseif type==MANTIS.SamType.MEDIUM then
table.insert(SAM_Tbl_md,{grpname,grpcoord,grprange,grpheight,blind})
table.insert(SEAD_Grps,grpname)
elseif type==MANTIS.SamType.SHORT then
table.insert(SAM_Tbl_sh,{grpname,grpcoord,grprange,grpheight,blind})
self.ShoradGroupSet:Add(grpname,group)
if not self.autoshorad then
table.insert(SEAD_Grps,grpname)
end
end
self.SamStateTracker[grpname]="GREEN"
end
end
self.SAM_Table=SAM_Tbl
self.SAM_Table_Long=SAM_Tbl_lg
self.SAM_Table_Medium=SAM_Tbl_md
self.SAM_Table_Short=SAM_Tbl_sh
local mysead=SEAD:New(SEAD_Grps,self.Padding)
mysead:SetEngagementRange(engagerange)
mysead:AddCallBack(self)
if self.UseEmOnOff then
mysead:SwitchEmissions(true)
end
self.mysead=mysead
return self
end
function MANTIS:_RefreshSAMTable()
self:T(self.lid.."RefreshSAMTable")
local SAM_SET=self.SAM_Group
local SAM_Grps=SAM_SET.Set
local SAM_Tbl={}
local SAM_Tbl_lg={}
local SAM_Tbl_md={}
local SAM_Tbl_sh={}
local SEAD_Grps={}
local engagerange=self.engagerange
for _i,_group in pairs(SAM_Grps)do
local group=_group
group:OptionEngageRange(engagerange)
if group:IsGround()and group:IsAlive()then
local grpname=group:GetName()
local grpcoord=group:GetCoordinate()
local grprange,grpheight,type,blind=self:_GetSAMRange(grpname)
table.insert(SAM_Tbl,{grpname,grpcoord,grprange,grpheight,blind})
table.insert(SEAD_Grps,grpname)
if type==MANTIS.SamType.LONG then
table.insert(SAM_Tbl_lg,{grpname,grpcoord,grprange,grpheight,blind})
elseif type==MANTIS.SamType.MEDIUM then
table.insert(SAM_Tbl_md,{grpname,grpcoord,grprange,grpheight,blind})
elseif type==MANTIS.SamType.SHORT then
table.insert(SAM_Tbl_sh,{grpname,grpcoord,grprange,grpheight,blind})
self.ShoradGroupSet:Add(grpname,group)
if self.autoshorad then
self.Shorad.Groupset=self.ShoradGroupSet
end
end
end
end
self.SAM_Table=SAM_Tbl
self.SAM_Table_Long=SAM_Tbl_lg
self.SAM_Table_Medium=SAM_Tbl_md
self.SAM_Table_Short=SAM_Tbl_sh
if self.mysead~=nil then
local mysead=self.mysead
mysead:UpdateSet(SEAD_Grps)
end
return self
end
function MANTIS:AddShorad(Shorad,Shoradtime)
self:T(self.lid.."AddShorad")
local Shorad=Shorad or nil
local ShoradTime=Shoradtime or 600
local ShoradLink=true
if Shorad:IsInstanceOf("SHORAD")then
self.ShoradLink=ShoradLink
self.Shorad=Shorad
self.ShoradTime=Shoradtime
end
return self
end
function MANTIS:RemoveShorad()
self:T(self.lid.."RemoveShorad")
self.ShoradLink=false
return self
end
function MANTIS:_CheckLoop(samset,detset,dlink,limit)
self:T(self.lid.."CheckLoop "..#detset.." Coordinates")
local switchedon=0
for _,_data in pairs(samset)do
local samcoordinate=_data[2]
local name=_data[1]
local radius=_data[3]
local height=_data[4]
local blind=_data[5]*1.25+1
local samgroup=GROUP:FindByName(name)
local IsInZone,Distance=self:_CheckObjectInZone(detset,samcoordinate,radius,height,dlink)
local suppressed=self.SuppressedGroups[name]or false
local activeshorad=self.Shorad.ActiveGroups[name]or false
if IsInZone and not suppressed and not activeshorad then
if samgroup:IsAlive()then
local switch=false
if self.UseEmOnOff and switchedon<limit then
samgroup:EnableEmission(true)
switchedon=switchedon+1
switch=true
elseif(not self.UseEmOnOff)and switchedon<limit then
samgroup:OptionAlarmStateRed()
switchedon=switchedon+1
switch=true
end
if self.SamStateTracker[name]~="RED"and switch then
self:__RedState(1,samgroup)
self.SamStateTracker[name]="RED"
end
if self.ShoradLink and(Distance<self.ShoradActDistance or Distance<blind)then
local Shorad=self.Shorad
local radius=self.checkradius
local ontime=self.ShoradTime
Shorad:WakeUpShorad(name,radius,ontime)
self:__ShoradActivated(1,name,radius,ontime)
end
if(self.debug or self.verbose)and switch then
local text=string.format("SAM %s in alarm state RED!",name)
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(self.lid..text)end
end
end
else
if samgroup:IsAlive()and not suppressed and not activeshorad then
if self.UseEmOnOff then
samgroup:EnableEmission(false)
else
samgroup:OptionAlarmStateGreen()
end
if self.SamStateTracker[name]~="GREEN"then
self:__GreenState(1,samgroup)
self.SamStateTracker[name]="GREEN"
end
if self.debug or self.verbose then
local text=string.format("SAM %s in alarm state GREEN!",name)
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(self.lid..text)end
end
end
end
end
return self
end
function MANTIS:_Check(detection,dlink)
self:T(self.lid.."Check")
local detset=detection:GetDetectedItemCoordinates()
local rand=math.random(1,100)
if rand>65 then
self:_RefreshSAMTable()
end
if self.automode then
local samset=self.SAM_Table_Long
self:_CheckLoop(samset,detset,dlink,self.maxlongrange)
local samset=self.SAM_Table_Medium
self:_CheckLoop(samset,detset,dlink,self.maxmidrange)
local samset=self.SAM_Table_Short
self:_CheckLoop(samset,detset,dlink,self.maxshortrange)
else
local samset=self:_GetSAMTable()
self:_CheckLoop(samset,detset,dlink,self.maxclassic)
end
return self
end
function MANTIS:_Relocate()
self:T(self.lid.."Relocate")
self:_RelocateGroups()
return self
end
function MANTIS:_CheckAdvState()
self:T(self.lid.."CheckAdvSate")
local interval,oldstate=self:_CalcAdvState()
local newstate=self.adv_state
if newstate~=oldstate then
self:__AdvStateChange(1,oldstate,newstate,interval)
if newstate==2 then
self.state2flag=true
local samset=self:_GetSAMTable()
for _,_data in pairs(samset)do
local name=_data[1]
local samgroup=GROUP:FindByName(name)
if samgroup:IsAlive()then
if self.UseEmOnOff then
samgroup:EnableEmission(true)
else
samgroup:OptionAlarmStateRed()
end
end
end
elseif newstate<=1 then
self.detectinterval=interval
self.state2flag=false
end
end
return self
end
function MANTIS:_CheckDLinkState()
self:T(self.lid.."_CheckDLinkState")
local dlink=self.Detection
local TS=timer.getAbsTime()
if not dlink:Is("Running")and(TS-self.DLTimeStamp>29)then
self.DLink=false
self.Detection=self:StartDetection()
self:I(self.lid.."Intel DLink not running - switching back to single detection!")
end
end
function MANTIS:onafterStart(From,Event,To)
self:T({From,Event,To})
self:T(self.lid.."Starting MANTIS")
self:SetSAMStartState()
if not INTEL then
self.Detection=self:StartDetection()
else
self.Detection=self:StartIntelDetection()
end
if self.autoshorad then
self.Shorad=SHORAD:New(self.name.."-SHORAD",self.name.."-SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff)
self.Shorad:SetDefenseLimits(80,95)
self.ShoradLink=true
self.Shorad.Groupset=self.ShoradGroupSet
end
self:__Status(-math.random(1,10))
return self
end
function MANTIS:onbeforeStatus(From,Event,To)
self:T({From,Event,To})
if not self.state2flag then
self:_Check(self.Detection,self.DLink)
end
if self.autorelocate then
local relointerval=self.relointerval
local thistime=timer.getAbsTime()
local timepassed=thistime-self.TimeStamp
local halfintv=math.floor(timepassed/relointerval)
if halfintv>=1 then
self.TimeStamp=timer.getAbsTime()
self:_Relocate()
self:__Relocating(1)
end
end
if self.advanced then
self:_CheckAdvState()
end
if self.DLink then
self:_CheckDLinkState()
end
return self
end
function MANTIS:onafterStatus(From,Event,To)
self:T({From,Event,To})
if self.debug and self.verbose then
self:I(self.lid.."Status Report")
for _name,_state in pairs(self.SamStateTracker)do
self:I(string.format("Site %s\tStatus %s",_name,_state))
end
end
local interval=self.detectinterval*-1
self:__Status(interval)
return self
end
function MANTIS:onafterStop(From,Event,To)
self:T({From,Event,To})
return self
end
function MANTIS:onafterRelocating(From,Event,To)
self:T({From,Event,To})
return self
end
function MANTIS:onafterGreenState(From,Event,To,Group)
self:T({From,Event,To,Group:GetName()})
return self
end
function MANTIS:onafterRedState(From,Event,To,Group)
self:T({From,Event,To,Group:GetName()})
return self
end
function MANTIS:onafterAdvStateChange(From,Event,To,Oldstate,Newstate,Interval)
self:T({From,Event,To,Oldstate,Newstate,Interval})
return self
end
function MANTIS:onafterShoradActivated(From,Event,To,Name,Radius,Ontime)
self:T({From,Event,To,Name,Radius,Ontime})
return self
end
function MANTIS:onafterSeadSuppressionStart(From,Event,To,Group,Name,Attacker)
self:T({From,Event,To,Name})
self.SuppressedGroups[Name]=true
if self.ShoradLink then
local Shorad=self.Shorad
local radius=self.checkradius
local ontime=self.ShoradTime
Shorad:WakeUpShorad(Name,radius,ontime)
self:__ShoradActivated(1,Name,radius,ontime)
end
return self
end
function MANTIS:onafterSeadSuppressionEnd(From,Event,To,Group,Name)
self:T({From,Event,To,Name})
self.SuppressedGroups[Name]=false
return self
end
function MANTIS:onafterSeadSuppressionPlanned(From,Event,To,Group,Name,SuppressionStartTime,SuppressionEndTime,Attacker)
self:T({From,Event,To,Name})
return self
end
end
SHORAD={
ClassName="SHORAD",
name="MyShorad",
debug=false,
Prefixes="",
Radius=20000,
Groupset=nil,
Samset=nil,
Coalition=nil,
ActiveTimer=600,
ActiveGroups={},
lid="",
DefendHarms=true,
DefendMavs=true,
DefenseLowProb=70,
DefenseHighProb=90,
UseEmOnOff=false,
}
do
SHORAD.Harms={
["AGM_88"]="AGM_88",
["AGM_45"]="AGM_45",
["AGM_122"]="AGM_122",
["AGM_84"]="AGM_84",
["AGM_45"]="AGM_45",
["ALARM"]="ALARM",
["LD-10"]="LD-10",
["X_58"]="X_58",
["X_28"]="X_28",
["X_25"]="X_25",
["X_31"]="X_31",
["Kh25"]="Kh25",
}
SHORAD.Mavs={
["AGM"]="AGM",
["C-701"]="C-701",
["Kh25"]="Kh25",
["Kh29"]="Kh29",
["Kh31"]="Kh31",
["Kh66"]="Kh66",
}
function SHORAD:New(Name,ShoradPrefix,Samset,Radius,ActiveTimer,Coalition,UseEmOnOff)
local self=BASE:Inherit(self,FSM:New())
self:T({Name,ShoradPrefix,Samset,Radius,ActiveTimer,Coalition})
local GroupSet=SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart()
self.name=Name or"MyShorad"
self.Prefixes=ShoradPrefix or"SAM SHORAD"
self.Radius=Radius or 20000
self.Coalition=Coalition or"blue"
self.Samset=Samset or GroupSet
self.ActiveTimer=ActiveTimer or 600
self.ActiveGroups={}
self.Groupset=GroupSet
self.DefendHarms=true
self.DefendMavs=true
self.DefenseLowProb=70
self.DefenseHighProb=90
self.UseEmOnOff=UseEmOnOff or false
self:I("*** SHORAD - Started Version 0.3.1")
self.lid=string.format("SHORAD %s | ",self.name)
self:_InitState()
self:HandleEvent(EVENTS.Shot,self.HandleEventShot)
self:SetStartState("Running")
self:AddTransition("*","WakeUpShorad","*")
self:AddTransition("*","CalculateHitZone","*")
return self
end
function SHORAD:_InitState()
self:T(self.lid.." _InitState")
local table={}
local set=self.Groupset
self:T({set=set})
local aliveset=set:GetAliveSet()
for _,_group in pairs(aliveset)do
if self.UseEmOnOff then
_group:EnableEmission(false)
_group:OptionAlarmStateRed()
else
_group:OptionAlarmStateGreen()
end
_group:OptionDisperseOnAttack(30)
end
for i=1,100 do
math.random()
end
return self
end
function SHORAD:SwitchDebug(onoff)
self:T({onoff})
if onoff then
self:SwitchDebugOn()
else
self.SwitchDebugOff()
end
return self
end
function SHORAD:SwitchDebugOn()
self.debug=true
BASE:TraceOn()
BASE:TraceClass("SHORAD")
return self
end
function SHORAD:SwitchDebugOff()
self.debug=false
BASE:TraceOff()
return self
end
function SHORAD:SwitchHARMDefense(onoff)
self:T({onoff})
local onoff=onoff or true
self.DefendHarms=onoff
return self
end
function SHORAD:SwitchAGMDefense(onoff)
self:T({onoff})
local onoff=onoff or true
self.DefendMavs=onoff
return self
end
function SHORAD:SetDefenseLimits(low,high)
self:T({low,high})
local low=low or 70
local high=high or 90
if(low<0)or(low>100)or(low>high)then
low=70
end
if(high<0)or(high>100)or(high<low)then
high=90
end
self.DefenseLowProb=low
self.DefenseHighProb=high
return self
end
function SHORAD:SetActiveTimer(seconds)
self:T(self.lid.." SetActiveTimer")
local timer=seconds or 600
if timer<0 then
timer=600
end
self.ActiveTimer=timer
return self
end
function SHORAD:SetDefenseRadius(meters)
self:T(self.lid.." SetDefenseRadius")
local radius=meters or 20000
if radius<0 then
radius=20000
end
self.Radius=radius
return self
end
function SHORAD:SetUsingEmOnOff(switch)
self:T(self.lid.." SetUsingEmOnOff")
self.UseEmOnOff=switch or false
return self
end
function SHORAD:_CheckHarms(WeaponName)
self:T(self.lid.." _CheckHarms")
self:T({WeaponName})
local hit=false
if self.DefendHarms then
for _,_name in pairs(SHORAD.Harms)do
if string.find(WeaponName,_name,1,true)then hit=true end
end
end
return hit
end
function SHORAD:_CheckMavs(WeaponName)
self:T(self.lid.." _CheckMavs")
self:T({WeaponName})
local hit=false
if self.DefendMavs then
for _,_name in pairs(SHORAD.Mavs)do
if string.find(WeaponName,_name,1,true)then hit=true end
end
end
return hit
end
function SHORAD:_CheckCoalition(Coalition)
self:T(self.lid.." _CheckCoalition")
local owncoalition=self.Coalition
local othercoalition=""
if Coalition==0 then
othercoalition="neutral"
elseif Coalition==1 then
othercoalition="red"
else
othercoalition="blue"
end
self:T({owncoalition=owncoalition,othercoalition=othercoalition})
if owncoalition~=othercoalition then
return true
else
return false
end
end
function SHORAD:_CheckShotAtShorad(TargetGroupName)
self:T(self.lid.." _CheckShotAtShorad")
local tgtgrp=TargetGroupName
local shorad=self.Groupset
local shoradset=shorad:GetAliveSet()
local returnname=false
for _,_groups in pairs(shoradset)do
local groupname=_groups:GetName()
if string.find(groupname,tgtgrp,1,true)then
returnname=true
end
end
return returnname
end
function SHORAD:_CheckShotAtSams(TargetGroupName)
self:T(self.lid.." _CheckShotAtSams")
local tgtgrp=TargetGroupName
local shorad=self.Samset
local shoradset=shorad:GetSet()
local returnname=false
for _,_groups in pairs(shoradset)do
local groupname=_groups:GetName()
if string.find(groupname,tgtgrp,1,true)then
returnname=true
end
end
return returnname
end
function SHORAD:_ShotIsDetected()
self:T(self.lid.." _ShotIsDetected")
if self.debug then return true end
local IsDetected=false
local DetectionProb=math.random(self.DefenseLowProb,self.DefenseHighProb)
local ActualDetection=math.random(1,100)
if ActualDetection<=DetectionProb then
IsDetected=true
end
return IsDetected
end
function SHORAD:onafterWakeUpShorad(From,Event,To,TargetGroup,Radius,ActiveTimer,TargetCat)
self:T(self.lid.." WakeUpShorad")
self:T({TargetGroup,Radius,ActiveTimer,TargetCat})
local targetcat=TargetCat or Object.Category.UNIT
local targetgroup=TargetGroup
local targetvec2=nil
if targetcat==Object.Category.UNIT then
targetvec2=GROUP:FindByName(targetgroup):GetVec2()
elseif targetcat==Object.Category.STATIC then
targetvec2=STATIC:FindByName(targetgroup,false):GetVec2()
else
local samset=self.Samset
local sam=samset:GetRandom()
targetvec2=sam:GetVec2()
end
local targetzone=ZONE_RADIUS:New("Shorad",targetvec2,Radius)
local groupset=self.Groupset
local shoradset=groupset:GetAliveSet()
local function SleepShorad(group)
local groupname=group:GetName()
self.ActiveGroups[groupname]=nil
if self.UseEmOnOff then
group:EnableEmission(false)
else
group:OptionAlarmStateGreen()
end
local text=string.format("Sleeping SHORAD %s",group:GetName())
self:T(text)
local m=MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
end
for _,_group in pairs(shoradset)do
if _group:IsAnyInZone(targetzone)then
local text=string.format("Waking up SHORAD %s",_group:GetName())
self:T(text)
local m=MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
if self.UseEmOnOff then
_group:EnableEmission(true)
end
_group:OptionAlarmStateRed()
local groupname=_group:GetName()
if self.ActiveGroups[groupname]==nil then
self.ActiveGroups[groupname]={Timing=ActiveTimer}
local endtime=timer.getTime()+(ActiveTimer*math.random(75,100)/100)
timer.scheduleFunction(SleepShorad,_group,endtime)
end
end
end
return self
end
function SHORAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup)
self:T("**** Calculating hit zone")
if SEADWeapon and SEADWeapon:isExist()then
local position=SEADWeapon:getPosition()
local mheight=height
local wph=math.atan2(position.x.z,position.x.x)
if wph<0 then
wph=wph+2*math.pi
end
wph=math.deg(wph)
local wpndata=SEAD.HarmData["AGM_88"]
local mveloc=math.floor(wpndata[2]*340.29)
local c1=(2*mheight*9.81)/(mveloc^2)
local c2=(mveloc^2)/9.81
local Ropt=c2*math.sqrt(c1+1)
if height<=5000 then
Ropt=Ropt*0.72
elseif height<=7500 then
Ropt=Ropt*0.82
elseif height<=10000 then
Ropt=Ropt*0.87
elseif height<=12500 then
Ropt=Ropt*0.98
end
for n=1,3 do
local dist=Ropt-((n-1)*20000)
local predpos=pos0:Translate(dist,wph)
if predpos then
local targetzone=ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
if self.debug then
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
end
local seadset=self.Groupset
local tgtcoord=targetzone:GetRandomPointVec2()
local tgtgrp=seadset:FindNearestGroupFromPointVec2(tgtcoord)
local _targetgroup=nil
local _targetgroupname="none"
local _targetskill="Random"
if tgtgrp and tgtgrp:IsAlive()then
_targetgroup=tgtgrp
_targetgroupname=tgtgrp:GetName()
_targetskill=tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".._targetgroupname)
self:WakeUpShorad(_targetgroupname,self.Radius,self.ActiveTimer,Object.Category.UNIT)
end
end
end
end
return self
end
function SHORAD:HandleEventShot(EventData)
self:T({EventData})
self:T(self.lid.." HandleEventShot")
local ShootingWeapon=EventData.Weapon
local ShootingWeaponName=EventData.WeaponName
local weaponcoalition=EventData.IniGroup:GetCoalition()
if self:_CheckCoalition(weaponcoalition)then
local IsDetected=self:_ShotIsDetected()
local DetectedText="false"
if IsDetected then
DetectedText="true"
end
local text=string.format("%s Missile Launched = %s | Detected probability state is %s",self.lid,ShootingWeaponName,DetectedText)
self:T(text)
local m=MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
if(self:_CheckHarms(ShootingWeaponName)or self:_CheckMavs(ShootingWeaponName))and IsDetected then
local targetdata=EventData.Weapon:getTarget()
if not targetdata or self.debug then
if string.find(ShootingWeaponName,"AGM_88",1,true)then
self:I("**** Tracking AGM-88 with no target data.")
local pos0=EventData.IniUnit:GetCoordinate()
local fheight=EventData.IniUnit:GetHeight()
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
end
return self
end
local targetcat=targetdata:getCategory()
self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat)))
self:T({targetdata})
local targetunit=nil
if targetcat==Object.Category.UNIT then
targetunit=UNIT:Find(targetdata)
elseif targetcat==Object.Category.STATIC then
local tgtcoord=COORDINATE:NewFromVec3(targetdata:getPoint())
local tgtgrp1=self.Samset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord1=tgtgrp1:GetCoordinate()
local tgtgrp2=self.Groupset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord2=tgtgrp2:GetCoordinate()
local dist1=tgtcoord:Get2DDistance(tgtcoord1)
local dist2=tgtcoord:Get2DDistance(tgtcoord2)
if dist1<dist2 then
targetunit=tgtgrp1
targetcat=Object.Category.UNIT
else
targetunit=tgtgrp2
targetcat=Object.Category.UNIT
end
end
if targetunit and targetunit:IsAlive()then
local targetunitname=targetunit:GetName()
local targetgroup=nil
local targetgroupname="none"
if targetcat==Object.Category.UNIT then
if targetunit.ClassName=="UNIT"then
targetgroup=targetunit:GetGroup()
elseif targetunit.ClassName=="GROUP"then
targetgroup=targetunit
end
targetgroupname=targetgroup:GetName()
elseif targetcat==Object.Category.STATIC then
targetgroup=targetunit
targetgroupname=targetunitname
end
local text=string.format("%s Missile Target = %s",self.lid,tostring(targetgroupname))
self:T(text)
local m=MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
local shotatus=self:_CheckShotAtShorad(targetgroupname)
local shotatsams=self:_CheckShotAtSams(targetgroupname)
if shotatsams or shotatus then
self:T({shotatsams=shotatsams,shotatus=shotatus})
self:WakeUpShorad(targetgroupname,self.Radius,self.ActiveTimer,targetcat)
end
end
end
end
return self
end
end
AIRBOSS={
ClassName="AIRBOSS",
Debug=false,
lid=nil,
theatre=nil,
carrier=nil,
carriertype=nil,
carrierparam={},
alias=nil,
airbase=nil,
waypoints={},
currentwp=nil,
beacon=nil,
TACANon=nil,
TACANchannel=nil,
TACANmode=nil,
TACANmorse=nil,
ICLSon=nil,
ICLSchannel=nil,
ICLSmorse=nil,
LSORadio=nil,
LSOFreq=nil,
LSOModu=nil,
MarshalRadio=nil,
MarshalFreq=nil,
MarshalModu=nil,
TowerFreq=nil,
radiotimer=nil,
zoneCCA=nil,
zoneCCZ=nil,
players={},
menuadded={},
BreakEntry={},
BreakEarly={},
BreakLate={},
Abeam={},
Ninety={},
Wake={},
Final={},
Groove={},
Platform={},
DirtyUp={},
Bullseye={},
defaultcase=nil,
case=nil,
defaultoffset=nil,
holdingoffset=nil,
recoverytimes={},
flights={},
Qpattern={},
Qmarshal={},
Qwaiting={},
Qspinning={},
RQMarshal={},
RQLSO={},
TQMarshal=0,
TQLSO=0,
Nmaxpattern=nil,
Nmaxmarshal=nil,
NmaxSection=nil,
NmaxStack=nil,
handleai=nil,
xtVoiceOvers=nil,
xtVoiceOversAI=nil,
tanker=nil,
Corientation=nil,
Corientlast=nil,
Cposition=nil,
defaultskill=nil,
adinfinitum=nil,
magvar=nil,
Tcollapse=nil,
recoverywindow=nil,
usersoundradio=nil,
Tqueue=nil,
dTqueue=nil,
dTstatus=nil,
menumarkzones=nil,
menusmokezones=nil,
playerscores=nil,
autosave=nil,
autosavefile=nil,
autosavepath=nil,
marshalradius=nil,
airbossnice=nil,
staticweather=nil,
windowcount=0,
LSOdT=nil,
senderac=nil,
radiorelayLSO=nil,
radiorelayMSH=nil,
turnintowind=nil,
detour=nil,
squadsetAI=nil,
excludesetAI=nil,
menusingle=nil,
collisiondist=nil,
holdtimestamp=nil,
Tmessage=nil,
soundfolder=nil,
soundfolderLSO=nil,
soundfolderMSH=nil,
despawnshutdown=nil,
dTbeacon=nil,
Tbeacon=nil,
LSOCall=nil,
MarshalCall=nil,
lowfuelAI=nil,
emergency=nil,
respawnAI=nil,
gle={},
lue={},
trapsheet=nil,
trappath=nil,
trapprefix=nil,
initialmaxalt=nil,
welcome=nil,
skipperMenu=nil,
skipperSpeed=nil,
skipperTime=nil,
skipperOffset=nil,
skipperUturn=nil,
}
AIRBOSS.AircraftCarrier={
AV8B="AV8BNA",
HORNET="FA-18C_hornet",
A4EC="A-4E-C",
F14A="F-14A-135-GR",
F14B="F-14B",
F14A_AI="F-14A",
FA18C="F/A-18C",
T45C="T-45",
S3B="S-3B",
S3BTANKER="S-3B Tanker",
E2D="E-2C",
C2A="C2A_Greyhound",
RHINOE="FA-18E",
RHINOF="FA-18F",
GROWLER="EA-18G",
}
AIRBOSS.CarrierType={
ROOSEVELT="CVN_71",
LINCOLN="CVN_72",
WASHINGTON="CVN_73",
TRUMAN="CVN_75",
STENNIS="Stennis",
FORRESTAL="Forrestal",
VINSON="VINSON",
HERMES="HERMES81",
INVINCIBLE="hms_invincible",
TARAWA="LHA_Tarawa",
AMERICA="USS America LHA-6",
JCARLOS="L61",
CANBERRA="L02",
KUZNETSOV="KUZNECOW",
}
AIRBOSS.PatternStep={
UNDEFINED="Undefined",
REFUELING="Refueling",
SPINNING="Spinning",
COMMENCING="Commencing",
HOLDING="Holding",
WAITING="Waiting for free Marshal stack",
PLATFORM="Platform",
ARCIN="Arc Turn In",
ARCOUT="Arc Turn Out",
DIRTYUP="Dirty Up",
BULLSEYE="Bullseye",
INITIAL="Initial",
BREAKENTRY="Break Entry",
EARLYBREAK="Early Break",
LATEBREAK="Late Break",
ABEAM="Abeam",
NINETY="Ninety",
WAKE="Wake",
FINAL="Turn Final",
GROOVE_XX="Groove X",
GROOVE_IM="Groove In the Middle",
GROOVE_IC="Groove In Close",
GROOVE_AR="Groove At the Ramp",
GROOVE_IW="Groove In the Wires",
GROOVE_AL="Groove Abeam Landing Spot",
GROOVE_LC="Groove Level Cross",
BOLTER="Bolter Pattern",
EMERGENCY="Emergency Landing",
DEBRIEF="Debrief",
}
AIRBOSS.GroovePos={
X0="X0",
XX="XX",
IM="IM",
IC="IC",
AR="AR",
AL="AL",
LC="LC",
IW="IW",
}
AIRBOSS.Difficulty={
EASY="Flight Student",
NORMAL="Naval Aviator",
HARD="TOPGUN Graduate",
}
AIRBOSS.MenuF10={}
AIRBOSS.MenuF10Root=nil
AIRBOSS.version="1.3.0"
function AIRBOSS:New(carriername,alias)
local self=BASE:Inherit(self,FSM:New())
self:F2({carriername=carriername,alias=alias})
self.carrier=UNIT:FindByName(carriername)
if self.carrier==nil then
local text=string.format("ERROR: Carrier unit %s could not be found! Make sure this UNIT is defined in the mission editor and check the spelling of the unit name carefully.",carriername)
MESSAGE:New(text,120):ToAll()
self:E(text)
return nil
end
self.lid=string.format("AIRBOSS %s | ",carriername)
self.theatre=env.mission.theatre
self:T2(self.lid..string.format("Theatre = %s.",tostring(self.theatre)))
self.carriertype=self.carrier:GetTypeName()
self.alias=alias or carriername
self.airbase=AIRBASE:FindByName(carriername)
self.beacon=BEACON:New(self.carrier)
self:_GetTowerFrequency()
self.playerscores={}
self:_InitWaypoints()
self.currentwp=1
self:_PatrolRoute()
self:SetMarshalRadio()
self:SetLSORadio()
self:SetLSOCallInterval()
self.radiotimer=SCHEDULER:New()
self:SetMagneticDeclination()
self:SetICLS()
self:SetTACAN()
self:SetBeaconRefresh()
self:SetMaxLandingPattern()
self:SetMaxMarshalStacks()
self:SetMaxSectionSize()
self:SetMaxFlightsPerStack()
self:SetHandleAION()
self:SetExtraVoiceOvers(false)
self:SetExtraVoiceOversAI(false)
self:SetAirbossNiceGuy()
self:SetEmergencyLandings()
self:SetDespawnOnEngineShutdown(false)
self:SetRespawnAI(false)
self:SetStaticWeather()
self:SetRecoveryCase()
self:SetRecoveryTurnTime()
self:SetHoldingOffsetAngle()
self:SetMarshalRadius()
self:SetInitialMaxAlt()
self:SetDefaultPlayerSkill(AIRBOSS.Difficulty.EASY)
self:SetGlideslopeErrorThresholds()
self:SetLineupErrorThresholds()
self:SetCarrierControlledArea()
self:SetCarrierControlledZone()
self:SetPatrolAdInfinitum(true)
self:SetCollisionDistance()
self:SetQueueUpdateTime()
self:SetStatusUpdateTime()
self:SetDefaultMessageDuration()
self:SetMenuMarkZones()
self:SetMenuSmokeZones()
self:SetMenuSingleCarrier(false)
self:SetWelcomePlayers(true)
self.landingcoord=COORDINATE:New(0,0,0)
self.sterncoord=COORDINATE:New(0,0,0)
self.landingspotcoord=COORDINATE:New(0,0,0)
if self.carriertype==AIRBOSS.CarrierType.STENNIS then
self:_InitNimitz()
elseif self.carriertype==AIRBOSS.CarrierType.ROOSEVELT then
self:_InitNimitz()
elseif self.carriertype==AIRBOSS.CarrierType.LINCOLN then
self:_InitNimitz()
elseif self.carriertype==AIRBOSS.CarrierType.WASHINGTON then
self:_InitNimitz()
elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then
self:_InitNimitz()
elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then
self:_InitForrestal()
elseif self.carriertype==AIRBOSS.CarrierType.VINSON then
self:_InitStennis()
elseif self.carriertype==AIRBOSS.CarrierType.HERMES then
self:_InitHermes()
elseif self.carriertype==AIRBOSS.CarrierType.INVINCIBLE then
self:_InitInvincible()
elseif self.carriertype==AIRBOSS.CarrierType.TARAWA then
self:_InitTarawa()
elseif self.carriertype==AIRBOSS.CarrierType.AMERICA then
self:_InitAmerica()
elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then
self:_InitJcarlos()
elseif self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self:_InitCanberra()
elseif self.carriertype==AIRBOSS.CarrierType.KUZNETSOV then
self:_InitStennis()
else
self:E(self.lid..string.format("ERROR: Unknown carrier type %s!",tostring(self.carriertype)))
return nil
end
self:_InitVoiceOvers()
if false then
self.Debug=true
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(3)
end
if false then
local case=3
self.holdingoffset=30
self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red,5)
self:_GetZoneLineup():SmokeZone(SMOKECOLOR.Green,5)
self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.White,45)
self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange,45)
self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue,45)
self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue,45)
self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Blue,45)
self:_GetZoneCorridor(case):SmokeZone(SMOKECOLOR.Green,45)
self:_GetZoneHolding(case,1):SmokeZone(SMOKECOLOR.White,45)
self:_GetZoneHolding(case,2):SmokeZone(SMOKECOLOR.White,45)
self:_GetZoneInitial(case):SmokeZone(SMOKECOLOR.Orange,45)
self:_GetZoneCommence(case,1):SmokeZone(SMOKECOLOR.Red,45)
self:_GetZoneCommence(case,2):SmokeZone(SMOKECOLOR.Red,45)
self:_GetZoneAbeamLandingSpot():SmokeZone(SMOKECOLOR.Red,5)
self:_GetZoneLandingSpot():SmokeZone(SMOKECOLOR.Red,5)
end
if false then
local FB=self:GetFinalBearing(false)
local hdg=self:GetHeading(false)
local stern=self:_GetSternCoord()
local bow=stern:Translate(self.carrierparam.totlength,hdg,true)
local rwy=stern:Translate(self.carrierparam.rwylength,FB,true)
local function flareme()
self:GetCoordinate():FlareYellow()
stern:FlareYellow()
bow:FlareYellow()
local r1=stern:Translate(self.carrierparam.rwywidth*0.5,FB+90,true)
local r2=stern:Translate(self.carrierparam.rwywidth*0.5,FB-90,true)
rwy:FlareRed()
local cR=stern:Translate(self.carrierparam.totwidthstarboard,hdg+90,true)
local cL=stern:Translate(self.carrierparam.totwidthport,hdg-90,true)
if self.carrier:GetTypeName()~=AIRBOSS.CarrierType.INVINCIBLE or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.CANBERRA then
local w1=stern:Translate(self.carrierparam.wire1,FB,true)
local w2=stern:Translate(self.carrierparam.wire2,FB,true)
local w3=stern:Translate(self.carrierparam.wire3,FB,true)
local w4=stern:Translate(self.carrierparam.wire4,FB,true)
w1:FlareWhite()
w2:FlareYellow()
w3:FlareWhite()
w4:FlareYellow()
else
local ALSPT=self:_GetZoneAbeamLandingSpot()
ALSPT:FlareZone(FLARECOLOR.Red,5,nil,UTILS.FeetToMeters(120))
local LSPT=self:_GetZoneLandingSpot()
LSPT:FlareZone(FLARECOLOR.Green,5,nil,self.carrierparam.deckheight)
local PLSC=self:_GetLandingSpotCoordinate()
PLSC:FlareWhite()
end
local cbox=self:_GetZoneCarrierBox()
local rbox=self:_GetZoneRunwayBox()
cbox:FlareZone(FLARECOLOR.Green,5,nil,self.carrierparam.deckheight)
rbox:FlareZone(FLARECOLOR.White,5,nil,self.carrierparam.deckheight)
end
SCHEDULER:New(nil,flareme,{},1,3,nil,180)
end
self:SetStartState("Stopped")
self:AddTransition("Stopped","Load","Stopped")
self:AddTransition("Stopped","Start","Idle")
self:AddTransition("*","Idle","Idle")
self:AddTransition("Idle","RecoveryStart","Recovering")
self:AddTransition("Recovering","RecoveryStop","Idle")
self:AddTransition("Recovering","RecoveryPause","Paused")
self:AddTransition("Paused","RecoveryUnpause","Recovering")
self:AddTransition("*","Status","*")
self:AddTransition("*","RecoveryCase","*")
self:AddTransition("*","PassingWaypoint","*")
self:AddTransition("*","LSOGrade","*")
self:AddTransition("*","Marshal","*")
self:AddTransition("*","Save","*")
self:AddTransition("*","Stop","Stopped")
return self
end
function AIRBOSS:SetWelcomePlayers(Switch)
self.welcome=Switch
return self
end
function AIRBOSS:SetCarrierControlledArea(Radius)
Radius=UTILS.NMToMeters(Radius or 50)
self.zoneCCA=ZONE_UNIT:New("Carrier Controlled Area",self.carrier,Radius)
return self
end
function AIRBOSS:SetCarrierControlledZone(Radius)
Radius=UTILS.NMToMeters(Radius or 5)
self.zoneCCZ=ZONE_UNIT:New("Carrier Controlled Zone",self.carrier,Radius)
return self
end
function AIRBOSS:SetCollisionDistance(Distance)
self.collisiondist=UTILS.NMToMeters(Distance or 5)
return self
end
function AIRBOSS:SetRecoveryCase(Case)
self.defaultcase=Case or 1
self.case=self.defaultcase
return self
end
function AIRBOSS:SetHoldingOffsetAngle(Offset)
self.defaultoffset=Offset or 0
self.holdingoffset=self.defaultoffset
return self
end
function AIRBOSS:SetMenuRecovery(Duration,WindOnDeck,Uturn,Offset)
self.skipperMenu=true
self.skipperTime=Duration or 30
self.skipperSpeed=WindOnDeck or 25
self.skipperOffset=Offset or 30
if Uturn then
self.skipperUturn=true
else
self.skipperUturn=false
end
return self
end
function AIRBOSS:AddRecoveryWindow(starttime,stoptime,case,holdingoffset,turnintowind,speed,uturn)
local Tnow=timer.getAbsTime()
if starttime and type(starttime)=="number"then
starttime=UTILS.SecondsToClock(Tnow+starttime)
end
if stoptime and type(stoptime)=="number"then
stoptime=UTILS.SecondsToClock(Tnow+stoptime)
end
starttime=starttime or UTILS.SecondsToClock(Tnow)
local Tstart=UTILS.ClockToSeconds(starttime)
local Tstop=stoptime and UTILS.ClockToSeconds(stoptime)or Tstart+90*60
if Tstart>Tstop then
self:E(string.format("ERROR: Recovery stop time %s lies before recovery start time %s! Recovery window rejected.",UTILS.SecondsToClock(Tstart),UTILS.SecondsToClock(Tstop)))
return self
end
if Tstop<=Tnow then
self:I(string.format("WARNING: Recovery stop time %s already over. Tnow=%s! Recovery window rejected.",UTILS.SecondsToClock(Tstop),UTILS.SecondsToClock(Tnow)))
return self
end
case=case or self.defaultcase
holdingoffset=holdingoffset or self.defaultoffset
if case==1 then
holdingoffset=0
end
self.windowcount=self.windowcount+1
local recovery={}
recovery.START=Tstart
recovery.STOP=Tstop
recovery.CASE=case
recovery.OFFSET=holdingoffset
recovery.OPEN=false
recovery.OVER=false
recovery.WIND=turnintowind
recovery.SPEED=speed or 20
recovery.ID=self.windowcount
if uturn==nil or uturn==true then
recovery.UTURN=true
else
recovery.UTURN=false
end
table.insert(self.recoverytimes,recovery)
return recovery
end
function AIRBOSS:SetSquadronAI(SetGroup)
self.squadsetAI=SetGroup
return self
end
function AIRBOSS:SetExcludeAI(SetGroup)
self.excludesetAI=SetGroup
return self
end
function AIRBOSS:AddExcludeAI(Group)
self.excludesetAI=self.excludesetAI or SET_GROUP:New()
self.excludesetAI:AddGroup(Group)
return self
end
function AIRBOSS:CloseCurrentRecoveryWindow(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,self.CloseCurrentRecoveryWindow,self)
else
if self:IsRecovering()and self.recoverywindow and self.recoverywindow.OPEN then
self:RecoveryStop()
self.recoverywindow.OPEN=false
self.recoverywindow.OVER=true
self:DeleteRecoveryWindow(self.recoverywindow)
end
end
end
function AIRBOSS:DeleteAllRecoveryWindows(Delay)
for _,recovery in pairs(self.recoverytimes)do
self:I(self.lid..string.format("Deleting recovery window ID %s",tostring(recovery.ID)))
self:DeleteRecoveryWindow(recovery,Delay)
end
return self
end
function AIRBOSS:GetRecoveryWindowByID(id)
if id then
for _,_window in pairs(self.recoverytimes)do
local window=_window
if window and window.ID==id then
return window
end
end
end
return nil
end
function AIRBOSS:DeleteRecoveryWindow(Window,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,self.DeleteRecoveryWindow,self,Window)
else
for i,_recovery in pairs(self.recoverytimes)do
local recovery=_recovery
if Window and Window.ID==recovery.ID then
if Window.OPEN then
self:RecoveryStop()
else
table.remove(self.recoverytimes,i)
end
end
end
end
end
function AIRBOSS:SetRecoveryTurnTime(Interval)
self.dTturn=Interval or 300
return self
end
function AIRBOSS:SetMPWireCorrection(Dcorr)
self.mpWireCorrection=Dcorr or 12
return self
end
function AIRBOSS:SetQueueUpdateTime(TimeInterval)
self.dTqueue=TimeInterval or 30
return self
end
function AIRBOSS:SetLSOCallInterval(TimeInterval)
self.LSOdT=TimeInterval or 4
return self
end
function AIRBOSS:SetAirbossNiceGuy(Switch)
if Switch==true or Switch==nil then
self.airbossnice=true
else
self.airbossnice=false
end
return self
end
function AIRBOSS:SetEmergencyLandings(Switch)
if Switch==true or Switch==nil then
self.emergency=true
else
self.emergency=false
end
return self
end
function AIRBOSS:SetDespawnOnEngineShutdown(Switch)
if Switch==true or Switch==nil then
self.despawnshutdown=true
else
self.despawnshutdown=false
end
return self
end
function AIRBOSS:SetRespawnAI(Switch)
if Switch==true or Switch==nil then
self.respawnAI=true
else
self.respawnAI=false
end
return self
end
function AIRBOSS:SetRefuelAI(LowFuelThreshold)
self.lowfuelAI=LowFuelThreshold or 10
return self
end
function AIRBOSS:SetInitialMaxAlt(MaxAltitude)
self.initialmaxalt=UTILS.FeetToMeters(MaxAltitude or 1300)
return self
end
function AIRBOSS:SetSoundfilesFolder(FolderPath)
if FolderPath then
local lastchar=string.sub(FolderPath,-1)
if lastchar~="/"then
FolderPath=FolderPath.."/"
end
end
self.soundfolder=FolderPath
self:I(self.lid..string.format("Setting sound files folder to: %s",self.soundfolder))
return self
end
function AIRBOSS:SetStatusUpdateTime(TimeInterval)
self.dTstatus=TimeInterval or 0.5
return self
end
function AIRBOSS:SetDefaultMessageDuration(Duration)
self.Tmessage=Duration or 10
return self
end
function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min,High,HIGH,Low,LOW)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self.gle._max=_max or 0.7
self.gle.High=High or 1.4
self.gle.HIGH=HIGH or 1.9
self.gle._min=_min or-0.5
self.gle.Low=Low or-1.2
self.gle.LOW=LOW or-1.5
else
self.gle._max=_max or 0.4
self.gle.High=High or 0.8
self.gle.HIGH=HIGH or 1.5
self.gle._min=_min or-0.3
self.gle.Low=Low or-0.6
self.gle.LOW=LOW or-0.9
end
return self
end
function AIRBOSS:SetLineupErrorThresholds(_max,_min,Left,LeftMed,LEFT,Right,RightMed,RIGHT)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self.lue._max=_max or 1.8
self.lue._min=_min or-1.8
self.lue.Left=Left or-2.8
self.lue.LeftMed=LeftMed or-3.8
self.lue.LEFT=LEFT or-4.5
self.lue.Right=Right or 2.8
self.lue.RightMed=RightMed or 3.8
self.lue.RIGHT=RIGHT or 4.5
else
self.lue._max=_max or 0.5
self.lue._min=_min or-0.5
self.lue.Left=Left or-1.0
self.lue.LeftMed=LeftMed or-2.0
self.lue.LEFT=LEFT or-3.0
self.lue.Right=Right or 1.0
self.lue.RightMed=RightMed or 2.0
self.lue.RIGHT=RIGHT or 3.0
end
return self
end
function AIRBOSS:SetMarshalRadius(Radius)
self.marshalradius=UTILS.NMToMeters(Radius or 2.8)
return self
end
function AIRBOSS:SetMenuSingleCarrier(Switch)
if Switch==true or Switch==nil then
self.menusingle=true
else
self.menusingle=false
end
return self
end
function AIRBOSS:SetMenuMarkZones(Switch)
if Switch==nil or Switch==true then
self.menumarkzones=true
else
self.menumarkzones=false
end
return self
end
function AIRBOSS:SetMenuSmokeZones(Switch)
if Switch==nil or Switch==true then
self.menusmokezones=true
else
self.menusmokezones=false
end
return self
end
function AIRBOSS:SetTrapSheet(Path,Prefix)
if io then
self.trapsheet=true
self.trappath=Path
self.trapprefix=Prefix
else
self:E(self.lid.."ERROR: io is not desanitized. Cannot save trap sheet.")
end
return self
end
function AIRBOSS:SetStaticWeather(Switch)
if Switch==nil or Switch==true then
self.staticweather=true
else
self.staticweather=false
end
return self
end
function AIRBOSS:SetTACANoff()
self.TACANon=false
return self
end
function AIRBOSS:SetTACAN(Channel,Mode,MorseCode)
self.TACANchannel=Channel or 74
self.TACANmode=Mode or"X"
self.TACANmorse=MorseCode or"STN"
self.TACANon=true
return self
end
function AIRBOSS:SetICLSoff()
self.ICLSon=false
return self
end
function AIRBOSS:SetICLS(Channel,MorseCode)
self.ICLSchannel=Channel or 1
self.ICLSmorse=MorseCode or"STN"
self.ICLSon=true
return self
end
function AIRBOSS:SetBeaconRefresh(TimeInterval)
self.dTbeacon=TimeInterval or(20*60)
return self
end
function AIRBOSS:SetLSORadio(Frequency,Modulation)
self.LSOFreq=(Frequency or 264)
Modulation=Modulation or"AM"
if Modulation=="FM"then
self.LSOModu=radio.modulation.FM
else
self.LSOModu=radio.modulation.AM
end
self.LSORadio={}
self.LSORadio.frequency=self.LSOFreq
self.LSORadio.modulation=self.LSOModu
self.LSORadio.alias="LSO"
return self
end
function AIRBOSS:SetMarshalRadio(Frequency,Modulation)
self.MarshalFreq=Frequency or 305
Modulation=Modulation or"AM"
if Modulation=="FM"then
self.MarshalModu=radio.modulation.FM
else
self.MarshalModu=radio.modulation.AM
end
self.MarshalRadio={}
self.MarshalRadio.frequency=self.MarshalFreq
self.MarshalRadio.modulation=self.MarshalModu
self.MarshalRadio.alias="MARSHAL"
return self
end
function AIRBOSS:SetRadioUnitName(unitname)
self.senderac=unitname
return self
end
function AIRBOSS:SetRadioRelayLSO(unitname)
self.radiorelayLSO=unitname
return self
end
function AIRBOSS:SetRadioRelayMarshal(unitname)
self.radiorelayMSH=unitname
return self
end
function AIRBOSS:SetUserSoundRadio()
self.usersoundradio=true
return self
end
function AIRBOSS:SoundCheckLSO(delay)
if delay and delay>0 then
self:ScheduleOnce(delay,AIRBOSS.SoundCheckLSO,self)
else
local text="Playing LSO sound files:"
for _name,_call in pairs(self.LSOCall)do
local call=_call
text=text..string.format("\nFile=%s.%s, duration=%.2f sec, loud=%s, subtitle=\"%s\".",call.file,call.suffix,call.duration,tostring(call.loud),call.subtitle)
self:RadioTransmission(self.LSORadio,call,false)
if call.loud then
self:RadioTransmission(self.LSORadio,call,true)
end
end
self:I(self.lid..text)
end
end
function AIRBOSS:SoundCheckMarshal(delay)
if delay and delay>0 then
self:ScheduleOnce(delay,AIRBOSS.SoundCheckMarshal,self)
else
local text="Playing Marshal sound files:"
for _name,_call in pairs(self.MarshalCall)do
local call=_call
text=text..string.format("\nFile=%s.%s, duration=%.2f sec, loud=%s, subtitle=\"%s\".",call.file,call.suffix,call.duration,tostring(call.loud),call.subtitle)
self:RadioTransmission(self.MarshalRadio,call,false)
if call.loud then
self:RadioTransmission(self.MarshalRadio,call,true)
end
end
self:I(self.lid..text)
end
end
function AIRBOSS:SetMaxLandingPattern(nmax)
nmax=nmax or 4
nmax=math.max(nmax,1)
nmax=math.min(nmax,6)
self.Nmaxpattern=nmax
return self
end
function AIRBOSS:SetMaxMarshalStacks(nmax)
self.Nmaxmarshal=nmax or 3
self.Nmaxmarshal=math.max(self.Nmaxmarshal,1)
return self
end
function AIRBOSS:SetMaxSectionSize(nmax)
nmax=nmax or 2
nmax=math.max(nmax,1)
nmax=math.min(nmax,4)
self.NmaxSection=nmax-1
return self
end
function AIRBOSS:SetMaxFlightsPerStack(nmax)
nmax=nmax or 2
nmax=math.max(nmax,1)
nmax=math.min(nmax,4)
self.NmaxStack=nmax
return self
end
function AIRBOSS:SetHandleAION()
self.handleai=true
return self
end
function AIRBOSS:SetExtraVoiceOvers(status)
self.xtVoiceOvers=status
return self
end
function AIRBOSS:SetExtraVoiceOversAI(status)
self.xtVoiceOversAI=status
return self
end
function AIRBOSS:SetHandleAIOFF()
self.handleai=false
return self
end
function AIRBOSS:SetRecoveryTanker(recoverytanker)
self.tanker=recoverytanker
return self
end
function AIRBOSS:SetAWACS(awacs)
self.awacs=awacs
return self
end
function AIRBOSS:SetDefaultPlayerSkill(skill)
self.defaultskill=skill or AIRBOSS.Difficulty.NORMAL
local gotit=false
for _,_skill in pairs(AIRBOSS.Difficulty)do
if _skill==self.defaultskill then
gotit=true
end
end
if not gotit then
self.defaultskill=AIRBOSS.Difficulty.NORMAL
self:E(self.lid..string.format("ERROR: Invalid default skill = %s. Resetting to Naval Aviator.",tostring(skill)))
end
return self
end
function AIRBOSS:SetAutoSave(path,filename)
self.autosave=true
self.autosavepath=path
self.autosavefile=filename
return self
end
function AIRBOSS:SetDebugModeON()
self.Debug=true
return self
end
function AIRBOSS:SetPatrolAdInfinitum(switch)
if switch==false then
self.adinfinitum=false
else
self.adinfinitum=true
end
return self
end
function AIRBOSS:SetMagneticDeclination(declination)
self.magvar=declination or UTILS.GetMagneticDeclination()
return self
end
function AIRBOSS:SetDebugModeOFF()
self.Debug=false
return self
end
function AIRBOSS:SetFunkManOn(Port,Host)
self.funkmanSocket=SOCKET:New(Port,Host)
return self
end
function AIRBOSS:GetNextRecoveryTime(InSeconds)
if self.recoverywindow then
if InSeconds then
return self.recoverywindow.START,self.recoverywindow.STOP
else
return UTILS.SecondsToClock(self.recoverywindow.START),UTILS.SecondsToClock(self.recoverywindow.STOP)
end
else
if InSeconds then
return-1,-1
else
return"?","?"
end
end
end
function AIRBOSS:IsRecovering()
return self:is("Recovering")
end
function AIRBOSS:IsIdle()
return self:is("Idle")
end
function AIRBOSS:IsPaused()
return self:is("Paused")
end
function AIRBOSS:_ActivateBeacons()
self:T(self.lid..string.format("Activating Beacons (TACAN=%s, ICLS=%s)",tostring(self.TACANon),tostring(self.ICLSon)))
if self.TACANon then
self:I(self.lid..string.format("Activating TACAN Channel %d%s (%s)",self.TACANchannel,self.TACANmode,self.TACANmorse))
self.beacon:ActivateTACAN(self.TACANchannel,self.TACANmode,self.TACANmorse,true)
end
if self.ICLSon then
self:I(self.lid..string.format("Activating ICLS Channel %d (%s)",self.ICLSchannel,self.ICLSmorse))
self.beacon:ActivateICLS(self.ICLSchannel,self.ICLSmorse)
end
self.Tbeacon=timer.getTime()
end
function AIRBOSS:onafterStart(From,Event,To)
self:I(self.lid..string.format("Starting AIRBOSS v%s for carrier unit %s of type %s on map %s",AIRBOSS.version,self.carrier:GetName(),self.carriertype,self.theatre))
self:_ActivateBeacons()
self.Cposition=self:GetCoordinate()
self.Corientation=self.carrier:GetOrientationX()
self.Corientlast=self.Corientation
self.Tpupdate=timer.getTime()
if#self.recoverytimes==0 and false then
local Topen=timer.getAbsTime()+15*60
local Tclose=Topen+3*60*60
self:AddRecoveryWindow(UTILS.SecondsToClock(Topen),UTILS.SecondsToClock(Tclose))
end
self:_CheckRecoveryTimes()
self.Tqueue=timer.getTime()-60
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.EngineShutdown)
self:HandleEvent(EVENTS.Takeoff)
self:HandleEvent(EVENTS.Crash)
self:HandleEvent(EVENTS.Ejection)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._PlayerLeft)
self:HandleEvent(EVENTS.MissionEnd)
self:HandleEvent(EVENTS.RemoveUnit)
self.StatusTimer=TIMER:New(self._Status,self):Start(2,0.5)
self:__Status(1)
end
function AIRBOSS:onafterStatus(From,Event,To)
local time=timer.getTime()
if time-self.Tqueue>self.dTqueue then
local clock=UTILS.SecondsToClock(timer.getAbsTime())
local eta=UTILS.SecondsToClock(self:_GetETAatNextWP())
local hdg=self:GetHeading()
local pos=self:GetCoordinate()
local speed=self.carrier:GetVelocityKNOTS()
local collision=false
local holdtime=0
if self.holdtimestamp then
holdtime=timer.getTime()-self.holdtimestamp
end
local NextWP=self:_GetNextWaypoint()
local ExpectedSpeed=UTILS.MpsToKnots(NextWP:GetVelocity())
if speed<0.5 and ExpectedSpeed>0 and not(self.detour or self.turnintowind)then
if not self.holdtimestamp then
self:E(self.lid..string.format("Carrier came to an unexpected standstill. Trying to re-route in 3 min. Speed=%.1f knots, expected=%.1f knots",speed,ExpectedSpeed))
self.holdtimestamp=timer.getTime()
else
if holdtime>3*60 then
local coord=self:GetCoordinate():Translate(500,hdg+10)
self:CarrierResumeRoute(coord)
self.holdtimestamp=nil
end
end
end
local text=string.format("Time %s - Status %s (case=%d) - Speed=%.1f kts - Heading=%d - WP=%d - ETA=%s - Turning=%s - Collision Warning=%s - Detour=%s - Turn Into Wind=%s - Holdtime=%d sec",clock,self:GetState(),self.case,speed,hdg,self.currentwp,eta,tostring(self.turning),tostring(collision),tostring(self.detour),tostring(self.turnintowind),holdtime)
self:T(self.lid..text)
text="Players:"
local i=0
for _name,_player in pairs(self.players)do
i=i+1
local player=_player
text=text..string.format("\n%d.) %s: Step=%s, Unit=%s, Airframe=%s",i,tostring(player.name),tostring(player.step),tostring(player.unitname),tostring(player.actype))
end
if i==0 then
text=text.." none"
end
self:I(self.lid..text)
if collision then
if self.turnintowind then
self:CarrierResumeRoute(self.Creturnto)
if self:IsRecovering()and self.recoverywindow and self.recoverywindow.WIND then
self.recoverywindow.WIND=false
end
end
end
self:_CheckRecoveryTimes()
self:_ScanCarrierZone()
self:_CheckQueue()
self:_CheckCarrierTurning()
self:_CheckPatternUpdate()
self.Tqueue=time
end
if time-self.Tbeacon>self.dTbeacon then
self:_ActivateBeacons()
end
self:__Status(-30)
end
function AIRBOSS:_Status()
self:_CheckPlayerStatus()
self:_CheckAIStatus()
end
function AIRBOSS:_CheckAIStatus()
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
if flight.ai then
local fuel=flight.group:GetFuelMin()*100
local text=string.format("Group %s fuel=%.1f %%",flight.groupname,fuel)
self:T3(self.lid..text)
if self.lowfuelAI and fuel<self.lowfuelAI and not flight.refueling then
self:_RefuelAI(flight)
self:_RemoveFlightFromMarshalQueue(flight,true)
end
end
end
for _,_flight in pairs(self.Qpattern)do
local flight=_flight
if flight.ai then
for _,_element in pairs(flight.elements)do
local element=_element
local unit=element.unit
local lineup=self:_Lineup(unit,true)
local unitcoord=unit:GetCoord()
local dist=unitcoord:Get2DDistance(self:GetCoord())
local distance=UTILS.MetersToNM(dist)
local alt=UTILS.MetersToFeet(unitcoord.y)
if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then
self:RadioTransmission(self.LSORadio,self.LSOCall.CALLTHEBALL,nil,nil,nil,true)
self:_LSOCallAircraftBall(element.onboard,self:_GetACNickname(unit:GetTypeName()),self:_GetFuelState(unit)/1000)
self:RadioTransmission(self.LSORadio,self.LSOCall.ROGERBALL,nil,nil,0.5,true)
element.ballcall=true
flight.ballcall=true
end
end
end
end
end
function AIRBOSS:_CheckPlayerPatternDistance(player)
if player.step==AIRBOSS.PatternStep.INITIAL or
player.step==AIRBOSS.PatternStep.BREAKENTRY or
player.step==AIRBOSS.PatternStep.EARLYBREAK or
player.step==AIRBOSS.PatternStep.LATEBREAK or
player.step==AIRBOSS.PatternStep.ABEAM or
player.step==AIRBOSS.PatternStep.GROOVE_XX or
player.step==AIRBOSS.PatternStep.GROOVE_IM then
return
else
return
end
if#self.Qpattern==0 then
return
end
local function _checkclose(_unit1,_unit2)
local unit1=_unit1
local unit2=_unit2
if(not unit1)or(not unit2)then
return false
end
if unit1:GetName()==unit2:GetName()then
return false
end
if not unit2:InAir()then
return false
end
local c1=unit1:GetCoordinate()
local c2=unit2:GetCoordinate()
local vec12={x=c2.x-c1.x,y=0,z=c2.z-c1.z}
local dist=UTILS.VecNorm(vec12)
local vec1=unit1:GetOrientationX()
vec1.y=0
local rhdg=math.deg(math.acos(UTILS.VecDot(vec12,vec1)/UTILS.VecNorm(vec12)/UTILS.VecNorm(vec1)))
local dalt=math.abs(c2.y-c1.y)
local dcrit=UTILS.FeetToMeters(650)
if math.abs(rhdg)<10 and dist<dcrit and dalt<50 then
return true
else
return false
end
end
for _,_flight in pairs(self.Qpattern)do
local flight=_flight
for _,_element in pairs(flight.elements)do
local element=_element
local tooclose=_checkclose(player.unit,element.unit)
if tooclose then
local text=string.format("Player %s too close (<200 meters) to aircraft %s!",player.name,element.unit:GetName())
self:T2(self.lid..text)
end
end
end
end
function AIRBOSS:_CheckRecoveryTimes()
local time=timer.getAbsTime()
local Cnow=UTILS.SecondsToClock(time)
local text=string.format(self.lid.."Recovery time windows:")
if#self.recoverytimes==0 then
text=text.." none!"
end
local _sort=function(a,b)
return a.START<b.START
end
table.sort(self.recoverytimes,_sort)
local nextwindow=nil
local currwindow=nil
for _,_recovery in pairs(self.recoverytimes)do
local recovery=_recovery
local Cstart=UTILS.SecondsToClock(recovery.START)
local Cstop=UTILS.SecondsToClock(recovery.STOP)
local state=""
if time>=recovery.START then
if time<recovery.STOP then
if self:IsRecovering()then
state="in progress"
else
self:RecoveryStart(recovery.CASE,recovery.OFFSET)
state="starting now"
recovery.OPEN=true
end
currwindow=recovery
else
if self:IsRecovering()and not recovery.OVER then
local _,npattern=self:_GetQueueInfo(self.Qpattern)
if npattern>0 then
local extmin=5*npattern
recovery.STOP=recovery.STOP+extmin*60
local text=string.format("We still got flights in the pattern.\nRecovery time prolonged by %d minutes.\nNow get your act together and no more bolters!",extmin)
self:MessageToPattern(text,"AIRBOSS","99",10,false,nil)
else
self:RecoveryStop()
state="closing now"
recovery.OPEN=false
recovery.OVER=true
end
else
state="closed"
end
end
else
state="in the future"
if nextwindow==nil then
nextwindow=recovery
state="next in line"
end
end
text=text..string.format("\n- Start=%s Stop=%s Case=%d Offset=%d Open=%s Closed=%s Status=\"%s\"",Cstart,Cstop,recovery.CASE,recovery.OFFSET,tostring(recovery.OPEN),tostring(recovery.OVER),state)
end
self:T(self.lid..text)
self.recoverywindow=nil
if self:IsIdle()then
if nextwindow then
self:RecoveryCase(nextwindow.CASE,nextwindow.OFFSET)
if nextwindow.WIND and nextwindow.START-time<self.dTturn and not self.turnintowind then
local hdg=self:GetHeading()
local wind=self:GetHeadingIntoWind()
local delta=self:_GetDeltaHeading(hdg,wind)
local uturn=delta>5
local _,vwind=self:GetWind()
if vwind<0.1 then
uturn=false
end
if not nextwindow.UTURN then
uturn=false
end
self:T(self.lid..string.format("Heading=%03d°, Wind=%03d° %.1f kts, Delta=%03d° ==> U-turn=%s",hdg,wind,UTILS.MpsToKnots(vwind),delta,tostring(uturn)))
local t=math.max(nextwindow.STOP-nextwindow.START+self.dTturn,60*60*24)
local v=UTILS.KnotsToMps(nextwindow.SPEED)
local vmax=self.carrier:GetSpeedMax()/3.6
v=math.min(v,vmax)
self:CarrierTurnIntoWind(t,v,uturn)
end
self.recoverywindow=nextwindow
else
self:RecoveryCase()
end
else
if currwindow then
self.recoverywindow=currwindow
else
self.recoverywindow=nextwindow
end
end
self:T2({"FF",recoverywindow=self.recoverywindow})
end
function AIRBOSS:_GetFlightLead(flight)
if flight.name~=flight.seclead then
local lead=self.players[flight.seclead]
return lead,false
else
return flight,true
end
end
function AIRBOSS:onbeforeRecoveryCase(From,Event,To,Case,Offset)
Case=Case or self.defaultcase
Offset=Offset or self.defaultoffset
if Case==self.case and Offset==self.holdingoffset then
return false
end
return true
end
function AIRBOSS:onafterRecoveryCase(From,Event,To,Case,Offset)
Case=Case or self.defaultcase
Offset=Offset or self.defaultoffset
local text=string.format("Switching recovery case %d ==> %d",self.case,Case)
if Case>1 then
text=text..string.format(" Holding offset angle %d degrees.",Offset)
end
MESSAGE:New(text,20,self.alias):ToAllIf(self.Debug)
self:T(self.lid..text)
self.case=Case
self.holdingoffset=Offset
for _,_flight in pairs(self.flights)do
local flight=_flight
if not(self:_InQueue(self.Qmarshal,flight.group)or self:_InQueue(self.Qpattern,flight.group))then
if flight.name~=flight.seclead then
local lead=self.players[flight.seclead]
if lead and not(self:_InQueue(self.Qmarshal,lead.group)or self:_InQueue(self.Qpattern,lead.group))then
flight.case=self.case
end
else
flight.case=self.case
end
end
end
end
function AIRBOSS:onafterRecoveryStart(From,Event,To,Case,Offset)
Case=Case or self.defaultcase
Offset=Offset or self.defaultoffset
self:_MarshalCallRecoveryStart(Case)
self:RecoveryCase(Case,Offset)
end
function AIRBOSS:onafterRecoveryStop(From,Event,To)
self:T(self.lid..string.format("Stopping aircraft recovery."))
self:_MarshalCallRecoveryStopped(self.case)
if self.turnintowind then
local coord=self.Creturnto
if self.recoverywindow and self.recoverywindow.UTURN==false then
coord=nil
end
self:CarrierResumeRoute(coord)
end
if self.recoverywindow and self.recoverywindow.OPEN==true then
self.recoverywindow.OPEN=false
self.recoverywindow.OVER=true
self:DeleteRecoveryWindow(self.recoverywindow)
end
self:_CheckRecoveryTimes()
end
function AIRBOSS:onafterRecoveryPause(From,Event,To,duration)
self:T(self.lid..string.format("Pausing aircraft recovery."))
if duration then
self:__RecoveryUnpause(duration)
local clock=UTILS.SecondsToClock(timer.getAbsTime()+duration)
self:_MarshalCallRecoveryPausedResumedAt(clock)
else
local text=string.format("aircraft recovery is paused until further notice.")
self:_MarshalCallRecoveryPausedNotice()
end
end
function AIRBOSS:onafterRecoveryUnpause(From,Event,To)
self:T(self.lid..string.format("Unpausing aircraft recovery."))
self:_MarshalCallResumeRecovery()
end
function AIRBOSS:onafterPassingWaypoint(From,Event,To,n)
self:I(self.lid..string.format("Carrier passed waypoint %d.",n))
end
function AIRBOSS:onafterIdle(From,Event,To)
self:T(self.lid..string.format("Carrier goes to idle."))
end
function AIRBOSS:onafterStop(From,Event,To)
self:I(self.lid..string.format("Stopping airboss script."))
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.Takeoff)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.PlayerLeaveUnit)
self:UnHandleEvent(EVENTS.MissionEnd)
self.CallScheduler:Clear()
end
function AIRBOSS:_InitStennis()
self.carrierparam.sterndist=-153
self.carrierparam.deckheight=18.30
self.carrierparam.totlength=310
self.carrierparam.totwidthport=40
self.carrierparam.totwidthstarboard=30
self.carrierparam.rwyangle=-9.1359
self.carrierparam.rwylength=225
self.carrierparam.rwywidth=20
self.carrierparam.wire1=46
self.carrierparam.wire2=46+12
self.carrierparam.wire3=46+24
self.carrierparam.wire4=46+35
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3
self.Platform.name="Platform 5k"
self.Platform.Xmin=-UTILS.NMToMeters(22)
self.Platform.Xmax=nil
self.Platform.Zmin=-UTILS.NMToMeters(30)
self.Platform.Zmax=UTILS.NMToMeters(30)
self.Platform.LimitXmin=nil
self.Platform.LimitXmax=nil
self.Platform.LimitZmin=nil
self.Platform.LimitZmax=nil
self.DirtyUp.name="Dirty Up"
self.DirtyUp.Xmin=-UTILS.NMToMeters(21)
self.DirtyUp.Xmax=nil
self.DirtyUp.Zmin=-UTILS.NMToMeters(30)
self.DirtyUp.Zmax=UTILS.NMToMeters(30)
self.DirtyUp.LimitXmin=nil
self.DirtyUp.LimitXmax=nil
self.DirtyUp.LimitZmin=nil
self.DirtyUp.LimitZmax=nil
self.Bullseye.name="Bullseye"
self.Bullseye.Xmin=-UTILS.NMToMeters(11)
self.Bullseye.Xmax=nil
self.Bullseye.Zmin=-UTILS.NMToMeters(30)
self.Bullseye.Zmax=UTILS.NMToMeters(30)
self.Bullseye.LimitXmin=nil
self.Bullseye.LimitXmax=nil
self.Bullseye.LimitZmin=nil
self.Bullseye.LimitZmax=nil
self.BreakEntry.name="Break Entry"
self.BreakEntry.Xmin=-UTILS.NMToMeters(4)
self.BreakEntry.Xmax=nil
self.BreakEntry.Zmin=-UTILS.NMToMeters(0.5)
self.BreakEntry.Zmax=UTILS.NMToMeters(1.5)
self.BreakEntry.LimitXmin=0
self.BreakEntry.LimitXmax=nil
self.BreakEntry.LimitZmin=nil
self.BreakEntry.LimitZmax=nil
self.BreakEarly.name="Early Break"
self.BreakEarly.Xmin=-UTILS.NMToMeters(1)
self.BreakEarly.Xmax=UTILS.NMToMeters(5)
self.BreakEarly.Zmin=-UTILS.NMToMeters(2)
self.BreakEarly.Zmax=UTILS.NMToMeters(1)
self.BreakEarly.LimitXmin=0
self.BreakEarly.LimitXmax=nil
self.BreakEarly.LimitZmin=-UTILS.NMToMeters(0.2)
self.BreakEarly.LimitZmax=nil
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
self.BreakLate.Xmax=UTILS.NMToMeters(5)
self.BreakLate.Zmin=-UTILS.NMToMeters(2)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.8)
self.BreakLate.LimitZmax=nil
self.Abeam.name="Abeam Position"
self.Abeam.Xmin=-UTILS.NMToMeters(5)
self.Abeam.Xmax=UTILS.NMToMeters(5)
self.Abeam.Zmin=-UTILS.NMToMeters(2)
self.Abeam.Zmax=500
self.Abeam.LimitXmin=-200
self.Abeam.LimitXmax=nil
self.Abeam.LimitZmin=nil
self.Abeam.LimitZmax=nil
self.Ninety.name="Ninety"
self.Ninety.Xmin=-UTILS.NMToMeters(4)
self.Ninety.Xmax=0
self.Ninety.Zmin=-UTILS.NMToMeters(2)
self.Ninety.Zmax=nil
self.Ninety.LimitXmin=nil
self.Ninety.LimitXmax=nil
self.Ninety.LimitZmin=nil
self.Ninety.LimitZmax=-UTILS.NMToMeters(0.6)
self.Wake.name="Wake"
self.Wake.Xmin=-UTILS.NMToMeters(4)
self.Wake.Xmax=0
self.Wake.Zmin=-2000
self.Wake.Zmax=nil
self.Wake.LimitXmin=nil
self.Wake.LimitXmax=nil
self.Wake.LimitZmin=0
self.Wake.LimitZmax=nil
self.Final.name="Final"
self.Final.Xmin=-UTILS.NMToMeters(4)
self.Final.Xmax=0
self.Final.Zmin=-2000
self.Final.Zmax=nil
self.Final.LimitXmin=nil
self.Final.LimitXmax=nil
self.Final.LimitZmin=nil
self.Final.LimitZmax=nil
self.Groove.name="Groove"
self.Groove.Xmin=-UTILS.NMToMeters(4)
self.Groove.Xmax=nil
self.Groove.Zmin=-UTILS.NMToMeters(2)
self.Groove.Zmax=UTILS.NMToMeters(2)
self.Groove.LimitXmin=nil
self.Groove.LimitXmax=nil
self.Groove.LimitZmin=nil
self.Groove.LimitZmax=nil
end
function AIRBOSS:_InitNimitz()
self:_InitStennis()
self.carrierparam.sterndist=-164
self.carrierparam.deckheight=20.1494
self.carrierparam.totlength=332.8
self.carrierparam.totwidthport=45
self.carrierparam.totwidthstarboard=35
self.carrierparam.rwyangle=-9.1359
self.carrierparam.rwylength=250
self.carrierparam.rwywidth=25
self.carrierparam.wire1=55
self.carrierparam.wire2=67
self.carrierparam.wire3=79
self.carrierparam.wire4=92
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3
end
function AIRBOSS:_InitForrestal()
self:_InitNimitz()
self.carrierparam.sterndist=-135.5
self.carrierparam.deckheight=20
self.carrierparam.totlength=315
self.carrierparam.totwidthport=45
self.carrierparam.totwidthstarboard=35
self.carrierparam.rwyangle=-9.1359
self.carrierparam.rwylength=212
self.carrierparam.rwywidth=25
self.carrierparam.wire1=44
self.carrierparam.wire2=54
self.carrierparam.wire3=64
self.carrierparam.wire4=74
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3
end
function AIRBOSS:_InitHermes()
self:_InitStennis()
self.carrierparam.sterndist=-105
self.carrierparam.deckheight=12
self.carrierparam.totlength=228.19
self.carrierparam.totwidthport=20.5
self.carrierparam.totwidthstarboard=24.5
self.carrierparam.rwyangle=0
self.carrierparam.rwylength=215
self.carrierparam.rwywidth=13
self.carrierparam.wire1=nil
self.carrierparam.wire2=nil
self.carrierparam.wire3=nil
self.carrierparam.wire4=nil
self.carrierparam.landingspot=69
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
self.BreakLate.Xmax=UTILS.NMToMeters(5)
self.BreakLate.Zmin=-UTILS.NMToMeters(1.6)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5)
self.BreakLate.LimitZmax=nil
end
function AIRBOSS:_InitInvincible()
self:_InitStennis()
self.carrierparam.sterndist=-105
self.carrierparam.deckheight=12
self.carrierparam.totlength=228.19
self.carrierparam.totwidthport=20.5
self.carrierparam.totwidthstarboard=24.5
self.carrierparam.rwyangle=0
self.carrierparam.rwylength=215
self.carrierparam.rwywidth=13
self.carrierparam.wire1=nil
self.carrierparam.wire2=nil
self.carrierparam.wire3=nil
self.carrierparam.wire4=nil
self.carrierparam.landingspot=69
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
self.BreakLate.Xmax=UTILS.NMToMeters(5)
self.BreakLate.Zmin=-UTILS.NMToMeters(1.6)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5)
self.BreakLate.LimitZmax=nil
end
function AIRBOSS:_InitTarawa()
self:_InitStennis()
self.carrierparam.sterndist=-125
self.carrierparam.deckheight=21
self.carrierparam.totlength=245
self.carrierparam.totwidthport=10
self.carrierparam.totwidthstarboard=25
self.carrierparam.rwyangle=0
self.carrierparam.rwylength=225
self.carrierparam.rwywidth=15
self.carrierparam.wire1=nil
self.carrierparam.wire2=nil
self.carrierparam.wire3=nil
self.carrierparam.wire4=nil
self.carrierparam.landingspot=57
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
self.BreakLate.Xmax=UTILS.NMToMeters(5)
self.BreakLate.Zmin=-UTILS.NMToMeters(1.6)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5)
self.BreakLate.LimitZmax=nil
end
function AIRBOSS:_InitAmerica()
self:_InitStennis()
self.carrierparam.sterndist=-125
self.carrierparam.deckheight=20
self.carrierparam.totlength=257
self.carrierparam.totwidthport=11
self.carrierparam.totwidthstarboard=25
self.carrierparam.rwyangle=0
self.carrierparam.rwylength=240
self.carrierparam.rwywidth=15
self.carrierparam.wire1=nil
self.carrierparam.wire2=nil
self.carrierparam.wire3=nil
self.carrierparam.wire4=nil
self.carrierparam.landingspot=59
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
self.BreakLate.Xmax=UTILS.NMToMeters(5)
self.BreakLate.Zmin=-UTILS.NMToMeters(1.6)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5)
self.BreakLate.LimitZmax=nil
end
function AIRBOSS:_InitJcarlos()
self:_InitStennis()
self.carrierparam.sterndist=-125
self.carrierparam.deckheight=20
self.carrierparam.totlength=231
self.carrierparam.totwidthport=10
self.carrierparam.totwidthstarboard=22
self.carrierparam.rwyangle=0
self.carrierparam.rwylength=202
self.carrierparam.rwywidth=14
self.carrierparam.wire1=nil
self.carrierparam.wire2=nil
self.carrierparam.wire3=nil
self.carrierparam.wire4=nil
self.carrierparam.landingspot=89
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
self.BreakLate.Xmax=UTILS.NMToMeters(5)
self.BreakLate.Zmin=-UTILS.NMToMeters(1.6)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5)
self.BreakLate.LimitZmax=nil
end
function AIRBOSS:_InitCanberra()
self:_InitJcarlos()
end
function AIRBOSS:SetVoiceOversMarshalByGabriella(mizfolder)
if mizfolder then
local lastchar=string.sub(mizfolder,-1)
if lastchar~="/"then
mizfolder=mizfolder.."/"
end
self.soundfolderMSH=mizfolder
else
self.soundfolderMSH=self.soundfolder
end
self:I(self.lid..string.format("Marshal Gabriella reporting for duty! Soundfolder=%s",tostring(self.soundfolderMSH)))
self.MarshalCall.AFFIRMATIVE.duration=0.65
self.MarshalCall.ALTIMETER.duration=0.60
self.MarshalCall.BRC.duration=0.67
self.MarshalCall.CARRIERTURNTOHEADING.duration=1.62
self.MarshalCall.CASE.duration=0.30
self.MarshalCall.CHARLIETIME.duration=0.77
self.MarshalCall.CLEAREDFORRECOVERY.duration=0.93
self.MarshalCall.DECKCLOSED.duration=0.73
self.MarshalCall.DEGREES.duration=0.48
self.MarshalCall.EXPECTED.duration=0.50
self.MarshalCall.FLYNEEDLES.duration=0.89
self.MarshalCall.HOLDATANGELS.duration=0.81
self.MarshalCall.HOURS.duration=0.41
self.MarshalCall.MARSHALRADIAL.duration=0.95
self.MarshalCall.N0.duration=0.41
self.MarshalCall.N1.duration=0.30
self.MarshalCall.N2.duration=0.34
self.MarshalCall.N3.duration=0.31
self.MarshalCall.N4.duration=0.34
self.MarshalCall.N5.duration=0.30
self.MarshalCall.N6.duration=0.33
self.MarshalCall.N7.duration=0.38
self.MarshalCall.N8.duration=0.35
self.MarshalCall.N9.duration=0.35
self.MarshalCall.NEGATIVE.duration=0.60
self.MarshalCall.NEWFB.duration=0.95
self.MarshalCall.OPS.duration=0.23
self.MarshalCall.POINT.duration=0.38
self.MarshalCall.RADIOCHECK.duration=1.27
self.MarshalCall.RECOVERY.duration=0.60
self.MarshalCall.RECOVERYOPSSTOPPED.duration=1.25
self.MarshalCall.RECOVERYPAUSEDNOTICE.duration=2.55
self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=2.55
self.MarshalCall.REPORTSEEME.duration=0.87
self.MarshalCall.RESUMERECOVERY.duration=1.55
self.MarshalCall.ROGER.duration=0.50
self.MarshalCall.SAYNEEDLES.duration=0.82
self.MarshalCall.STACKFULL.duration=5.70
self.MarshalCall.STARTINGRECOVERY.duration=1.61
end
function AIRBOSS:SetVoiceOversMarshalByRaynor(mizfolder)
if mizfolder then
local lastchar=string.sub(mizfolder,-1)
if lastchar~="/"then
mizfolder=mizfolder.."/"
end
self.soundfolderMSH=mizfolder
else
self.soundfolderMSH=self.soundfolder
end
self:I(self.lid..string.format("Marshal Raynor reporting for duty! Soundfolder=%s",tostring(self.soundfolderMSH)))
self.MarshalCall.AFFIRMATIVE.duration=0.70
self.MarshalCall.ALTIMETER.duration=0.60
self.MarshalCall.BRC.duration=0.60
self.MarshalCall.CARRIERTURNTOHEADING.duration=1.87
self.MarshalCall.CASE.duration=0.60
self.MarshalCall.CHARLIETIME.duration=0.81
self.MarshalCall.CLEAREDFORRECOVERY.duration=1.21
self.MarshalCall.DECKCLOSED.duration=0.86
self.MarshalCall.DEGREES.duration=0.55
self.MarshalCall.EXPECTED.duration=0.61
self.MarshalCall.FLYNEEDLES.duration=0.90
self.MarshalCall.HOLDATANGELS.duration=0.91
self.MarshalCall.HOURS.duration=0.54
self.MarshalCall.MARSHALRADIAL.duration=0.80
self.MarshalCall.N0.duration=0.38
self.MarshalCall.N1.duration=0.30
self.MarshalCall.N2.duration=0.30
self.MarshalCall.N3.duration=0.30
self.MarshalCall.N4.duration=0.32
self.MarshalCall.N5.duration=0.41
self.MarshalCall.N6.duration=0.48
self.MarshalCall.N7.duration=0.51
self.MarshalCall.N8.duration=0.38
self.MarshalCall.N9.duration=0.34
self.MarshalCall.NEGATIVE.duration=0.60
self.MarshalCall.NEWFB.duration=1.10
self.MarshalCall.OPS.duration=0.46
self.MarshalCall.POINT.duration=0.21
self.MarshalCall.RADIOCHECK.duration=0.95
self.MarshalCall.RECOVERY.duration=0.63
self.MarshalCall.RECOVERYOPSSTOPPED.duration=1.36
self.MarshalCall.RECOVERYPAUSEDNOTICE.duration=2.8
self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=2.75
self.MarshalCall.REPORTSEEME.duration=1.06
self.MarshalCall.RESUMERECOVERY.duration=1.41
self.MarshalCall.ROGER.duration=0.41
self.MarshalCall.SAYNEEDLES.duration=0.79
self.MarshalCall.STACKFULL.duration=4.70
self.MarshalCall.STARTINGRECOVERY.duration=2.06
end
function AIRBOSS:SetVoiceOversLSOByRaynor(mizfolder)
if mizfolder then
local lastchar=string.sub(mizfolder,-1)
if lastchar~="/"then
mizfolder=mizfolder.."/"
end
self.soundfolderLSO=mizfolder
else
self.soundfolderLSO=self.soundfolder
end
self:I(self.lid..string.format("LSO Raynor reporting for duty! Soundfolder=%s",tostring(self.soundfolderLSO)))
self.LSOCall.BOLTER.duration=0.75
self.LSOCall.CALLTHEBALL.duration=0.625
self.LSOCall.CHECK.duration=0.40
self.LSOCall.CLEAREDTOLAND.duration=0.85
self.LSOCall.COMELEFT.duration=0.60
self.LSOCall.DEPARTANDREENTER.duration=1.10
self.LSOCall.EXPECTHEAVYWAVEOFF.duration=1.30
self.LSOCall.EXPECTSPOT75.duration=1.85
self.LSOCall.EXPECTSPOT5.duration=1.3
self.LSOCall.FAST.duration=0.75
self.LSOCall.FOULDECK.duration=0.75
self.LSOCall.HIGH.duration=0.65
self.LSOCall.IDLE.duration=0.40
self.LSOCall.LONGINGROOVE.duration=1.25
self.LSOCall.LOW.duration=0.60
self.LSOCall.N0.duration=0.38
self.LSOCall.N1.duration=0.30
self.LSOCall.N2.duration=0.30
self.LSOCall.N3.duration=0.30
self.LSOCall.N4.duration=0.32
self.LSOCall.N5.duration=0.41
self.LSOCall.N6.duration=0.48
self.LSOCall.N7.duration=0.51
self.LSOCall.N8.duration=0.38
self.LSOCall.N9.duration=0.34
self.LSOCall.PADDLESCONTACT.duration=0.91
self.LSOCall.POWER.duration=0.45
self.LSOCall.RADIOCHECK.duration=0.90
self.LSOCall.RIGHTFORLINEUP.duration=0.70
self.LSOCall.ROGERBALL.duration=0.72
self.LSOCall.SLOW.duration=0.63
self.LSOCall.STABILIZED.duration=0.75
self.LSOCall.WAVEOFF.duration=0.55
self.LSOCall.WELCOMEABOARD.duration=0.80
end
function AIRBOSS:SetVoiceOversLSOByFF(mizfolder)
if mizfolder then
local lastchar=string.sub(mizfolder,-1)
if lastchar~="/"then
mizfolder=mizfolder.."/"
end
self.soundfolderLSO=mizfolder
else
self.soundfolderLSO=self.soundfolder
end
self:I(self.lid..string.format("LSO FF reporting for duty! Soundfolder=%s",tostring(self.soundfolderLSO)))
self.LSOCall.BOLTER.duration=0.75
self.LSOCall.CALLTHEBALL.duration=0.60
self.LSOCall.CHECK.duration=0.45
self.LSOCall.CLEAREDTOLAND.duration=1.00
self.LSOCall.COMELEFT.duration=0.60
self.LSOCall.DEPARTANDREENTER.duration=1.10
self.LSOCall.EXPECTHEAVYWAVEOFF.duration=1.20
self.LSOCall.EXPECTSPOT75.duration=2.00
self.LSOCall.EXPECTSPOT5.duration=1.3
self.LSOCall.FAST.duration=0.70
self.LSOCall.FOULDECK.duration=0.62
self.LSOCall.HIGH.duration=0.65
self.LSOCall.IDLE.duration=0.45
self.LSOCall.LONGINGROOVE.duration=1.20
self.LSOCall.LOW.duration=0.50
self.LSOCall.N0.duration=0.40
self.LSOCall.N1.duration=0.25
self.LSOCall.N2.duration=0.37
self.LSOCall.N3.duration=0.37
self.LSOCall.N4.duration=0.39
self.LSOCall.N5.duration=0.39
self.LSOCall.N6.duration=0.40
self.LSOCall.N7.duration=0.40
self.LSOCall.N8.duration=0.37
self.LSOCall.N9.duration=0.40
self.LSOCall.PADDLESCONTACT.duration=1.00
self.LSOCall.POWER.duration=0.50
self.LSOCall.RADIOCHECK.duration=1.10
self.LSOCall.RIGHTFORLINEUP.duration=0.80
self.LSOCall.ROGERBALL.duration=1.00
self.LSOCall.SLOW.duration=0.65
self.LSOCall.SLOW.duration=0.59
self.LSOCall.STABILIZED.duration=0.90
self.LSOCall.WAVEOFF.duration=0.60
self.LSOCall.WELCOMEABOARD.duration=1.00
end
function AIRBOSS:SetVoiceOversMarshalByFF(mizfolder)
if mizfolder then
local lastchar=string.sub(mizfolder,-1)
if lastchar~="/"then
mizfolder=mizfolder.."/"
end
self.soundfolderMSH=mizfolder
else
self.soundfolderMSH=self.soundfolder
end
self:I(self.lid..string.format("Marshal FF reporting for duty! Soundfolder=%s",tostring(self.soundfolderMSH)))
self.MarshalCall.AFFIRMATIVE.duration=0.90
self.MarshalCall.ALTIMETER.duration=0.85
self.MarshalCall.BRC.duration=0.80
self.MarshalCall.CARRIERTURNTOHEADING.duration=2.48
self.MarshalCall.CASE.duration=0.40
self.MarshalCall.CHARLIETIME.duration=0.90
self.MarshalCall.CLEAREDFORRECOVERY.duration=1.25
self.MarshalCall.DECKCLOSED.duration=1.10
self.MarshalCall.DEGREES.duration=0.60
self.MarshalCall.EXPECTED.duration=0.55
self.MarshalCall.FLYNEEDLES.duration=0.90
self.MarshalCall.HOLDATANGELS.duration=1.10
self.MarshalCall.HOURS.duration=0.60
self.MarshalCall.MARSHALRADIAL.duration=1.10
self.MarshalCall.N0.duration=0.40
self.MarshalCall.N1.duration=0.25
self.MarshalCall.N2.duration=0.37
self.MarshalCall.N3.duration=0.37
self.MarshalCall.N4.duration=0.39
self.MarshalCall.N5.duration=0.39
self.MarshalCall.N6.duration=0.40
self.MarshalCall.N7.duration=0.40
self.MarshalCall.N8.duration=0.37
self.MarshalCall.N9.duration=0.40
self.MarshalCall.NEGATIVE.duration=0.80
self.MarshalCall.NEWFB.duration=1.35
self.MarshalCall.OPS.duration=0.48
self.MarshalCall.POINT.duration=0.33
self.MarshalCall.RADIOCHECK.duration=1.20
self.MarshalCall.RECOVERY.duration=0.70
self.MarshalCall.RECOVERYOPSSTOPPED.duration=1.65
self.MarshalCall.RECOVERYPAUSEDNOTICE.duration=2.9
self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=3.40
self.MarshalCall.REPORTSEEME.duration=0.95
self.MarshalCall.RESUMERECOVERY.duration=1.75
self.MarshalCall.ROGER.duration=0.53
self.MarshalCall.SAYNEEDLES.duration=0.90
self.MarshalCall.STACKFULL.duration=6.35
self.MarshalCall.STARTINGRECOVERY.duration=2.65
end
function AIRBOSS:_InitVoiceOvers()
self.LSOCall={
BOLTER={file="LSO-BolterBolter",suffix="ogg",loud=false,subtitle="Bolter, Bolter",duration=0.75,subduration=5},
CALLTHEBALL={file="LSO-CallTheBall",suffix="ogg",loud=false,subtitle="Call the ball",duration=0.6,subduration=2},
CHECK={file="LSO-Check",suffix="ogg",loud=false,subtitle="Check",duration=0.45,subduration=2.5},
CLEAREDTOLAND={file="LSO-ClearedToLand",suffix="ogg",loud=false,subtitle="Cleared to land",duration=1.0,subduration=5},
COMELEFT={file="LSO-ComeLeft",suffix="ogg",loud=true,subtitle="Come left",duration=0.60,subduration=1},
RADIOCHECK={file="LSO-RadioCheck",suffix="ogg",loud=false,subtitle="Paddles, radio check",duration=1.1,subduration=5},
RIGHTFORLINEUP={file="LSO-RightForLineup",suffix="ogg",loud=true,subtitle="Right for line up",duration=0.80,subduration=1},
HIGH={file="LSO-High",suffix="ogg",loud=true,subtitle="You're high",duration=0.65,subduration=1},
LOW={file="LSO-Low",suffix="ogg",loud=true,subtitle="You're low",duration=0.50,subduration=1},
POWER={file="LSO-Power",suffix="ogg",loud=true,subtitle="Power",duration=0.50,subduration=1},
SLOW={file="LSO-Slow",suffix="ogg",loud=true,subtitle="You're slow",duration=0.65,subduration=1},
FAST={file="LSO-Fast",suffix="ogg",loud=true,subtitle="You're fast",duration=0.70,subduration=1},
ROGERBALL={file="LSO-RogerBall",suffix="ogg",loud=false,subtitle="Roger ball",duration=1.00,subduration=2},
WAVEOFF={file="LSO-WaveOff",suffix="ogg",loud=false,subtitle="Wave off",duration=0.6,subduration=5},
LONGINGROOVE={file="LSO-LongInTheGroove",suffix="ogg",loud=false,subtitle="You're long in the groove",duration=1.2,subduration=5},
FOULDECK={file="LSO-FoulDeck",suffix="ogg",loud=false,subtitle="Foul deck",duration=0.62,subduration=5},
DEPARTANDREENTER={file="LSO-DepartAndReenter",suffix="ogg",loud=false,subtitle="Depart and re-enter",duration=1.1,subduration=5},
PADDLESCONTACT={file="LSO-PaddlesContact",suffix="ogg",loud=false,subtitle="Paddles, contact",duration=1.0,subduration=5},
WELCOMEABOARD={file="LSO-WelcomeAboard",suffix="ogg",loud=false,subtitle="Welcome aboard",duration=1.0,subduration=5},
EXPECTHEAVYWAVEOFF={file="LSO-ExpectHeavyWaveoff",suffix="ogg",loud=false,subtitle="Expect heavy waveoff",duration=1.2,subduration=5},
EXPECTSPOT75={file="LSO-ExpectSpot75",suffix="ogg",loud=false,subtitle="Expect spot 7.5",duration=2.0,subduration=5},
EXPECTSPOT5={file="LSO-ExpectSpot5",suffix="ogg",loud=false,subtitle="Expect spot 5",duration=1.3,subduration=5},
STABILIZED={file="LSO-Stabilized",suffix="ogg",loud=false,subtitle="Stabilized",duration=0.9,subduration=5},
IDLE={file="LSO-Idle",suffix="ogg",loud=false,subtitle="Idle",duration=0.45,subduration=5},
N0={file="LSO-N0",suffix="ogg",loud=false,subtitle="",duration=0.40},
N1={file="LSO-N1",suffix="ogg",loud=false,subtitle="",duration=0.25},
N2={file="LSO-N2",suffix="ogg",loud=false,subtitle="",duration=0.37},
N3={file="LSO-N3",suffix="ogg",loud=false,subtitle="",duration=0.37},
N4={file="LSO-N4",suffix="ogg",loud=false,subtitle="",duration=0.39},
N5={file="LSO-N5",suffix="ogg",loud=false,subtitle="",duration=0.39},
N6={file="LSO-N6",suffix="ogg",loud=false,subtitle="",duration=0.40},
N7={file="LSO-N7",suffix="ogg",loud=false,subtitle="",duration=0.40},
N8={file="LSO-N8",suffix="ogg",loud=false,subtitle="",duration=0.37},
N9={file="LSO-N9",suffix="ogg",loud=false,subtitle="",duration=0.40},
CLICK={file="AIRBOSS-RadioClick",suffix="ogg",loud=false,subtitle="",duration=0.35},
NOISE={file="AIRBOSS-Noise",suffix="ogg",loud=false,subtitle="",duration=3.6},
SPINIT={file="AIRBOSS-SpinIt",suffix="ogg",loud=false,subtitle="",duration=0.73,subduration=5},
}
self.PilotCall={
N0={file="PILOT-N0",suffix="ogg",loud=false,subtitle="",duration=0.40},
N1={file="PILOT-N1",suffix="ogg",loud=false,subtitle="",duration=0.25},
N2={file="PILOT-N2",suffix="ogg",loud=false,subtitle="",duration=0.37},
N3={file="PILOT-N3",suffix="ogg",loud=false,subtitle="",duration=0.37},
N4={file="PILOT-N4",suffix="ogg",loud=false,subtitle="",duration=0.39},
N5={file="PILOT-N5",suffix="ogg",loud=false,subtitle="",duration=0.39},
N6={file="PILOT-N6",suffix="ogg",loud=false,subtitle="",duration=0.40},
N7={file="PILOT-N7",suffix="ogg",loud=false,subtitle="",duration=0.40},
N8={file="PILOT-N8",suffix="ogg",loud=false,subtitle="",duration=0.37},
N9={file="PILOT-N9",suffix="ogg",loud=false,subtitle="",duration=0.40},
POINT={file="PILOT-Point",suffix="ogg",loud=false,subtitle="",duration=0.33},
SKYHAWK={file="PILOT-Skyhawk",suffix="ogg",loud=false,subtitle="",duration=0.95,subduration=5},
HARRIER={file="PILOT-Harrier",suffix="ogg",loud=false,subtitle="",duration=0.58,subduration=5},
HAWKEYE={file="PILOT-Hawkeye",suffix="ogg",loud=false,subtitle="",duration=0.63,subduration=5},
TOMCAT={file="PILOT-Tomcat",suffix="ogg",loud=false,subtitle="",duration=0.66,subduration=5},
HORNET={file="PILOT-Hornet",suffix="ogg",loud=false,subtitle="",duration=0.56,subduration=5},
VIKING={file="PILOT-Viking",suffix="ogg",loud=false,subtitle="",duration=0.61,subduration=5},
BALL={file="PILOT-Ball",suffix="ogg",loud=false,subtitle="",duration=0.50,subduration=5},
BINGOFUEL={file="PILOT-BingoFuel",suffix="ogg",loud=false,subtitle="",duration=0.80},
GASATDIVERT={file="PILOT-GasAtDivert",suffix="ogg",loud=false,subtitle="",duration=1.80},
GASATTANKER={file="PILOT-GasAtTanker",suffix="ogg",loud=false,subtitle="",duration=1.95},
}
self.MarshalCall={
AFFIRMATIVE={file="MARSHAL-Affirmative",suffix="ogg",loud=false,subtitle="",duration=0.90},
ALTIMETER={file="MARSHAL-Altimeter",suffix="ogg",loud=false,subtitle="",duration=0.85},
BRC={file="MARSHAL-BRC",suffix="ogg",loud=false,subtitle="",duration=0.80},
CARRIERTURNTOHEADING={file="MARSHAL-CarrierTurnToHeading",suffix="ogg",loud=false,subtitle="",duration=2.48,subduration=5},
CASE={file="MARSHAL-Case",suffix="ogg",loud=false,subtitle="",duration=0.40},
CHARLIETIME={file="MARSHAL-CharlieTime",suffix="ogg",loud=false,subtitle="",duration=0.90},
CLEAREDFORRECOVERY={file="MARSHAL-ClearedForRecovery",suffix="ogg",loud=false,subtitle="",duration=1.25},
DECKCLOSED={file="MARSHAL-DeckClosed",suffix="ogg",loud=false,subtitle="",duration=1.10,subduration=5},
DEGREES={file="MARSHAL-Degrees",suffix="ogg",loud=false,subtitle="",duration=0.60},
EXPECTED={file="MARSHAL-Expected",suffix="ogg",loud=false,subtitle="",duration=0.55},
FLYNEEDLES={file="MARSHAL-FlyYourNeedles",suffix="ogg",loud=false,subtitle="Fly your needles",duration=0.9,subduration=5},
HOLDATANGELS={file="MARSHAL-HoldAtAngels",suffix="ogg",loud=false,subtitle="",duration=1.10},
HOURS={file="MARSHAL-Hours",suffix="ogg",loud=false,subtitle="",duration=0.60,subduration=5},
MARSHALRADIAL={file="MARSHAL-MarshalRadial",suffix="ogg",loud=false,subtitle="",duration=1.10},
N0={file="MARSHAL-N0",suffix="ogg",loud=false,subtitle="",duration=0.40},
N1={file="MARSHAL-N1",suffix="ogg",loud=false,subtitle="",duration=0.25},
N2={file="MARSHAL-N2",suffix="ogg",loud=false,subtitle="",duration=0.37},
N3={file="MARSHAL-N3",suffix="ogg",loud=false,subtitle="",duration=0.37},
N4={file="MARSHAL-N4",suffix="ogg",loud=false,subtitle="",duration=0.39},
N5={file="MARSHAL-N5",suffix="ogg",loud=false,subtitle="",duration=0.39},
N6={file="MARSHAL-N6",suffix="ogg",loud=false,subtitle="",duration=0.40},
N7={file="MARSHAL-N7",suffix="ogg",loud=false,subtitle="",duration=0.40},
N8={file="MARSHAL-N8",suffix="ogg",loud=false,subtitle="",duration=0.37},
N9={file="MARSHAL-N9",suffix="ogg",loud=false,subtitle="",duration=0.40},
NEGATIVE={file="MARSHAL-Negative",suffix="ogg",loud=false,subtitle="",duration=0.80,subduration=5},
NEWFB={file="MARSHAL-NewFB",suffix="ogg",loud=false,subtitle="",duration=1.35},
OPS={file="MARSHAL-Ops",suffix="ogg",loud=false,subtitle="",duration=0.48},
POINT={file="MARSHAL-Point",suffix="ogg",loud=false,subtitle="",duration=0.33},
RADIOCHECK={file="MARSHAL-RadioCheck",suffix="ogg",loud=false,subtitle="Radio check",duration=1.20,subduration=5},
RECOVERY={file="MARSHAL-Recovery",suffix="ogg",loud=false,subtitle="",duration=0.70,subduration=5},
RECOVERYOPSSTOPPED={file="MARSHAL-RecoveryOpsStopped",suffix="ogg",loud=false,subtitle="",duration=1.65,subduration=5},
RECOVERYPAUSEDNOTICE={file="MARSHAL-RecoveryPausedNotice",suffix="ogg",loud=false,subtitle="aircraft recovery paused until further notice",duration=2.90,subduration=5},
RECOVERYPAUSEDRESUMED={file="MARSHAL-RecoveryPausedResumed",suffix="ogg",loud=false,subtitle="",duration=3.40,subduration=5},
REPORTSEEME={file="MARSHAL-ReportSeeMe",suffix="ogg",loud=false,subtitle="",duration=0.95},
RESUMERECOVERY={file="MARSHAL-ResumeRecovery",suffix="ogg",loud=false,subtitle="resuming aircraft recovery",duration=1.75,subduraction=5},
ROGER={file="MARSHAL-Roger",suffix="ogg",loud=false,subtitle="",duration=0.53,subduration=5},
SAYNEEDLES={file="MARSHAL-SayNeedles",suffix="ogg",loud=false,subtitle="Say needles",duration=0.90,subduration=5},
STACKFULL={file="MARSHAL-StackFull",suffix="ogg",loud=false,subtitle="Marshal Stack is currently full. Hold outside 10 NM zone and wait for further instructions",duration=6.35,subduration=10},
STARTINGRECOVERY={file="MARSHAL-StartingRecovery",suffix="ogg",loud=false,subtitle="",duration=2.65,subduration=5},
CLICK={file="AIRBOSS-RadioClick",suffix="ogg",loud=false,subtitle="",duration=0.35},
NOISE={file="AIRBOSS-Noise",suffix="ogg",loud=false,subtitle="",duration=3.6},
}
self:SetVoiceOversLSOByRaynor()
self:SetVoiceOversMarshalByRaynor()
end
function AIRBOSS:SetVoiceOver(radiocall,duration,subtitle,subduration,filename,suffix)
radiocall.duration=duration
radiocall.subtitle=subtitle or radiocall.subtitle
radiocall.file=filename
radiocall.suffix=suffix or".ogg"
end
function AIRBOSS:_GetAircraftAoA(playerData)
local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER
local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C
local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC
local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B
local aoa={}
if hornet then
aoa.SLOW=12.6
aoa.Slow=11.1
aoa.OnSpeedMax=9.6
aoa.OnSpeed=8.1
aoa.OnSpeedMin=6.6
aoa.Fast=5.1
aoa.FAST=3.6
elseif tomcat then
aoa.SLOW=self:_AoAUnit2Deg(playerData,19.5)
aoa.Slow=self:_AoAUnit2Deg(playerData,18.0)
aoa.OnSpeedMax=self:_AoAUnit2Deg(playerData,16.5)
aoa.OnSpeed=self:_AoAUnit2Deg(playerData,15.0)
aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,13.5)
aoa.Fast=self:_AoAUnit2Deg(playerData,12.0)
aoa.FAST=self:_AoAUnit2Deg(playerData,10.5)
elseif goshawk then
aoa.SLOW=10.00
aoa.Slow=9.00
aoa.OnSpeedMax=8.00
aoa.OnSpeed=7.00
aoa.OnSpeedMin=6.00
aoa.Fast=5.00
aoa.FAST=4.00
elseif skyhawk then
aoa.SLOW=11.75
aoa.Slow=10.75
aoa.OnSpeedMax=9.75
aoa.OnSpeed=8.75
aoa.OnSpeedMin=7.75
aoa.Fast=6.75
aoa.FAST=5.75
elseif harrier then
aoa.SLOW=16.0
aoa.Slow=14.0
aoa.OnSpeedMax=12.0
aoa.OnSpeed=10.0
aoa.OnSpeedMin=8.0
aoa.Fast=6.0
aoa.FAST=4.0
end
return aoa
end
function AIRBOSS:_AoAUnit2Deg(playerData,aoaunits)
local degrees=aoaunits
if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
degrees=-10+50/30*aoaunits
degrees=0.918*aoaunits-3.411
elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then
degrees=0.5*aoaunits
end
return degrees
end
function AIRBOSS:_AoADeg2Units(playerData,degrees)
local aoaunits=degrees
if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
aoaunits=(degrees+10)*30/50
aoaunits=1.089*degrees+3.715
elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then
aoaunits=2*degrees
end
return aoaunits
end
function AIRBOSS:_GetAircraftParameters(playerData,step)
step=step or playerData.step
local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER
local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC
local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B
local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
local alt
local aoa
local dist
local speed
local aoaac=self:_GetAircraftAoA(playerData)
if step==AIRBOSS.PatternStep.PLATFORM then
alt=UTILS.FeetToMeters(5000)
speed=UTILS.KnotsToMps(250)
elseif step==AIRBOSS.PatternStep.ARCIN then
if tomcat then
speed=UTILS.KnotsToMps(150)
else
speed=UTILS.KnotsToMps(250)
end
elseif step==AIRBOSS.PatternStep.ARCOUT then
if tomcat then
speed=UTILS.KnotsToMps(150)
else
speed=UTILS.KnotsToMps(250)
end
elseif step==AIRBOSS.PatternStep.DIRTYUP then
alt=UTILS.FeetToMeters(1200)
elseif step==AIRBOSS.PatternStep.BULLSEYE then
alt=UTILS.FeetToMeters(1200)
dist=-UTILS.NMToMeters(3)
aoa=aoaac.OnSpeed
elseif step==AIRBOSS.PatternStep.INITIAL then
if hornet or tomcat or harrier then
alt=UTILS.FeetToMeters(800)
speed=UTILS.KnotsToMps(350)
elseif skyhawk then
alt=UTILS.FeetToMeters(600)
speed=UTILS.KnotsToMps(250)
elseif goshawk then
alt=UTILS.FeetToMeters(800)
speed=UTILS.KnotsToMps(300)
end
elseif step==AIRBOSS.PatternStep.BREAKENTRY then
if hornet or tomcat or harrier then
alt=UTILS.FeetToMeters(800)
speed=UTILS.KnotsToMps(350)
elseif skyhawk then
alt=UTILS.FeetToMeters(600)
speed=UTILS.KnotsToMps(250)
elseif goshawk then
alt=UTILS.FeetToMeters(800)
speed=UTILS.KnotsToMps(300)
end
elseif step==AIRBOSS.PatternStep.EARLYBREAK then
if hornet or tomcat or harrier or goshawk then
alt=UTILS.FeetToMeters(800)
elseif skyhawk then
alt=UTILS.FeetToMeters(600)
end
elseif step==AIRBOSS.PatternStep.LATEBREAK then
if hornet or tomcat or harrier or goshawk then
alt=UTILS.FeetToMeters(800)
elseif skyhawk then
alt=UTILS.FeetToMeters(600)
end
elseif step==AIRBOSS.PatternStep.ABEAM then
if hornet or tomcat or harrier or goshawk then
alt=UTILS.FeetToMeters(600)
elseif skyhawk then
alt=UTILS.FeetToMeters(500)
end
aoa=aoaac.OnSpeed
if goshawk then
dist=UTILS.NMToMeters(0.9)
elseif harrier then
dist=UTILS.NMToMeters(0.9)
else
dist=UTILS.NMToMeters(1.1)
end
elseif step==AIRBOSS.PatternStep.NINETY then
if hornet or tomcat then
alt=UTILS.FeetToMeters(500)
elseif goshawk then
alt=UTILS.FeetToMeters(450)
elseif skyhawk then
alt=UTILS.FeetToMeters(500)
elseif harrier then
alt=UTILS.FeetToMeters(425)
end
aoa=aoaac.OnSpeed
elseif step==AIRBOSS.PatternStep.WAKE then
if hornet or goshawk then
alt=UTILS.FeetToMeters(370)
elseif tomcat then
alt=UTILS.FeetToMeters(430)
elseif skyhawk then
alt=UTILS.FeetToMeters(370)
end
aoa=aoaac.OnSpeed
elseif step==AIRBOSS.PatternStep.FINAL then
if hornet or goshawk then
alt=UTILS.FeetToMeters(300)
elseif tomcat then
alt=UTILS.FeetToMeters(360)
elseif skyhawk then
alt=UTILS.FeetToMeters(300)
elseif harrier then
alt=UTILS.FeetToMeters(312)
end
aoa=aoaac.OnSpeed
end
return alt,aoa,dist,speed
end
function AIRBOSS:_GetNextMarshalFight()
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
local stack=flight.flag
local Tmarshal=timer.getAbsTime()-flight.time
local TmarshalMin=2*60
if flight.ai then
TmarshalMin=3*60
end
if flight.holding~=nil and Tmarshal>=TmarshalMin then
if flight.case==1 and stack==1 or flight.case>1 then
if flight.ai then
return flight
else
if flight.step~=AIRBOSS.PatternStep.COMMENCING then
return flight
end
end
end
end
end
return nil
end
function AIRBOSS:_CheckQueue()
if self.Debug then
self:_PrintQueue(self.flights,"All Flights")
end
self:_PrintQueue(self.Qmarshal,"Marshal")
self:_PrintQueue(self.Qpattern,"Pattern")
self:_PrintQueue(self.Qwaiting,"Waiting")
self:_PrintQueue(self.Qspinning,"Spinning")
if self.case>1 then
for _,_flight in pairs(self.Qwaiting)do
local flight=_flight
local removed=self:_RemoveFlightFromQueue(self.Qwaiting,flight)
if removed then
local stack=self:_GetFreeStack(flight.ai)
self:T(self.lid..string.format("Moving flight %s onboard %s from Waiting queue to Case %d Marshal stack %d",flight.groupname,flight.onboard,self.case,stack))
if flight.ai then
self:_MarshalAI(flight,stack)
else
self:_MarshalPlayer(flight,stack)
end
break
end
end
end
if not self:IsRecovering()then
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
if(flight.case==1 and self.case>1)or(flight.case>1 and self.case==1)then
local removed=self:_RemoveFlightFromQueue(self.Qmarshal,flight)
if removed then
local stack=self:_GetFreeStack(flight.ai)
self:T(self.lid..string.format("Moving flight %s onboard %s from Marshal Case %d ==> %d Marshal stack %d",flight.groupname,flight.onboard,flight.case,self.case,stack))
if flight.ai then
self:_MarshalAI(flight,stack)
else
self:_MarshalPlayer(flight,stack)
end
break
elseif flight.case~=self.case then
flight.case=self.case
end
end
end
return
end
local _,npattern=self:_GetQueueInfo(self.Qpattern)
local _,nspinning=self:_GetQueueInfo(self.Qspinning)
local marshalflight=self:_GetNextMarshalFight()
if marshalflight and npattern<self.Nmaxpattern and nspinning==0 then
local Tmarshal=timer.getAbsTime()-marshalflight.time
self:T(self.lid..string.format("Marshal time of next group %s = %d seconds",marshalflight.groupname,Tmarshal))
local Tpattern=9999
local npunits=1
local pcase=1
if npattern>0 then
local patternflight=self.Qpattern[#self.Qpattern]
pcase=patternflight.case
local npunits=self:_GetFlightUnits(patternflight,false)
Tpattern=timer.getAbsTime()-patternflight.time
self:T(self.lid..string.format("Pattern time of last group %s = %d seconds. # of units=%d.",patternflight.groupname,Tpattern,npunits))
end
local TpatternMin
if pcase==1 then
TpatternMin=2*60*npunits
else
TpatternMin=2*60*npunits
end
if Tpattern>TpatternMin then
self:T(self.lid..string.format("Sending marshal flight %s to pattern.",marshalflight.groupname))
self:_ClearForLanding(marshalflight)
end
end
end
function AIRBOSS:_ClearForLanding(flight)
if flight.ai then
self:_RemoveFlightFromMarshalQueue(flight,false)
self:_LandAI(flight)
self:_MarshalCallClearedForRecovery(flight.onboard,flight.case)
if self.xtVoiceOversAI then
local leader=flight.group:GetUnits()[1]
self:_CommencingCall(leader,flight.onboard)
end
else
if flight.step~=AIRBOSS.PatternStep.COMMENCING then
self:_MarshalCallClearedForRecovery(flight.onboard,flight.case)
flight.time=timer.getAbsTime()
end
self:_SetPlayerStep(flight,AIRBOSS.PatternStep.COMMENCING,3)
end
end
function AIRBOSS:_SetPlayerStep(playerData,step,delay)
if delay and delay>0 then
self:ScheduleOnce(delay,self._SetPlayerStep,self,playerData,step)
else
if playerData then
playerData.step=step
playerData.warning=nil
self:_StepHint(playerData)
end
end
end
function AIRBOSS:_ScanCarrierZone()
local coord=self:GetCoordinate()
local RCCZ=self.zoneCCA:GetRadius()
self:T(self.lid..string.format("Scanning Carrier Controlled Area. Radius=%.1f NM.",UTILS.MetersToNM(RCCZ)))
local _,_,_,unitscan=coord:ScanObjects(RCCZ,true,false,false)
local insideCCA={}
for _,_unit in pairs(unitscan)do
local unit=_unit
local airborne=unit:IsAir()
local inzone=unit:IsInZone(self.zoneCCA)
local friendly=self:GetCoalition()==unit:GetCoalition()
local carrierac=self:_IsCarrierAircraft(unit)
if airborne and inzone and friendly and carrierac then
local group=unit:GetGroup()
local groupname=group:GetName()
if insideCCA[groupname]==nil then
insideCCA[groupname]=group
end
end
end
for groupname,_group in pairs(insideCCA)do
local group=_group
local knownflight=self:_GetFlightFromGroupInQueue(group,self.flights)
local actype=group:GetTypeName()
if knownflight then
if knownflight.ai and knownflight.flag==-100 and self.handleai and false then
local putintomarshal=false
local flight=_DATABASE:GetOpsGroup(groupname)
if flight and flight:IsInbound()and flight.destbase:GetName()==self.carrier:GetName()then
if flight.ishelo then
else
putintomarshal=true
end
flight.airboss=self
end
if putintomarshal then
local stack=self:_GetFreeStack(knownflight.ai)
local respawn=self.respawnAI
if stack then
self:_MarshalAI(knownflight,stack,respawn)
else
if not self:_InQueue(self.Qwaiting,knownflight.group)then
self:_WaitAI(knownflight,respawn)
end
end
break
end
end
else
if not self:_IsHuman(group)then
self:_CreateFlightGroup(group)
end
end
end
local remove={}
for _,_flight in pairs(self.flights)do
local flight=_flight
if insideCCA[flight.groupname]==nil then
if flight.ai and not(self:_InQueue(self.Qmarshal,flight.group)or self:_InQueue(self.Qpattern,flight.group))then
table.insert(remove,flight)
end
end
end
for _,flight in pairs(remove)do
self:_RemoveFlightFromQueue(self.flights,flight)
end
end
function AIRBOSS:_WaitPlayer(playerData)
if playerData then
local nwaiting=#self.Qwaiting
self:_MarshalCallStackFull(playerData.onboard,nwaiting)
table.insert(self.Qwaiting,playerData)
playerData.time=timer.getAbsTime()
playerData.step=AIRBOSS.PatternStep.WAITING
playerData.warning=nil
for _,_flight in pairs(playerData.section)do
local flight=_flight
flight.step=AIRBOSS.PatternStep.WAITING
flight.time=timer.getAbsTime()
flight.warning=nil
end
end
end
function AIRBOSS:_MarshalPlayer(playerData,stack)
if playerData then
self:_AddMarshalGroup(playerData,stack)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.HOLDING)
playerData.holding=nil
for _,_flight in pairs(playerData.section)do
local flight=_flight
self:_SetPlayerStep(flight,AIRBOSS.PatternStep.HOLDING)
flight.holding=nil
flight.case=playerData.case
flight.flag=stack
self:Marshal(flight)
end
else
self:E(self.lid.."ERROR: Could not add player to Marshal stack! playerData=nil")
end
end
function AIRBOSS:_WaitAI(flight,respawn)
flight.flag=-99
table.insert(self.Qwaiting,flight)
local group=flight.group
local groupname=flight.groupname
local speedOrbitMps=UTILS.KnotsToMps(274)
local speedOrbitKmh=UTILS.KnotsToKmph(274)
local speedTransit=UTILS.KnotsToKmph(370)
local cv=self:GetCoordinate()
local fc=group:GetCoordinate()
local hdg=self:GetHeading(false)
local hdgto=cv:HeadingTo(fc)
local angels=math.random(6,10)
local altitude=UTILS.FeetToMeters(angels*1000)
local p0=cv:Translate(UTILS.NMToMeters(11),hdgto):Translate(UTILS.NMToMeters(5),hdg):SetAltitude(altitude)
local wp={}
wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil,speedTransit,{},"Current Position")
local taskorbit=group:TaskOrbit(p0,altitude,speedOrbitMps)
wp[#wp+1]=p0:WaypointAirTurningPoint(nil,speedOrbitKmh,{taskorbit},string.format("Waiting Orbit at Angels %d",angels))
if self.Debug then
p0:MarkToAll(string.format("Waiting Orbit of flight %s at Angels %s",groupname,angels))
end
if respawn then
local Template=group:GetTemplate()
Template.route.points=wp
group=group:Respawn(Template,true)
end
group:WayPointInitialize(wp)
group:Route(wp,1)
end
function AIRBOSS:_MarshalAI(flight,nstack,respawn)
self:F2({flight=flight,nstack=nstack,respawn=respawn})
if flight==nil or flight.group==nil then
self:E(self.lid.."ERROR: flight or flight.group is nil.")
return
end
if flight.group:GetCoordinate()==nil then
self:E(self.lid.."ERROR: cannot get coordinate of flight group.")
return
end
if not self:_InQueue(self.Qmarshal,flight.group)then
if self.xtVoiceOversAI then
local leader=flight.group:GetUnits()[1]
self:_MarshallInboundCall(leader,flight.onboard)
end
self:_AddMarshalGroup(flight,nstack)
end
local case=flight.case
local ostack=flight.flag
local group=flight.group
local groupname=flight.groupname
flight.flag=nstack
local Carrier=self:GetCoordinate()
local hdg=self:GetHeading()
local speedOrbitMps=UTILS.KnotsToMps(274)
local speedOrbitKmh=UTILS.KnotsToKmph(274)
local speedTransit=UTILS.KnotsToKmph(370)
local altitude
local p0
local p1
local p2
altitude,p1,p2=self:_GetMarshalAltitude(nstack,case)
local wp={}
if not flight.holding then
self:T(self.lid..string.format("Guiding AI flight %s to marshal stack %d-->%d.",groupname,ostack,nstack))
wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil,speedTransit,{},"Current Position")
local TaskArrivedHolding=flight.group:TaskFunction("AIRBOSS._ReachedHoldingZone",self,flight)
if case==1 then
local pE=Carrier:Translate(UTILS.NMToMeters(7),hdg-30):SetAltitude(altitude)
p0=Carrier:Translate(UTILS.NMToMeters(5),hdg-135):SetAltitude(altitude)
wp[#wp+1]=pE:WaypointAirTurningPoint(nil,speedTransit,{TaskArrivedHolding},"Entering Case I Marshal Pattern")
else
local radial=self:GetRadial(case,false,true)
p0=p2:Translate(UTILS.NMToMeters(5),radial+90,true):Translate(UTILS.NMToMeters(5),radial,true)
wp[#wp+1]=p0:WaypointAirTurningPoint(nil,speedTransit,{TaskArrivedHolding},"Entering Case II/III Marshal Pattern")
end
else
self:T(self.lid..string.format("Updating AI flight %s at marshal stack %d-->%d.",groupname,ostack,nstack))
wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil,speedOrbitKmh,{},"Current Position")
p0=group:GetCoordinate():Translate(UTILS.NMToMeters(0.2),group:GetHeading(),true)
end
local taskorbit=group:TaskOrbit(p1,altitude,speedOrbitMps,p2)
wp[#wp+1]=p0:WaypointAirTurningPoint(nil,speedOrbitKmh,{taskorbit},string.format("Marshal Orbit Stack %d",nstack))
if self.Debug then
p0:MarkToAll("WP P0 "..groupname)
p1:MarkToAll("RT P1 "..groupname)
p2:MarkToAll("RT P2 "..groupname)
end
if respawn then
local Template=group:GetTemplate()
Template.route.points=wp
flight.group=group:Respawn(Template,true)
end
flight.group:WayPointInitialize(wp)
flight.group:Route(wp,1)
self:Marshal(flight)
end
function AIRBOSS:_RefuelAI(flight)
local wp={}
local CurrentSpeed=flight.group:GetVelocityKMH()
wp[#wp+1]=flight.group:GetCoordinate():WaypointAirTurningPoint(nil,CurrentSpeed,{},"Current position")
local refuelac=false
local actype=flight.group:GetTypeName()
if actype==AIRBOSS.AircraftCarrier.AV8B or
actype==AIRBOSS.AircraftCarrier.F14A or
actype==AIRBOSS.AircraftCarrier.F14B or
actype==AIRBOSS.AircraftCarrier.F14A_AI or
actype==AIRBOSS.AircraftCarrier.HORNET or
actype==AIRBOSS.AircraftCarrier.RHINOE or
actype==AIRBOSS.AircraftCarrier.RHINOF or
actype==AIRBOSS.AircraftCarrier.GROWLER or
actype==AIRBOSS.AircraftCarrier.FA18C or
actype==AIRBOSS.AircraftCarrier.S3B or
actype==AIRBOSS.AircraftCarrier.S3BTANKER then
refuelac=true
end
local text=""
if self.tanker and refuelac then
local tankerpos=self.tanker.tanker:GetCoordinate()
local TaskRefuel=flight.group:TaskRefueling()
local TaskMarshal=flight.group:TaskFunction("AIRBOSS._TaskFunctionMarshalAI",self,flight)
wp[#wp+1]=tankerpos:WaypointAirTurningPoint(nil,CurrentSpeed,{TaskRefuel,TaskMarshal},"Refueling")
self:_MarshalCallGasAtTanker(flight.onboard)
else
local divertfield=self:GetCoordinate():GetClosestAirbase(Airbase.Category.AIRDROME,self:GetCoalition())
if divertfield==nil then
divertfield=self:GetCoordinate():GetClosestAirbase(Airbase.Category.AIRDROME,0)
end
if divertfield then
local divertcoord=divertfield:GetCoordinate()
wp[#wp+1]=divertcoord:WaypointAirLanding(UTILS.KnotsToKmph(300),divertfield,{},"Divert Field")
self:_MarshalCallGasAtDivert(flight.onboard,divertfield:GetName())
local Template=flight.group:GetTemplate()
Template.route.points=wp
flight.group=flight.group:Respawn(Template,true)
else
self:E(self.lid..string.format("WARNING: No recovery tanker or divert field available for group %s.",flight.groupname))
flight.refueling=true
return
end
end
flight.group:WayPointInitialize(wp)
flight.group:Route(wp,1)
flight.refueling=true
end
function AIRBOSS:_LandAI(flight)
self:T(self.lid..string.format("Landing AI flight %s.",flight.groupname))
local Speed=UTILS.KnotsToKmph(200)
if flight.actype==AIRBOSS.AircraftCarrier.HORNET
or flight.actype==AIRBOSS.AircraftCarrier.FA18C
or flight.actype==AIRBOSS.AircraftCarrier.RHINOE
or flight.actype==AIRBOSS.AircraftCarrier.RHINOF
or flight.actype==AIRBOSS.AircraftCarrier.GROWLER then
Speed=UTILS.KnotsToKmph(200)
elseif flight.actype==AIRBOSS.AircraftCarrier.E2D then
Speed=UTILS.KnotsToKmph(150)
elseif flight.actype==AIRBOSS.AircraftCarrier.F14A_AI or flight.actype==AIRBOSS.AircraftCarrier.F14A or flight.actype==AIRBOSS.AircraftCarrier.F14B then
Speed=UTILS.KnotsToKmph(175)
elseif flight.actype==AIRBOSS.AircraftCarrier.S3B or flight.actype==AIRBOSS.AircraftCarrier.S3BTANKER then
Speed=UTILS.KnotsToKmph(140)
end
local Carrier=self:GetCoordinate()
local hdg=self:GetHeading()
local wp={}
local CurrentSpeed=flight.group:GetVelocityKMH()
wp[#wp+1]=flight.group:GetCoordinate():WaypointAirTurningPoint(nil,CurrentSpeed,{},"Current position")
local alt=UTILS.FeetToMeters(800)
wp[#wp+1]=Carrier:Translate(UTILS.NMToMeters(4),hdg-160):SetAltitude(alt):WaypointAirLanding(Speed,self.airbase,nil,"Landing")
flight.group:WayPointInitialize(wp)
flight.group:Route(wp,0)
end
function AIRBOSS:_GetMarshalAltitude(stack,case)
if stack<=0 then
return 0,nil,nil
end
case=case or self.case
local Carrier=self:GetCoordinate()
local angels0
local Dist
local p1=nil
local p2=nil
local nstack=stack-1
if case==1 then
angels0=2
local hdg=self.carrier:GetHeading()
p1=Carrier
p2=Carrier:Translate(UTILS.NMToMeters(1.5),hdg)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
p1=Carrier:Translate(UTILS.NMToMeters(1.0),hdg+90)
p2=p1:Translate(2.5,hdg)
end
else
angels0=6
Dist=UTILS.NMToMeters(nstack+angels0+15)
local radial=self:GetRadial(case,false,true)
local l=UTILS.NMToMeters(10)
p1=Carrier:Translate(Dist+l,radial)
p2=Carrier:Translate(Dist,radial)
end
local altitude=UTILS.FeetToMeters((nstack+angels0)*1000)
p1:SetAltitude(altitude,true)
p2:SetAltitude(altitude,true)
return altitude,p1,p2
end
function AIRBOSS:_GetCharlieTime(flightgroup)
local stack=flightgroup.flag
if stack<=0 then
return nil
end
local Tnow=timer.getAbsTime()
local Tcharlie=0
local Trecovery=0
if self.recoverywindow then
Trecovery=math.max(self.recoverywindow.START-Tnow,0)
else
Trecovery=7*60
end
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
local mstack=flight.flag
local Tarrive=0
local Tholding=3*60
if stack>0 and mstack>0 and mstack<=stack then
if flight.holding==nil then
local holdingzone=self:_GetZoneHolding(flight.case,1):GetCoordinate()
local d0=holdingzone:Get2DDistance(flight.group:GetCoordinate())
local v0=flight.group:GetVelocityMPS()
Tarrive=d0/v0
self:T3(self.lid..string.format("Tarrive=%.1f seconds, Clock %s",Tarrive,UTILS.SecondsToClock(Tnow+Tarrive)))
else
if mstack==1 then
local tholding=timer.getAbsTime()-flight.time
Tholding=math.max(3*60-tholding,0)
end
end
local Tmin=math.max(Tarrive,Trecovery)
Tcharlie=math.max(Tmin,Tcharlie)+Tholding
end
end
Tcharlie=Tcharlie+Tnow
local text=string.format("Charlie time for flight %s (%s) %s",flightgroup.onboard,flightgroup.groupname,UTILS.SecondsToClock(Tcharlie))
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
return Tcharlie
end
function AIRBOSS:_AddMarshalGroup(flight,stack)
flight.flag=stack
flight.case=self.case
table.insert(self.Qmarshal,flight)
local P=UTILS.hPa2inHg(self:GetCoordinate():GetPressure())
local alt=self:_GetMarshalAltitude(stack,flight.case)
local brc=self:GetBRC()
if self.recoverywindow and self.recoverywindow.WIND then
brc=self:GetBRCintoWind()
end
flight.Tcharlie=self:_GetCharlieTime(flight)
local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie)
self:_MarshalCallArrived(flight.onboard,flight.case,brc,alt,Ccharlie,P)
if self.TACANon and(not flight.ai)and flight.difficulty==AIRBOSS.Difficulty.EASY then
local radial=self:GetRadial(flight.case,true,true,true)
if flight.case==1 then
radial=self:GetBRC()
end
local text=string.format("Select TACAN %03d°, channel %d%s (%s)",radial,self.TACANchannel,self.TACANmode,self.TACANmorse)
self:MessageToPlayer(flight,text,nil,"")
end
end
function AIRBOSS:_CollapseMarshalStack(flight,nopattern)
self:F2({flight=flight,nopattern=nopattern})
local case=flight.case
local stack=flight.flag
if stack<=0 then
self:E(self.lid..string.format("ERROR: Flight %s is has stack value %d<0. Cannot collapse stack!",flight.groupname,stack))
return
end
self.Tcollapse=timer.getTime()
for _,_flight in pairs(self.Qmarshal)do
local mflight=_flight
if(case==1 and mflight.case==1)then
local mstack=mflight.flag
if mstack>stack then
local newstack=self:_GetFreeStack(mflight.ai,mflight.case,true)
if newstack and newstack<mstack then
self:T(self.lid..string.format("Collapse Marshal: Flight %s (case %d) is changing marshal stack %d --> %d.",mflight.groupname,mflight.case,mstack,newstack))
if mflight.ai then
self:_MarshalAI(mflight,newstack)
else
mflight.flag=newstack
local angels=self:_GetAngels(self:_GetMarshalAltitude(newstack,case))
if mflight.difficulty~=AIRBOSS.Difficulty.HARD then
local text=string.format("descent to stack at Angels %d.",angels)
self:MessageToPlayer(mflight,text,"MARSHAL")
end
mflight.time=timer.getAbsTime()
for _,_sec in pairs(mflight.section)do
local sec=_sec
sec.flag=newstack
sec.time=timer.getAbsTime()
if sec.difficulty~=AIRBOSS.Difficulty.HARD then
local text=string.format("descent to stack at Angels %d.",angels)
self:MessageToPlayer(sec,text,"MARSHAL")
end
end
end
end
end
end
end
if nopattern then
self:T(self.lid..string.format("Flight %s is leaving stack but not going to pattern.",flight.groupname))
else
local Tmarshal=UTILS.SecondsToClock(timer.getAbsTime()-flight.time)
self:T(self.lid..string.format("Flight %s is leaving marshal after %s and going pattern.",flight.groupname,Tmarshal))
self:_AddFlightToPatternQueue(flight)
end
flight.flag=-1
flight.time=timer.getAbsTime()
end
function AIRBOSS:_GetFreeStack(ai,case,empty)
case=case or self.case
if case==1 then
return self:_GetFreeStack_Old(ai,case,empty)
end
local nmaxstacks=100
if case==1 then
nmaxstacks=self.Nmaxmarshal
end
local stack={}
for i=1,nmaxstacks do
stack[i]=self.NmaxStack
end
local nmax=1
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
if flight.case==case then
local n=flight.flag
if n>nmax then
nmax=n
end
if n>0 then
if flight.ai or flight.case>1 then
stack[n]=0
else
stack[n]=stack[n]-1
end
else
self:E(string.format("ERROR: Flight %s in marshal stack has stack value <= 0. Stack value is %d.",flight.groupname,n))
end
end
end
local nfree=nil
if stack[nmax]==0 then
if case==1 then
if nmax>=nmaxstacks then
nfree=nil
else
nfree=nmax+1
end
else
nfree=nmax+1
end
elseif stack[nmax]==self.NmaxStack then
self:E(self.lid..string.format("ERROR: Max occupied stack is empty. Should not happen! Nmax=%d, stack[nmax]=%d",nmax,stack[nmax]))
nfree=nmax
else
if ai or empty or case>1 then
nfree=nmax+1
else
nfree=nmax
end
end
self:I(self.lid..string.format("Returning free stack %s",tostring(nfree)))
return nfree
end
function AIRBOSS:_GetFreeStack_Old(ai,case,empty)
case=case or self.case
local nmaxstacks=100
if case==1 then
nmaxstacks=self.Nmaxmarshal
end
local stack={}
for i=1,nmaxstacks do
stack[i]=self.NmaxStack
end
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
if flight.case==case then
local n=flight.flag
if n>0 then
if flight.ai or flight.case>1 then
stack[n]=0
else
stack[n]=stack[n]-1
end
else
self:E(string.format("ERROR: Flight %s in marshal stack has stack value <= 0. Stack value is %d.",flight.groupname,n))
end
end
end
local nfree=nil
for i=1,nmaxstacks do
self:T2(self.lid..string.format("FF Stack[%d]=%d",i,stack[i]))
if ai or empty or case>1 then
if stack[i]==self.NmaxStack then
nfree=i
return i
end
else
if stack[i]>0 then
nfree=i
return i
end
end
end
return nfree
end
function AIRBOSS:_GetFlightUnits(flight,onground)
local inair=true
if onground==true then
inair=false
end
local function countunits(_group,inair)
local group=_group
local units=group:GetUnits()
local n=0
if units then
for _,_unit in pairs(units)do
local unit=_unit
if unit and unit:IsAlive()then
if inair then
if unit:InAir()then
self:T2(self.lid..string.format("Unit %s is in AIR",unit:GetName()))
n=n+1
end
else
n=n+1
end
end
end
end
return n
end
local nunits=countunits(flight.group,inair)
local nsection=0
for _,sec in pairs(flight.section)do
local secflight=sec
nsection=nsection+countunits(secflight.group,inair)
end
return nunits+nsection,nunits,nsection
end
function AIRBOSS:_GetQueueInfo(queue,case)
local ngroup=0
local Nunits=0
for _,_flight in pairs(queue)do
local flight=_flight
if case then
if(flight.case==case)or(case==23 and(flight.case==2 or flight.case==3))then
local ntot,nunits,nsection=self:_GetFlightUnits(flight)
Nunits=Nunits+ntot
if ntot>0 then
ngroup=ngroup+1
end
end
else
local ntot,nunits,nsection=self:_GetFlightUnits(flight)
Nunits=Nunits+ntot
if ntot>0 then
ngroup=ngroup+1
end
end
end
return ngroup,Nunits
end
function AIRBOSS:_PrintQueue(queue,name)
local Nqueue,nqueue=self:_GetQueueInfo(queue)
local text=string.format("%s Queue N=%d (#%d), n=%d:",name,Nqueue,#queue,nqueue)
if#queue==0 then
text=text.." empty."
else
for i,_flight in pairs(queue)do
local flight=_flight
local clock=UTILS.SecondsToClock(timer.getAbsTime()-flight.time)
local case=flight.case
local stack=flight.flag
local fuel=flight.group:GetFuelMin()*100
local ai=tostring(flight.ai)
local lead=flight.seclead
local Nsec=#flight.section
local actype=self:_GetACNickname(flight.actype)
local onboard=flight.onboard
local holding=tostring(flight.holding)
local _,nunits,nsec=self:_GetFlightUnits(flight,false)
text=text..string.format("\n[%d] %s*%d (%s): lead=%s (%d/%d), onboard=%s, flag=%d, case=%d, time=%s, fuel=%d, ai=%s, holding=%s",i,flight.groupname,nunits,actype,lead,nsec,Nsec,onboard,stack,case,clock,fuel,ai,holding)
if stack>0 then
local alt=UTILS.MetersToFeet(self:_GetMarshalAltitude(stack,case))
text=text..string.format(" stackalt=%d ft",alt)
end
for j,_element in pairs(flight.elements)do
local element=_element
text=text..string.format("\n  (%d) %s (%s): ai=%s, ballcall=%s, recovered=%s",j,element.onboard,element.unitname,tostring(element.ai),tostring(element.ballcall),tostring(element.recovered))
end
end
end
self:T(self.lid..text)
end
function AIRBOSS:_CreateFlightGroup(group)
self:T(self.lid..string.format("Creating new flight for group %s of aircraft type %s.",group:GetName(),group:GetTypeName()))
local flight={}
if not self:_InQueue(self.flights,group)then
local groupname=group:GetName()
local human,playername=self:_IsHuman(group)
flight.group=group
flight.groupname=group:GetName()
flight.nunits=#group:GetUnits()
flight.time=timer.getAbsTime()
flight.dist0=group:GetCoordinate():Get2DDistance(self:GetCoordinate())
flight.flag=-100
flight.ai=not human
flight.actype=group:GetTypeName()
flight.onboardnumbers=self:_GetOnboardNumbers(group)
flight.seclead=flight.group:GetUnit(1):GetName()
flight.section={}
flight.ballcall=false
flight.refueling=false
flight.holding=nil
flight.name=flight.group:GetUnit(1):GetName()
flight.case=self.case
local text=string.format("Flight elements of group %s:",flight.groupname)
flight.elements={}
local units=group:GetUnits()
for i,_unit in pairs(units)do
local unit=_unit
local element={}
element.unit=unit
element.unitname=unit:GetName()
element.onboard=flight.onboardnumbers[element.unitname]
element.ballcall=false
element.ai=not self:_IsHumanUnit(unit)
element.recovered=nil
text=text..string.format("\n[%d] %s onboard #%s, AI=%s",i,element.unitname,tostring(element.onboard),tostring(element.ai))
table.insert(flight.elements,element)
end
self:T(self.lid..text)
if flight.ai then
local onboard=flight.onboardnumbers[flight.seclead]
flight.onboard=onboard
else
flight.onboard=self:_GetOnboardNumberPlayer(group)
end
table.insert(self.flights,flight)
else
self:E(self.lid..string.format("ERROR: Flight group %s already exists in self.flights!",group:GetName()))
return nil
end
return flight
end
function AIRBOSS:_NewPlayer(unitname)
local playerunit,playername=self:_GetPlayerUnitAndName(unitname)
if playerunit and playername then
local group=playerunit:GetGroup()
local playerData
playerData=self:_CreateFlightGroup(group)
if playerData then
playerData.unit=playerunit
playerData.unitname=unitname
playerData.name=playername
playerData.callsign=playerData.unit:GetCallsign()
playerData.client=CLIENT:FindByName(unitname,nil,true)
playerData.seclead=playername
playerData.passes=0
playerData.messages={}
playerData.lastdebrief=playerData.lastdebrief or{}
playerData.attitudemonitor=false
if playerData.trapon==nil then
playerData.trapon=self.trapsheet
end
playerData.difficulty=playerData.difficulty or self.defaultskill
if playerData.subtitles==nil then
playerData.subtitles=true
end
if playerData.showhints==nil then
if playerData.difficulty==AIRBOSS.Difficulty.HARD then
playerData.showhints=false
else
playerData.showhints=true
end
end
playerData.points={}
playerData=self:_InitPlayer(playerData)
self.players[playername]=playerData
self.playerscores[playername]=self.playerscores[playername]or{}
if self.welcome then
self:MessageToPlayer(playerData,string.format("Welcome, %s %s!",playerData.difficulty,playerData.name),string.format("AIRBOSS %s",self.alias),"",5)
end
end
return playerData
end
return nil
end
function AIRBOSS:_InitPlayer(playerData,step)
self:T(self.lid..string.format("Initializing player data for %s callsign %s.",playerData.name,playerData.callsign))
playerData.step=step or AIRBOSS.PatternStep.UNDEFINED
playerData.groove={}
playerData.debrief={}
playerData.trapsheet={}
playerData.warning=nil
playerData.holding=nil
playerData.refueling=false
playerData.valid=false
playerData.lig=false
playerData.wop=false
playerData.waveoff=false
playerData.wofd=false
playerData.owo=false
playerData.boltered=false
playerData.hover=false
playerData.stable=false
playerData.landed=false
playerData.Tlso=timer.getTime()
playerData.Tgroove=nil
playerData.TIG0=nil
playerData.wire=nil
playerData.flag=-100
playerData.debriefschedulerID=nil
if playerData.group:GetName():match("Groove")and playerData.passes==0 then
self:MessageToPlayer(playerData,"Group name contains \"Groove\". Happy groove testing.")
playerData.attitudemonitor=true
playerData.step=AIRBOSS.PatternStep.FINAL
self:_AddFlightToPatternQueue(playerData)
self.dTstatus=0.1
end
return playerData
end
function AIRBOSS:_GetFlightFromGroupInQueue(group,queue)
if group then
local name=group:GetName()
for i,_flight in pairs(queue)do
local flight=_flight
if flight.groupname==name then
return flight,i
end
end
self:T2(self.lid..string.format("WARNING: Flight group %s could not be found in queue.",name))
end
self:T2(self.lid..string.format("WARNING: Flight group could not be found in queue. Group is nil!"))
return nil,nil
end
function AIRBOSS:_GetFlightElement(unitname)
local unit=UNIT:FindByName(unitname)
if unit then
local flight=self:_GetFlightFromGroupInQueue(unit:GetGroup(),self.flights)
if flight then
for i,_element in pairs(flight.elements)do
local element=_element
if element.unit:GetName()==unitname then
return element,i,flight
end
end
self:T2(self.lid..string.format("WARNING: Flight element %s could not be found in flight group.",unitname,flight.groupname))
end
end
return nil,nil,nil
end
function AIRBOSS:_RemoveFlightElement(unitname)
local element,idx,flight=self:_GetFlightElement(unitname)
if idx then
table.remove(flight.elements,idx)
return true
else
self:T("WARNING: Flight element could not be removed from flight group. Index=nil!")
return nil
end
end
function AIRBOSS:_InQueue(queue,group)
local name=group:GetName()
for _,_flight in pairs(queue)do
local flight=_flight
if name==flight.groupname then
return true
end
end
return false
end
function AIRBOSS:_RemoveDeadFlightGroups()
for i=#self.flight,1,-1 do
local flight=self.flights[i]
if not flight.group:IsAlive()then
self:T(string.format("Removing dead flight group %s from ALL flights table.",flight.groupname))
table.remove(self.flights,i)
end
end
for i=#self.Qmarshal,1,-1 do
local flight=self.Qmarshal[i]
if not flight.group:IsAlive()then
self:T(string.format("Removing dead flight group %s from Marshal Queue table.",flight.groupname))
table.remove(self.Qmarshal,i)
end
end
for i=#self.Qpattern,1,-1 do
local flight=self.Qpattern[i]
if not flight.group:IsAlive()then
self:T(string.format("Removing dead flight group %s from Pattern Queue table.",flight.groupname))
table.remove(self.Qpattern,i)
end
end
end
function AIRBOSS:_GetLeadFlight(flight)
local lead=flight
if flight.name~=flight.seclead then
lead=self.players[flight.seclead]
end
return lead
end
function AIRBOSS:_CheckSectionRecovered(flight)
if flight==nil then
return true
end
local lead=self:_GetLeadFlight(flight)
for _,_element in pairs(lead.elements)do
local element=_element
if not element.recovered then
return false
end
end
for _,_section in pairs(lead.section)do
local sectionmember=_section
for _,_element in pairs(sectionmember.elements)do
local element=_element
if not element.recovered then
return false
end
end
end
self:_RemoveFlightFromQueue(self.Qpattern,lead)
if self:_InQueue(self.Qmarshal,lead.group)then
self:E(self.lid..string.format("ERROR: lead flight group %s should not be in marshal queue",lead.groupname))
self:_RemoveFlightFromMarshalQueue(lead,true)
end
if self:_InQueue(self.Qwaiting,lead.group)then
self:E(self.lid..string.format("ERROR: lead flight group %s should not be in pattern queue",lead.groupname))
self:_RemoveFlightFromQueue(self.Qwaiting,lead)
end
return true
end
function AIRBOSS:_AddFlightToPatternQueue(flight)
table.insert(self.Qpattern,flight)
flight.flag=-1
flight.time=timer.getAbsTime()
flight.recovered=false
for _,elem in pairs(flight.elements)do
elem.recoverd=false
end
for _,sec in pairs(flight.section)do
sec.flag=-1
sec.time=timer.getAbsTime()
for _,elem in pairs(sec.elements)do
elem.recoverd=false
end
end
end
function AIRBOSS:_RecoveredElement(unit)
local element,idx,flight=self:_GetFlightElement(unit:GetName())
if element then
element.recovered=true
end
return flight
end
function AIRBOSS:_RemoveFlightFromMarshalQueue(flight,nopattern)
local removed,idx=self:_RemoveFlightFromQueue(self.Qmarshal,flight)
if removed then
flight.holding=nil
self:_CollapseMarshalStack(flight,nopattern)
if flight.case==1 and#self.Qwaiting>0 then
local nextflight=self.Qwaiting[1]
local freestack=self:_GetFreeStack(nextflight.ai)
if nextflight.ai then
self:_MarshalAI(nextflight,freestack)
else
self:_MarshalPlayer(nextflight,freestack)
end
self:_RemoveFlightFromQueue(self.Qwaiting,nextflight)
end
end
return removed,idx
end
function AIRBOSS:_RemoveFlightFromQueue(queue,flight)
for i,_flight in pairs(queue)do
local qflight=_flight
if qflight.groupname==flight.groupname then
self:T(self.lid..string.format("Removing flight group %s from queue.",flight.groupname))
table.remove(queue,i)
return true,i
end
end
return false,nil
end
function AIRBOSS:_RemoveUnitFromFlight(unit)
if unit and unit:IsInstanceOf("UNIT")then
local group=unit:GetGroup()
if group then
local flight=self:_GetFlightFromGroupInQueue(group,self.flights)
if flight then
local removed=self:_RemoveFlightElement(unit:GetName())
if removed then
local _,nunits=self:_GetFlightUnits(flight,not flight.ai)
local nelements=#flight.elements
self:T(self.lid..string.format("Removed unit %s: nunits=%d, nelements=%d",unit:GetName(),nunits,nelements))
if nunits==0 or nelements==0 then
self:_RemoveFlight(flight)
end
end
end
end
end
end
function AIRBOSS:_RemoveFlightFromSection(flight)
if flight.name~=flight.seclead then
local lead=self.players[flight.seclead]
if lead then
for i,sec in pairs(lead.section)do
local sectionmember=sec
if sectionmember.name==flight.name then
table.remove(lead.section,i)
break
end
end
end
end
end
function AIRBOSS:_UpdateFlightSection(flight)
if flight.seclead==flight.name then
if#flight.section>=1 then
local newlead=flight.section[1]
newlead.seclead=newlead.name
for i=2,#flight.section do
local member=flight.section[i]
table.insert(newlead.section,member)
member.seclead=newlead.name
end
end
flight.section={}
else
self:_RemoveFlightFromSection(flight)
end
end
function AIRBOSS:_RemoveFlight(flight,completely)
self:F(self.lid..string.format("Removing flight %s, ai=%s completely=%s.",tostring(flight.groupname),tostring(flight.ai),tostring(completely)))
self:_RemoveFlightFromMarshalQueue(flight,true)
self:_RemoveFlightFromQueue(self.Qpattern,flight)
self:_RemoveFlightFromQueue(self.Qwaiting,flight)
self:_RemoveFlightFromQueue(self.Qspinning,flight)
if flight.ai then
self:_RemoveFlightFromQueue(self.flights,flight)
else
local grades=self.playerscores[flight.name]
if grades and#grades>0 then
while#grades>0 and grades[#grades].finalscore==nil do
table.remove(grades,#grades)
end
end
if completely then
self:_UpdateFlightSection(flight)
self:_RemoveFlightFromQueue(self.flights,flight)
local playerdata=self.players[flight.name]
if playerdata then
self:I(self.lid..string.format("Removing player %s completely.",flight.name))
self.players[flight.name]=nil
end
flight=nil
else
self:_SetPlayerStep(flight,AIRBOSS.PatternStep.UNDEFINED)
for _,sectionmember in pairs(flight.section)do
self:_SetPlayerStep(sectionmember,AIRBOSS.PatternStep.UNDEFINED)
self:_RemoveFlightFromQueue(self.Qspinning,sectionmember)
end
self:_RemoveFlightFromSection(flight)
end
end
end
function AIRBOSS:_CheckPlayerStatus()
for _playerName,_playerData in pairs(self.players)do
local playerData=_playerData
if playerData then
local unit=playerData.unit
if unit and unit:IsAlive()then
if unit:IsInZone(self.zoneCCA)then
if playerData.attitudemonitor then
self:_AttitudeMonitor(playerData)
end
self:_CheckPlayerPatternDistance(playerData)
self:_CheckFoulDeck(playerData)
if playerData.step==AIRBOSS.PatternStep.UNDEFINED then
elseif playerData.step==AIRBOSS.PatternStep.REFUELING then
elseif playerData.step==AIRBOSS.PatternStep.SPINNING then
self:_Spinning(playerData)
elseif playerData.step==AIRBOSS.PatternStep.HOLDING then
self:_Holding(playerData)
elseif playerData.step==AIRBOSS.PatternStep.WAITING then
self:_Waiting(playerData)
elseif playerData.step==AIRBOSS.PatternStep.COMMENCING then
self:_Commencing(playerData,true)
elseif playerData.step==AIRBOSS.PatternStep.BOLTER then
self:_BolterPattern(playerData)
elseif playerData.step==AIRBOSS.PatternStep.PLATFORM then
self:_Platform(playerData)
elseif playerData.step==AIRBOSS.PatternStep.ARCIN then
self:_ArcInTurn(playerData)
elseif playerData.step==AIRBOSS.PatternStep.ARCOUT then
self:_ArcOutTurn(playerData)
elseif playerData.step==AIRBOSS.PatternStep.DIRTYUP then
self:_DirtyUp(playerData)
elseif playerData.step==AIRBOSS.PatternStep.BULLSEYE then
self:_Bullseye(playerData)
elseif playerData.step==AIRBOSS.PatternStep.INITIAL then
self:_Initial(playerData)
elseif playerData.step==AIRBOSS.PatternStep.BREAKENTRY then
self:_BreakEntry(playerData)
elseif playerData.step==AIRBOSS.PatternStep.EARLYBREAK then
self:_Break(playerData,AIRBOSS.PatternStep.EARLYBREAK)
elseif playerData.step==AIRBOSS.PatternStep.LATEBREAK then
self:_Break(playerData,AIRBOSS.PatternStep.LATEBREAK)
elseif playerData.step==AIRBOSS.PatternStep.ABEAM then
self:_Abeam(playerData)
elseif playerData.step==AIRBOSS.PatternStep.NINETY then
self:_CheckForLongDownwind(playerData)
self:_Ninety(playerData)
elseif playerData.step==AIRBOSS.PatternStep.WAKE then
self:_Wake(playerData)
elseif playerData.step==AIRBOSS.PatternStep.EMERGENCY then
self:_Final(playerData,true)
elseif playerData.step==AIRBOSS.PatternStep.FINAL then
self:_Final(playerData)
elseif playerData.step==AIRBOSS.PatternStep.GROOVE_XX or
playerData.step==AIRBOSS.PatternStep.GROOVE_IM or
playerData.step==AIRBOSS.PatternStep.GROOVE_IC or
playerData.step==AIRBOSS.PatternStep.GROOVE_AR or
playerData.step==AIRBOSS.PatternStep.GROOVE_AL or
playerData.step==AIRBOSS.PatternStep.GROOVE_LC or
playerData.step==AIRBOSS.PatternStep.GROOVE_IW then
self:_Groove(playerData)
elseif playerData.step==AIRBOSS.PatternStep.DEBRIEF then
playerData.debriefschedulerID=self:ScheduleOnce(5,self._Debrief,self,playerData)
playerData.step=AIRBOSS.PatternStep.UNDEFINED
else
self:E(self.lid..string.format("ERROR: Unknown player step %s. Please report!",tostring(playerData.step)))
end
self:_CheckMissedStepOnEntry(playerData)
else
self:T2(self.lid.."WARNING: Player unit not inside the CCA!")
end
else
self:T(self.lid.."WARNING: Player unit is not alive!")
end
end
end
end
function AIRBOSS:_CheckMissedStepOnEntry(playerData)
local rightcase=playerData.case>1
local rightqueue=self:_InQueue(self.Qpattern,playerData.group)
local rightflag=playerData.flag~=-42
local step=playerData.step
local missedstep=step==AIRBOSS.PatternStep.PLATFORM or step==AIRBOSS.PatternStep.ARCIN or step==AIRBOSS.PatternStep.ARCOUT or step==AIRBOSS.PatternStep.DIRTYUP
if rightcase and rightqueue and rightflag then
local zone=nil
if playerData.case==2 and missedstep then
zone=self:_GetZoneInitial(playerData.case)
elseif playerData.case==3 and missedstep then
zone=self:_GetZoneBullseye(playerData.case)
end
if zone then
local inzone=playerData.unit:IsInZone(zone)
local relheading=self:_GetRelativeHeading(playerData.unit,false)
if inzone and math.abs(relheading)<60 then
local text=string.format("you missed an important step in the pattern!\nYour next step would have been %s.",playerData.step)
self:MessageToPlayer(playerData,text,"AIRBOSS",nil,5)
if playerData.case==2 then
playerData.step=AIRBOSS.PatternStep.INITIAL
elseif playerData.case==3 then
playerData.step=AIRBOSS.PatternStep.BULLSEYE
end
playerData.flag=-42
end
end
end
end
function AIRBOSS:_SetTimeInGroove(playerData)
if playerData.TIG0 then
playerData.Tgroove=timer.getTime()-playerData.TIG0
else
playerData.Tgroove=999
end
end
function AIRBOSS:_GetTimeInGroove(playerData)
local Tgroove=999
if playerData.TIG0 then
Tgroove=timer.getTime()-playerData.TIG0
end
return Tgroove
end
function AIRBOSS:OnEventBirth(EventData)
self:F3({eventbirth=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event BIRTH!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T(self.lid.."BIRTH: unit   = "..tostring(EventData.IniUnitName))
self:T(self.lid.."BIRTH: group  = "..tostring(EventData.IniGroupName))
self:T(self.lid.."BIRTH: player = "..tostring(_playername))
if _unit and _playername then
local _uid=_unit:GetID()
local _group=_unit:GetGroup()
local _callsign=_unit:GetCallsign()
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.",_playername,_callsign,_unitName,_group:GetName())
self:T(self.lid..text)
MESSAGE:New(text,5):ToAllIf(self.Debug)
local rightaircraft=self:_IsCarrierAircraft(_unit)
if rightaircraft==false then
local text=string.format("Player aircraft type %s not supported by AIRBOSS class.",_unit:GetTypeName())
MESSAGE:New(text,30):ToAllIf(self.Debug)
self:T2(self.lid..text)
return
end
if self:GetCoalition()~=_unit:GetCoalition()then
local text=string.format("Player entered aircraft of other coalition.")
MESSAGE:New(text,30):ToAllIf(self.Debug)
self:T(self.lid..text)
return
end
self:_AddF10Commands(_unitName)
self:ScheduleOnce(1,self._NewPlayer,self,_unitName)
end
end
function AIRBOSS:OnEventLand(EventData)
self:F3({eventland=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event LAND!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event LAND!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T(self.lid.."LAND: unit   = "..tostring(EventData.IniUnitName))
self:T(self.lid.."LAND: group  = "..tostring(EventData.IniGroupName))
self:T(self.lid.."LAND: player = "..tostring(_playername))
local airbase=EventData.Place
if airbase==nil then
return
end
local airbasename=tostring(airbase:GetName())
if airbasename==self.airbase:GetName()then
local stern=self:_GetSternCoord()
local zoneCarrier=self:_GetZoneCarrierBox()
if _unit and _playername then
local _uid=_unit:GetID()
local _group=_unit:GetGroup()
local _callsign=_unit:GetCallsign()
local text=string.format("Player %s, callsign %s unit %s (ID=%d) of group %s landed at airbase %s",_playername,_callsign,_unitName,_uid,_group:GetName(),airbasename)
self:T(self.lid..text)
MESSAGE:New(text,5,"DEBUG"):ToAllIf(self.Debug)
local playerData=self.players[_playername]
if playerData==nil then
self:E(self.lid..string.format("ERROR: playerData nil in landing event. unit=%s player=%s",tostring(_unitName),tostring(_playername)))
return
end
if _unit:IsInZone(zoneCarrier)then
if not playerData.valid then
local text=string.format("you missed at least one important step in the pattern!\nYour next step would have been %s.\nThis pass is INVALID.",playerData.step)
self:MessageToPlayer(playerData,text,"AIRBOSS",nil,30,true,5)
self:_RemoveFlightFromMarshalQueue(playerData,true)
self:_RemoveFlightFromQueue(self.Qpattern,playerData)
self:_RemoveFlightFromQueue(self.Qwaiting,playerData)
self:_RemoveFlightFromQueue(self.Qspinning,playerData)
self:_InitPlayer(playerData)
return
end
if playerData.landed then
self:E(self.lid..string.format("Player %s just landed a second time.",_playername))
else
playerData.landed=true
playerData.attitudemonitor=false
local coord=playerData.unit:GetCoordinate()
local X,Z,rho,phi=self:_GetDistances(_unit)
local dist=coord:Get2DDistance(stern)
if self.Debug and false then
local lp=coord:MarkToAll("Landing coord.")
coord:SmokeGreen()
end
self:_SetTimeInGroove(playerData)
local text=string.format("Player %s AC type %s landed at dist=%.1f m. Tgroove=%.1f sec.",playerData.name,playerData.actype,dist,self:_GetTimeInGroove(playerData))
text=text..string.format(" X=%.1f m, Z=%.1f m, rho=%.1f m.",X,Z,rho)
self:T(self.lid..text)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self:RadioTransmission(self.LSORadio,self.LSOCall.IDLE,false,1,nil,true)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF)
else
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.UNDEFINED)
self:ScheduleOnce(1,self._Trapped,self,playerData)
end
end
else
if playerData then
self:E(self.lid..string.format("Player %s did not land in carrier box zone. Maybe in the water near the carrier?",playerData.name))
end
end
else
if self.carriertype~=AIRBOSS.CarrierType.INVINCIBLE or self.carriertype~=AIRBOSS.CarrierType.HERMES or self.carriertype~=AIRBOSS.CarrierType.TARAWA or self.carriertype~=AIRBOSS.CarrierType.AMERICA or self.carriertype~=AIRBOSS.CarrierType.JCARLOS or self.carriertype~=AIRBOSS.CarrierType.CANBERRA then
local coord=EventData.IniUnit:GetCoordinate()
local dist=coord:Get2DDistance(self:GetCoordinate())
local wire=self:_GetWire(coord,0)
local _type=EventData.IniUnit:GetTypeName()
local text=string.format("AI unit %s of type %s landed at dist=%.1f m. Trapped wire=%d.",_unitName,_type,dist,wire)
self:T(self.lid..text)
end
local flight=self:_RecoveredElement(EventData.IniUnit)
self:_CheckSectionRecovered(flight)
end
end
end
function AIRBOSS:OnEventEngineShutdown(EventData)
self:F3({eventengineshutdown=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event ENGINESHUTDOWN!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event ENGINESHUTDOWN!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."ENGINESHUTDOWN: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."ENGINESHUTDOWN: group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."ENGINESHUTDOWN: player = "..tostring(_playername))
if _unit and _playername then
self:T(self.lid..string.format("Player %s shut down its engines!",_playername))
else
self:T(self.lid..string.format("AI unit %s shut down its engines!",_unitName))
local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights)
if flight and flight.ai then
local recovered=self:_CheckSectionRecovered(flight)
if recovered then
self:T(self.lid..string.format("AI group %s completely recovered. Despawning group after engine shutdown event as requested in 5 seconds.",tostring(EventData.IniGroupName)))
self:_RemoveFlight(flight)
local istanker=self.tanker and self.tanker.tanker:GetName()==EventData.IniGroupName
local isawacs=self.awacs and self.awacs.tanker:GetName()==EventData.IniGroupName
if self.despawnshutdown and not(istanker or isawacs)then
EventData.IniGroup:Destroy(nil,5)
end
end
end
end
end
function AIRBOSS:OnEventTakeoff(EventData)
self:F3({eventtakeoff=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event TAKEOFF!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event TAKEOFF!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."TAKEOFF: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."TAKEOFF: group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."TAKEOFF: player = "..tostring(_playername))
local airbase=EventData.Place
local airbasename="unknown"
if airbase then
airbasename=airbase:GetName()
end
if airbasename==self.airbase:GetName()then
if _unit and _playername then
self:T(self.lid..string.format("Player %s took off at %s!",_playername,airbasename))
else
self:T2(self.lid..string.format("AI unit %s took off at %s!",_unitName,airbasename))
local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights)
if flight then
for _,elem in pairs(flight.elements)do
local element=elem
element.ballcall=false
element.recovered=nil
end
end
end
end
end
function AIRBOSS:OnEventCrash(EventData)
self:F3({eventcrash=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event CRASH!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event CRASH!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."CRASH: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."CRASH: group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."CARSH: player = "..tostring(_playername))
if _unit and _playername then
self:T(self.lid..string.format("Player %s crashed!",_playername))
local flight=self.players[_playername]
if flight then
self:_RemoveFlight(flight,true)
end
else
self:T2(self.lid..string.format("AI unit %s crashed!",EventData.IniUnitName))
self:_RemoveUnitFromFlight(EventData.IniUnit)
end
end
function AIRBOSS:OnEventEjection(EventData)
self:F3({eventland=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event EJECTION!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event EJECTION!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."EJECT: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."EJECT: group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."EJECT: player = "..tostring(_playername))
if _unit and _playername then
self:T(self.lid..string.format("Player %s ejected!",_playername))
local flight=self.players[_playername]
if flight then
self:_RemoveFlight(flight,true)
end
else
self:T(self.lid..string.format("AI unit %s ejected!",EventData.IniUnitName))
self:_RemoveUnitFromFlight(EventData.IniUnit)
local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights)
self:_CheckSectionRecovered(flight)
end
end
function AIRBOSS:OnEventRemoveUnit(EventData)
self:F3({eventland=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event REMOVEUNIT!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."EJECT: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."EJECT: group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."EJECT: player = "..tostring(_playername))
if _unit and _playername then
self:T(self.lid..string.format("Player %s removed!",_playername))
local flight=self.players[_playername]
if flight then
self:_RemoveFlight(flight,true)
end
else
self:T(self.lid..string.format("AI unit %s removed!",EventData.IniUnitName))
self:_RemoveUnitFromFlight(EventData.IniUnit)
local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights)
self:_CheckSectionRecovered(flight)
end
end
function AIRBOSS:_PlayerLeft(EventData)
self:F3({eventleave=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event PLAYERLEFTUNIT!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event PLAYERLEFTUNIT!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."PLAYERLEAVEUNIT: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."PLAYERLEAVEUNIT: group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."PLAYERLEAVEUNIT: player = "..tostring(_playername))
if _unit and _playername then
self:T(self.lid..string.format("Player %s left unit %s!",_playername,_unitName))
local flight=self.players[_playername]
if flight then
self:_RemoveFlight(flight,true)
end
end
end
function AIRBOSS:OnEventMissionEnd(EventData)
self:T3(self.lid.."Mission Ended")
end
function AIRBOSS:_Spinning(playerData)
local SpinIt={}
SpinIt.name="Spinning"
SpinIt.Xmin=-UTILS.NMToMeters(6)
SpinIt.Xmax=UTILS.NMToMeters(5)
SpinIt.Zmin=-UTILS.NMToMeters(6)
SpinIt.Zmax=UTILS.NMToMeters(2)
SpinIt.LimitXmin=-100
SpinIt.LimitXmax=nil
SpinIt.LimitZmin=-UTILS.NMToMeters(1)
SpinIt.LimitZmax=nil
local X,Z,rho,phi=self:_GetDistances(playerData.unit)
if self:_CheckLimits(X,Z,SpinIt)then
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.INITIAL)
self:_RemoveFlightFromQueue(self.Qspinning,playerData)
end
end
function AIRBOSS:_Waiting(playerData)
local radius=UTILS.NMToMeters(10)
local zone=ZONE_RADIUS:New("Carrier 10 NM Zone",self.carrier:GetVec2(),radius)
local inzone=playerData.unit:IsInZone(zone)
local Twaiting=timer.getAbsTime()-playerData.time
if inzone and Twaiting>3*60 and not playerData.warning then
local text=string.format("You are supposed to wait outside the 10 NM zone.")
self:MessageToPlayer(playerData,text,"AIRBOSS")
playerData.warning=true
end
if inzone==false and playerData.warning==true then
playerData.warning=nil
end
end
function AIRBOSS:_Holding(playerData)
local unit=playerData.unit
local stack=playerData.flag
if stack<=0 then
local text=string.format("ERROR: player %s in step %s is holding but has stack=%s (<=0)",playerData.name,playerData.step,tostring(stack))
self:E(self.lid..text)
end
local patternalt=self:_GetMarshalAltitude(stack,playerData.case)
local playeralt=unit:GetAltitude()
local zoneHolding=self:_GetZoneHolding(playerData.case,stack)
if zoneHolding==nil then
self:E(self.lid.."ERROR: zoneHolding is nil!")
self:E({playerData=playerData})
return
end
local inholdingzone=unit:IsInZone(zoneHolding)
local altdiff=playeralt-patternalt
local altgood=UTILS.FeetToMeters(500)
if playerData.difficulty==AIRBOSS.Difficulty.HARD then
altgood=UTILS.FeetToMeters(200)
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
altgood=UTILS.FeetToMeters(350)
elseif playerData.difficulty==AIRBOSS.Difficulty.EASY then
altgood=UTILS.FeetToMeters(500)
end
local altback=altgood*0.5
local justcollapsed=false
if self.Tcollapse then
local dT=timer.getTime()-self.Tcollapse
if dT<=90 then
justcollapsed=true
end
end
local goodalt=math.abs(altdiff)<altgood
local angels=self:_GetAngels(patternalt)
local text=""
if playerData.holding==true then
if inholdingzone then
self:T3("Player is still in the holding zone. Good job.")
else
text=text..string.format("You just left the holding zone. Watch your numbers!")
playerData.holding=false
end
if not justcollapsed then
if altdiff>altgood then
if not playerData.warning then
text=text..string.format("You left your assigned altitude. Descent to angels %d.",angels)
playerData.warning=true
end
elseif altdiff<-altgood then
if not playerData.warning then
text=text..string.format("You left your assigned altitude. Climb to angels %d.",angels)
playerData.warning=true
end
end
end
if playerData.warning and math.abs(altdiff)<=altback then
text=text..string.format("Altitude is looking good again.")
playerData.warning=nil
end
elseif playerData.holding==false then
if inholdingzone then
text=text..string.format("You are back in the holding zone. Now stay there!")
playerData.holding=true
else
self:T3("Player still outside the holding zone. What are you doing man?!")
end
elseif playerData.holding==nil then
if inholdingzone then
playerData.holding=true
text=text..string.format("You arrived at the holding zone.")
if goodalt then
text=text..string.format(" Altitude is good.")
else
if altdiff<0 then
text=text..string.format(" But you're too low.")
else
text=text..string.format(" But you're too high.")
end
text=text..string.format("\nCurrently assigned altitude is %d ft.",UTILS.MetersToFeet(patternalt))
playerData.warning=true
end
else
self:T3("Waiting for player to arrive in the holding zone.")
end
end
if playerData.showhints then
self:MessageToPlayer(playerData,text,"MARSHAL")
end
end
function AIRBOSS:_Commencing(playerData,zonecheck)
if zonecheck then
local zoneCommence=self:_GetZoneCommence(playerData.case,playerData.flag)
local inzone=playerData.unit:IsInZone(zoneCommence)
if not inzone then
if timer.getAbsTime()-playerData.time>180 then
self:_MarshalCallClearedForRecovery(playerData.onboard,playerData.case)
playerData.time=timer.getAbsTime()
end
return
end
end
self:_RemoveFlightFromMarshalQueue(playerData)
self:_InitPlayer(playerData)
if playerData.difficulty~=AIRBOSS.Difficulty.HARD then
local text=""
if playerData.case==1 then
text=text.."Proceed to initial."
else
text=text.."Descent to platform."
if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then
text=text.." VSI 4000 ft/min until you reach 5000 ft."
end
end
self:MessageToPlayer(playerData,text,"MARSHAL")
end
local nextstep
if playerData.case==1 then
nextstep=AIRBOSS.PatternStep.INITIAL
else
nextstep=AIRBOSS.PatternStep.PLATFORM
end
self:_SetPlayerStep(playerData,nextstep)
for i,_flight in pairs(playerData.section)do
local flight=_flight
self:_Commencing(flight,false)
end
end
function AIRBOSS:_Initial(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZoneInitial(playerData.case))
local relheading=self:_GetRelativeHeading(playerData.unit,false)
local altitude=playerData.unit:GetAltitude()
if inzone and math.abs(relheading)<60 and altitude<=self.initialmaxalt then
if playerData.showhints then
local hint=string.format("Initial")
if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then
if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
hint=hint.." - Hook down, SAS on, Wing Sweep 68°!"
else
hint=hint.." - Hook down!"
end
end
self:MessageToPlayer(playerData,hint,"MARSHAL")
end
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.BREAKENTRY)
return true
end
return false
end
function AIRBOSS:_CheckCorridor(playerData)
local validzone=self:_GetZoneCorridor(playerData.case)
local invalid=playerData.unit:IsNotInZone(validzone)
if invalid and(not playerData.warning)then
self:MessageToPlayer(playerData,"you left the approach corridor!","AIRBOSS")
playerData.warning=true
end
if(not invalid)and playerData.warning then
self:MessageToPlayer(playerData,"you're back in the approach corridor.","AIRBOSS")
playerData.warning=false
end
end
function AIRBOSS:_Platform(playerData)
self:_CheckCorridor(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZonePlatform(playerData.case))
if inzone then
self:_PlayerHint(playerData)
local nextstep
if math.abs(self.holdingoffset)>0 and playerData.case>1 then
nextstep=AIRBOSS.PatternStep.ARCIN
else
if playerData.case==2 then
nextstep=AIRBOSS.PatternStep.INITIAL
elseif playerData.case==3 then
nextstep=AIRBOSS.PatternStep.DIRTYUP
end
end
self:_SetPlayerStep(playerData,nextstep)
end
end
function AIRBOSS:_ArcInTurn(playerData)
self:_CheckCorridor(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZoneArcIn(playerData.case))
if inzone then
self:_PlayerHint(playerData)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.ARCOUT)
end
end
function AIRBOSS:_ArcOutTurn(playerData)
self:_CheckCorridor(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZoneArcOut(playerData.case))
if inzone then
self:_PlayerHint(playerData)
local nextstep
if playerData.case==3 then
nextstep=AIRBOSS.PatternStep.DIRTYUP
else
nextstep=AIRBOSS.PatternStep.INITIAL
end
self:_SetPlayerStep(playerData,nextstep)
end
end
function AIRBOSS:_DirtyUp(playerData)
self:_CheckCorridor(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZoneDirtyUp(playerData.case))
if inzone then
self:_PlayerHint(playerData)
if playerData.actype==AIRBOSS.AircraftCarrier.HORNET
or playerData.actype==AIRBOSS.AircraftCarrier.F14A
or playerData.actype==AIRBOSS.AircraftCarrier.F14B
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER
then
local callsay=self:_NewRadioCall(self.MarshalCall.SAYNEEDLES,nil,nil,5,playerData.onboard)
local callfly=self:_NewRadioCall(self.MarshalCall.FLYNEEDLES,nil,nil,5,playerData.onboard)
self:RadioTransmission(self.MarshalRadio,callsay,false,55,nil,true)
self:RadioTransmission(self.MarshalRadio,callfly,false,60,nil,true)
end
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.BULLSEYE)
end
end
function AIRBOSS:_Bullseye(playerData)
self:_CheckCorridor(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZoneBullseye(playerData.case))
local relheading=self:_GetRelativeHeading(playerData.unit,true)
if inzone and math.abs(relheading)<60 then
self:_PlayerHint(playerData)
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.JCARLOS then
self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT5,nil,nil,nil,true)
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT5,nil,nil,nil,true)
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT75,nil,nil,nil,true)
end
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_XX)
end
end
function AIRBOSS:_BolterPattern(playerData)
local X,Z,rho,phi=self:_GetDistances(playerData.unit)
local Bolter={}
Bolter.name="Bolter Pattern"
Bolter.Xmin=-UTILS.NMToMeters(5)
Bolter.Xmax=UTILS.NMToMeters(3)
Bolter.Zmin=-UTILS.NMToMeters(5)
Bolter.Zmax=UTILS.NMToMeters(1)
Bolter.LimitXmin=100
Bolter.LimitXmax=nil
Bolter.LimitZmin=nil
Bolter.LimitZmax=nil
if self:_CheckLimits(X,Z,Bolter)then
local nextstep
if playerData.case<3 then
nextstep=AIRBOSS.PatternStep.ABEAM
else
nextstep=AIRBOSS.PatternStep.BULLSEYE
end
self:_SetPlayerStep(playerData,nextstep)
end
end
function AIRBOSS:_BreakEntry(playerData)
local X,Z=self:_GetDistances(playerData.unit)
if self:_CheckAbort(X,Z,self.BreakEntry)then
self:_AbortPattern(playerData,X,Z,self.BreakEntry,true)
return
end
if self:_CheckLimits(X,Z,self.BreakEntry)then
self:_PlayerHint(playerData)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK)
end
end
function AIRBOSS:_Break(playerData,part)
local X,Z=self:_GetDistances(playerData.unit)
local breakpoint=self.BreakEarly
if part==AIRBOSS.PatternStep.LATEBREAK then
breakpoint=self.BreakLate
end
if self:_CheckAbort(X,Z,breakpoint)then
self:_AbortPattern(playerData,X,Z,breakpoint,true)
return
end
local tooclose=false
if part==AIRBOSS.PatternStep.LATEBREAK then
local close=0.8
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
close=0.5
end
if X<0 and Z<UTILS.NMToMeters(close)then
if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then
self:MessageToPlayer(playerData,"your turn was too tight! Allow for more distance to the boat next time.","LSO")
end
tooclose=true
end
end
if self:_CheckLimits(X,Z,breakpoint)or tooclose then
self:_PlayerHint(playerData)
local nextstep
if part==AIRBOSS.PatternStep.EARLYBREAK then
nextstep=AIRBOSS.PatternStep.LATEBREAK
else
nextstep=AIRBOSS.PatternStep.ABEAM
end
self:_SetPlayerStep(playerData,nextstep)
end
end
function AIRBOSS:_CheckForLongDownwind(playerData)
local X,Z=self:_GetDistances(playerData.unit)
local limit=UTILS.NMToMeters(-1.8)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
limit=UTILS.NMToMeters(-2.0)
end
if X<limit then
self:RadioTransmission(self.LSORadio,self.LSOCall.LONGINGROOVE)
self:RadioTransmission(self.LSORadio,self.LSOCall.DEPARTANDREENTER,nil,nil,nil,true)
self:_AddToDebrief(playerData,"Long in the groove - Pattern Waveoff!")
playerData.lig=true
playerData.wop=true
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF)
end
end
function AIRBOSS:_Abeam(playerData)
local X,Z=self:_GetDistances(playerData.unit)
if self:_CheckAbort(X,Z,self.Abeam)then
self:_AbortPattern(playerData,X,Z,self.Abeam,true)
return
end
if self:_CheckLimits(X,Z,self.Abeam)then
self:RadioTransmission(self.LSORadio,self.LSOCall.PADDLESCONTACT,nil,nil,nil,true)
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.JCARLOS then
self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT5,false,5,nil,true)
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT5,false,5,nil,true)
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT75,false,5,nil,true)
end
self:_PlayerHint(playerData,3)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.NINETY)
end
end
function AIRBOSS:_Ninety(playerData)
local X,Z=self:_GetDistances(playerData.unit)
if self:_CheckAbort(X,Z,self.Ninety)then
self:_AbortPattern(playerData,X,Z,self.Ninety,true)
return
end
local relheading=self:_GetRelativeHeading(playerData.unit,false)
if relheading<=90 then
self:_PlayerHint(playerData)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.FINAL)
else
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.WAKE)
end
elseif relheading>90 and self:_CheckLimits(X,Z,self.Wake)then
self:MessageToPlayer(playerData,"you are already at the wake and have not passed the 90. Turn faster next time!","LSO")
self:RadioTransmission(self.LSORadio,self.LSOCall.DEPARTANDREENTER,nil,nil,nil,true)
playerData.wop=true
self:_AddToDebrief(playerData,"Overshoot at wake - Pattern Waveoff!")
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF)
end
end
function AIRBOSS:_Wake(playerData)
local X,Z=self:_GetDistances(playerData.unit)
if self:_CheckAbort(X,Z,self.Wake)then
self:_AbortPattern(playerData,X,Z,self.Wake,true)
return
end
if self:_CheckLimits(X,Z,self.Wake)then
self:_PlayerHint(playerData)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.FINAL)
end
end
function AIRBOSS:_GetGrooveData(playerData)
local X,Z=self:_GetDistances(playerData.unit)
local stern=self:_GetSternCoord()
local rho=stern:Get2DDistance(playerData.unit:GetCoordinate())
local astern=X<self.carrierparam.sterndist
if astern==false then
rho=-rho
end
local vel=playerData.unit:GetVelocityVec3()
local Gg,Gp,Gd=self:_LSOgrade(playerData)
local groovedata={}
groovedata.Step=playerData.step
groovedata.Time=timer.getTime()
groovedata.Rho=rho
groovedata.X=X
groovedata.Z=Z
groovedata.Alt=self:_GetAltCarrier(playerData.unit)
groovedata.AoA=playerData.unit:GetAoA()
groovedata.GSE=self:_Glideslope(playerData.unit)
groovedata.LUE=self:_Lineup(playerData.unit,true)
groovedata.Roll=playerData.unit:GetRoll()
groovedata.Pitch=playerData.unit:GetPitch()
groovedata.Yaw=playerData.unit:GetYaw()
groovedata.Vel=UTILS.VecNorm(vel)
groovedata.Vy=vel.y
groovedata.Gamma=self:_GetRelativeHeading(playerData.unit,true)
groovedata.Grade=Gg
groovedata.GradePoints=Gp
groovedata.GradeDetail=Gd
return groovedata
end
function AIRBOSS:_Final(playerData,nocheck)
local X,Z,rho,phi=self:_GetDistances(playerData.unit)
if not nocheck then
if self:_CheckAbort(X,Z,self.Final)then
self:_AbortPattern(playerData,X,Z,self.Final,true)
return
end
end
local groovedata=self:_GetGrooveData(playerData)
table.insert(playerData.trapsheet,groovedata)
local zone=self:_GetZoneGroove()
local inzone=playerData.unit:IsInZone(zone)
if inzone then
self:_PlayerHint(playerData,nil,true)
groovedata.FlyThrough=nil
playerData.groove.X0=UTILS.DeepCopy(groovedata)
playerData.Tlso=timer.getTime()
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_XX)
end
groovedata.Step=playerData.step
end
function AIRBOSS:_Groove(playerData)
local RX0=UTILS.NMToMeters(1.000)
local RXX=UTILS.NMToMeters(0.750)
local RIM=UTILS.NMToMeters(0.500)
local RIC=UTILS.NMToMeters(0.250)
local RAR=UTILS.NMToMeters(0.040)
local groovedata=self:_GetGrooveData(playerData)
table.insert(playerData.trapsheet,groovedata)
local X=groovedata.X
local Z=groovedata.Z
if self:_CheckAbort(groovedata.X,groovedata.Z,self.Groove)then
self:_AbortPattern(playerData,groovedata.X,groovedata.Z,self.Groove,true)
return
end
local rho=groovedata.Rho
local lineupError=groovedata.LUE
local glideslopeError=groovedata.GSE
local AoA=groovedata.AoA
if rho<=RXX and playerData.step==AIRBOSS.PatternStep.GROOVE_XX and(math.abs(groovedata.Roll)<=4.0 or playerData.unit:IsInZone(self:_GetZoneLineup()))then
playerData.TIG0=timer.getTime()
self:RadioTransmission(self.LSORadio,self.LSOCall.CALLTHEBALL,nil,nil,nil,true)
playerData.Tlso=timer.getTime()
self:RadioTransmission(self.LSORadio,self.LSOCall.ROGERBALL,false,nil,2,true)
playerData.groove.XX=UTILS.DeepCopy(groovedata)
playerData.valid=true
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_IM)
elseif rho<=RIM and playerData.step==AIRBOSS.PatternStep.GROOVE_IM then
playerData.groove.IM=UTILS.DeepCopy(groovedata)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_IC)
elseif rho<=RIC and playerData.step==AIRBOSS.PatternStep.GROOVE_IC then
playerData.groove.IC=UTILS.DeepCopy(groovedata)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_AR)
elseif rho<=RAR and playerData.step==AIRBOSS.PatternStep.GROOVE_AR then
playerData.groove.AR=UTILS.DeepCopy(groovedata)
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_AL)
else
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_IW)
end
elseif rho<=RAR and playerData.step==AIRBOSS.PatternStep.GROOVE_AL then
playerData.groove.AL=UTILS.DeepCopy(groovedata)
local ZoneALS=self:_GetZoneAbeamLandingSpot()
local vplayer=playerData.unit:GetVelocityKMH()
local vcarrier=self.carrier:GetVelocityKMH()
local dv=math.abs(vplayer-vcarrier)
local stable=dv<30
if playerData.unit:IsInZone(ZoneALS)and stable then
self:RadioTransmission(self.LSORadio,self.LSOCall.CLEAREDTOLAND,nil,nil,nil,true)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_LC)
playerData.stable=true
playerData.hover=true
end
elseif rho<=RAR and playerData.step==AIRBOSS.PatternStep.GROOVE_LC then
playerData.groove.LC=UTILS.DeepCopy(groovedata)
local ZoneLS=self:_GetZoneLandingSpot()
local vplayer=playerData.unit:GetVelocityKMH()
local vcarrier=self.carrier:GetVelocityKMH()
local dv=math.abs(vplayer-vcarrier)
local stable=dv<15
if playerData.unit:IsInZone(ZoneLS)and stable and playerData.stable==true then
self:RadioTransmission(self.LSORadio,self.LSOCall.STABILIZED,nil,nil,nil,false)
playerData.stable=false
playerData.warning=true
end
end
if rho>=RAR and rho<=RIC and not playerData.waveoff then
local waveoff=self:_CheckWaveOff(glideslopeError,lineupError,AoA,playerData)
if waveoff then
self:T3(self.lid..string.format("Waveoff distance rho=%.1f m",rho))
self:RadioTransmission(self.LSORadio,self.LSOCall.WAVEOFF,nil,nil,nil,true)
playerData.Tlso=timer.getTime()
playerData.waveoff=true
return
end
end
groovedata.Step=playerData.step
if rho>=RAR and rho<RX0 and playerData.waveoff==false then
local gs=self:_GS(playerData.step,-1)
local gd=playerData.groove[gs]
if gd then
self:T3(gd)
local d=UTILS.MetersToNM(rho)
if rho>=RAR and rho<=RIM then
if gd.LUE>0.22 and lineupError<-0.22 then
env.info" Drift Right across centre ==> DR-"
gd.Drift=" DR"
self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError))
elseif gd.LUE<-0.22 and lineupError>0.22 then
env.info" Drift Left ==> DL-"
gd.Drift=" DL"
self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError))
elseif gd.LUE>0.13 and lineupError<-0.14 then
env.info" Little Drift Right across centre ==> (DR-)"
gd.Drift=" (DR)"
self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError))
elseif gd.LUE<-0.13 and lineupError>0.14 then
env.info" Little Drift Left across centre ==> (DL-)"
gd.Drift=" (DL)"
self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError))
end
end
if math.abs(lineupError)>math.abs(gd.LUE)then
self:T(self.lid..string.format("Got bigger LUE at step %s, d=%.3f: LUE %.3f>%.3f",gs,d,lineupError,gd.LUE))
gd.LUE=lineupError
end
if gd.GSE>0.4 and glideslopeError<-0.3 then
gd.FlyThrough="\\"
self:T(self.lid..string.format("Got Fly through DOWN at step %s, d=%.3f: Max GSE=%.3f, lower GSE=%.3f",gs,d,gd.GSE,glideslopeError))
elseif gd.GSE<-0.3 and glideslopeError>0.4 then
gd.FlyThrough="/"
self:E(self.lid..string.format("Got Fly through UP at step %s, d=%.3f: Min GSE=%.3f, lower GSE=%.3f",gs,d,gd.GSE,glideslopeError))
end
if math.abs(glideslopeError)>math.abs(gd.GSE)then
self:T(self.lid..string.format("Got bigger GSE at step %s, d=%.3f: GSE |%.3f|>|%.3f|",gs,d,glideslopeError,gd.GSE))
gd.GSE=glideslopeError
end
local aircraftaoa=self:_GetAircraftAoA(playerData)
local aoaopt=aircraftaoa.OnSpeed
if math.abs(AoA-aoaopt)>math.abs(gd.AoA-aoaopt)then
self:T(self.lid..string.format("Got bigger AoA error at step %s, d=%.3f: AoA %.3f>%.3f.",gs,d,AoA,gd.AoA))
gd.AoA=AoA
end
end
local deltaT=timer.getTime()-playerData.Tlso
local _advice=true
if playerData.TIG0==nil and playerData.difficulty~=AIRBOSS.Difficulty.EASY then
_advice=false
end
if deltaT>=self.LSOdT and _advice then
self:_LSOadvice(playerData,glideslopeError,lineupError)
end
end
if X>self.carrierparam.totlength+self.carrierparam.sterndist then
if playerData.waveoff then
if playerData.landed then
self:_AddToDebrief(playerData,"You were waved off but landed anyway. Airboss wants to talk to you!")
else
self:_AddToDebrief(playerData,"You were waved off.")
end
elseif playerData.boltered then
self:_AddToDebrief(playerData,"You boltered.")
else
self:T("Player was not waved off but flew past the carrier without landing ==> Own wave off!")
self:_AddToDebrief(playerData,"Own waveoff.")
playerData.owo=true
end
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF)
end
end
function AIRBOSS:_CheckWaveOff(glideslopeError,lineupError,AoA,playerData)
local waveoff=false
local glMax=1.8
local glMin=-1.2
local luAbs=3.0
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
glMax=2.6
glMin=-2.2
luAbs=4.1
end
if glideslopeError>glMax then
local text=string.format("\n- Waveoff due to glideslope error %.2f > %.1f degrees!",glideslopeError,glMax)
self:T(self.lid..string.format("%s: %s",playerData.name,text))
self:_AddToDebrief(playerData,text)
waveoff=true
elseif glideslopeError<glMin then
local text=string.format("\n- Waveoff due to glideslope error %.2f < %.1f degrees!",glideslopeError,glMin)
self:T(self.lid..string.format("%s: %s",playerData.name,text))
self:_AddToDebrief(playerData,text)
waveoff=true
end
if math.abs(lineupError)>luAbs then
local text=string.format("\n- Waveoff due to line up error |%.1f| > %.1f degrees!",lineupError,luAbs)
self:T(self.lid..string.format("%s: %s",playerData.name,text))
self:_AddToDebrief(playerData,text)
waveoff=true
end
if playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then
local aoaac=self:_GetAircraftAoA(playerData)
if AoA<aoaac.FAST then
local text=string.format("\n- Waveoff due to AoA %.1f < %.1f!",AoA,aoaac.FAST)
self:T(self.lid..string.format("%s: %s",playerData.name,text))
self:_AddToDebrief(playerData,text)
waveoff=true
elseif AoA>aoaac.SLOW then
local text=string.format("\n- Waveoff due to AoA %.1f > %.1f!",AoA,aoaac.SLOW)
self:T(self.lid..string.format("%s: %s",playerData.name,text))
self:_AddToDebrief(playerData,text)
waveoff=true
end
end
return waveoff
end
function AIRBOSS:_CheckFoulDeck(playerData)
local check=false
if playerData.step==AIRBOSS.PatternStep.GROOVE_AR or playerData.step==AIRBOSS.PatternStep.GROOVE_AL then
check=true
end
if playerData.wofd==true or check==false then
return
end
local runway=self:_GetZoneRunwayBox()
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
runway=self:_GetZoneLandingSpot()
end
local R=250
self:T(self.lid..string.format("Foul deck check: Scanning Carrier Runway Area. Radius=%.1f m.",R))
local _,_,_,unitscan=self:GetCoordinate():ScanObjects(R,true,false,false)
local fouldeck=false
local foulunit=nil
for _,_unit in pairs(unitscan)do
local unit=_unit
local inzone=unit:IsInZone(runway)
local isaircraft=unit:IsAir()
local isairborn=unit:InAir()
if inzone and isaircraft and not isairborn then
local text=string.format("Unit %s on landing runway ==> Foul deck!",unit:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
if self.Debug then
runway:FlareZone(FLARECOLOR.Red,30)
end
fouldeck=true
foulunit=unit
end
end
if playerData and fouldeck then
local text=string.format("Foul deck waveoff due to aircraft %s!",foulunit:GetName())
self:T(self.lid..string.format("%s: %s",playerData.name,text))
self:_AddToDebrief(playerData,text)
self:RadioTransmission(self.LSORadio,self.LSOCall.FOULDECK,false,1)
self:RadioTransmission(self.LSORadio,self.LSOCall.WAVEOFF,false,1.2,nil,true)
if playerData.showhints then
local text=string.format("overfly landing area and enter bolter pattern.")
self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3)
end
playerData.wofd=true
playerData.step=AIRBOSS.PatternStep.DEBRIEF
playerData.warning=nil
playerData.valid=false
if foulunit then
local foulflight=self:_GetFlightFromGroupInQueue(foulunit:GetGroup(),self.flights)
if foulflight and not foulflight.ai then
self:MessageToPlayer(foulflight,"move your ass from my runway. NOW!","AIRBOSS")
end
end
end
return fouldeck
end
function AIRBOSS:_GetSternCoord()
local hdg=self.carrier:GetHeading()
local FB=self:GetFinalBearing()
local case=self.case
self.sterncoord:UpdateFromCoordinate(self:GetCoordinate())
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
if case==3 then
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(8,FB-90,true,true)
elseif case==2 or case==1 then
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(8,FB-90,true,true)
end
elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(7,FB+90,true,true)
elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(7.5,FB+90,true,true)
else
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(9.5,FB+90,true,true)
end
self.sterncoord:SetAltitude(self.carrierparam.deckheight)
return self.sterncoord
end
function AIRBOSS:_GetWire(Lcoord,dc)
local FB=self:GetFinalBearing()
local Scoord=self:_GetSternCoord()
local Ldist=Lcoord:Get2DDistance(Scoord)
dc=dc or 65
local d=Ldist-dc
if self.mpWireCorrection then
d=d-self.mpWireCorrection
end
local w1=self.carrierparam.wire1
local w2=self.carrierparam.wire2
local w3=self.carrierparam.wire3
local w4=self.carrierparam.wire4
local wire
if d<w1 then
wire=1
elseif d<w2 then
wire=2
elseif d<w3 then
wire=3
elseif d<w4 then
wire=4
else
wire=99
end
if self.Debug and false then
local wp1=Scoord:Translate(w1,FB)
local wp2=Scoord:Translate(w2,FB)
local wp3=Scoord:Translate(w3,FB)
local wp4=Scoord:Translate(w4,FB)
wp1:MarkToAll("Wire 1")
wp2:MarkToAll("Wire 2")
wp3:MarkToAll("Wire 3")
wp4:MarkToAll("Wire 4")
Scoord:MarkToAll("Stern")
Lcoord:MarkToAll(string.format("Landing Point wire=%s",wire))
Lcoord:SmokeGreen()
local Dcoord=Lcoord:Translate(-dc,FB)
Dcoord:SmokeRed()
end
self:T(string.format("GetWire: L=%.1f, L-dc=%.1f ==> wire=%d (dc=%.1f)",Ldist,Ldist-dc,wire,dc))
return wire
end
function AIRBOSS:_Trapped(playerData)
if playerData.unit:InAir()==false then
local unit=playerData.unit
local coord=unit:GetCoordinate()
local v=unit:GetVelocityKMH()-self.carrier:GetVelocityKMH()
local stern=self:_GetSternCoord()
local s=stern:Get2DDistance(coord)
local dcorr=100
if playerData.actype==AIRBOSS.AircraftCarrier.HORNET
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER then
dcorr=100
elseif playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
dcorr=100
elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then
dcorr=56
elseif playerData.actype==AIRBOSS.AircraftCarrier.T45C then
dcorr=56
end
local wire=self:_GetWire(coord,dcorr)
local text=string.format("Player %s _Trapped: v=%.1f km/h, s-dcorr=%.1f m ==> wire=%d (dcorr=%d)",playerData.name,v,s-dcorr,wire,dcorr)
self:T(self.lid..text)
if v>5 then
if wire>4 and v>10 and not playerData.warning then
self:RadioTransmission(self.LSORadio,self.LSOCall.BOLTER,nil,nil,nil,true)
playerData.warning=true
end
self:ScheduleOnce(0.1,self._Trapped,self,playerData)
return
end
if self.Debug then
coord:SmokeBlue()
coord:MarkToAll(text)
stern:MarkToAll("Stern")
end
playerData.wire=wire
local text=string.format("Trapped %d-wire.",wire)
if wire==3 then
text=text.." Well done!"
elseif wire==2 then
text=text.." Not bad, maybe you even get the 3rd next time."
elseif wire==4 then
text=text.." That was scary. You can do better than this!"
elseif wire==1 then
text=text.." Try harder next time!"
end
self:MessageToPlayer(playerData,text,"LSO","")
local hint=string.format("Trapped %d-wire.",wire)
self:_AddToDebrief(playerData,hint,"Groove: IW")
else
local text=string.format("Player %s boltered in trapped function.",playerData.name)
self:T(self.lid..text)
MESSAGE:New(text,5,"DEBUG"):ToAllIf(self.debug)
playerData.boltered=true
end
playerData.step=AIRBOSS.PatternStep.DEBRIEF
playerData.warning=nil
end
function AIRBOSS:_GetZoneInitial(case)
self.zoneInitial=self.zoneInitial or ZONE_POLYGON_BASE:New("Zone CASE I/II Initial")
local radial=self:GetRadial(2,false,false)
local cv=self:GetCoordinate()
local vec2={}
if case==1 then
local c1=cv:Translate(UTILS.NMToMeters(0.5),radial-90)
local c2=cv:Translate(UTILS.NMToMeters(1.3),radial-90):Translate(UTILS.NMToMeters(3),radial)
local c3=cv:Translate(UTILS.NMToMeters(0.4),radial+90):Translate(UTILS.NMToMeters(3),radial)
local c4=cv:Translate(UTILS.NMToMeters(1.0),radial)
local c5=cv
vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2()}
else
local c1=cv:Translate(UTILS.NMToMeters(0.5),radial-90)
local c2=c1:Translate(UTILS.NMToMeters(0.5),radial)
local c3=cv:Translate(UTILS.NMToMeters(1.2),radial-90):Translate(UTILS.NMToMeters(3),radial)
local c4=cv:Translate(UTILS.NMToMeters(1.2),radial+90):Translate(UTILS.NMToMeters(3),radial)
local c5=cv:Translate(UTILS.NMToMeters(0.5),radial)
local c6=cv
vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2(),c6:GetVec2()}
end
self.zoneInitial:UpdateFromVec2(vec2)
return self.zoneInitial
end
function AIRBOSS:_GetZoneLineup()
self.zoneLineup=self.zoneLineup or ZONE_POLYGON_BASE:New("Zone Lineup")
local fbi=self:GetRadial(1,false,false)
local st=self:_GetOptLandingCoordinate()
local c1=st
local c2=st:Translate(UTILS.NMToMeters(0.50),fbi+15)
local c3=st:Translate(UTILS.NMToMeters(0.50),fbi+self.lue._max-0.05)
local c4=st:Translate(UTILS.NMToMeters(0.77),fbi+self.lue._max-0.05)
local c5=c4:Translate(UTILS.NMToMeters(0.25),fbi-90)
local vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2()}
self.zoneLineup:UpdateFromVec2(vec2)
return self.zoneLineup
end
function AIRBOSS:_GetZoneGroove(l,w,b)
self.zoneGroove=self.zoneGroove or ZONE_POLYGON_BASE:New("Zone Groove")
l=l or 1.50
w=w or 0.25
b=b or 0.10
local fbi=self:GetRadial(1,false,false)
local st=self:_GetSternCoord()
local c1=st:Translate(self.carrierparam.totwidthstarboard,fbi-90)
local c2=st:Translate(UTILS.NMToMeters(0.10),fbi-90):Translate(UTILS.NMToMeters(0.3),fbi)
local c3=st:Translate(UTILS.NMToMeters(0.25),fbi-90):Translate(UTILS.NMToMeters(l),fbi)
local c4=st:Translate(UTILS.NMToMeters(w/2),fbi+90):Translate(UTILS.NMToMeters(l),fbi)
local c5=st:Translate(UTILS.NMToMeters(b),fbi+90):Translate(UTILS.NMToMeters(0.3),fbi)
local c6=st:Translate(self.carrierparam.totwidthport,fbi+90)
local vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2(),c6:GetVec2()}
self.zoneGroove:UpdateFromVec2(vec2)
return self.zoneGroove
end
function AIRBOSS:_GetZoneBullseye(case)
local radius=UTILS.NMToMeters(1)
local distance=UTILS.NMToMeters(3)
local radial=self:GetRadial(case,false,false)
local coord=self:GetCoordinate():Translate(distance,radial)
local vec2=coord:GetVec2()
local zone=ZONE_RADIUS:New("Zone Bullseye",vec2,radius)
return zone
end
function AIRBOSS:_GetZoneDirtyUp(case)
local radius=UTILS.NMToMeters(1)
local distance=UTILS.NMToMeters(9)
local radial=self:GetRadial(case,false,false)
local coord=self:GetCoordinate():Translate(distance,radial)
local vec2=coord:GetVec2()
local zone=ZONE_RADIUS:New("Zone Dirty Up",vec2,radius)
return zone
end
function AIRBOSS:_GetZoneArcOut(case)
local radius=UTILS.NMToMeters(1.25)
local distance=UTILS.NMToMeters(11.75)
local radial=self:GetRadial(case,false,false)
local coord=self:GetCoordinate():Translate(distance,radial)
local zone=ZONE_RADIUS:New("Zone Arc Out",coord:GetVec2(),radius)
return zone
end
function AIRBOSS:_GetZoneArcIn(case)
local radius=UTILS.NMToMeters(1.25)
local radial=self:GetRadial(case,false,true)
local alpha=math.rad(self.holdingoffset)
local x=14
local distance=UTILS.NMToMeters(x)
local coord=self:GetCoordinate():Translate(distance,radial)
local zone=ZONE_RADIUS:New("Zone Arc In",coord:GetVec2(),radius)
return zone
end
function AIRBOSS:_GetZonePlatform(case)
local radius=UTILS.NMToMeters(1)
local radial=self:GetRadial(case,false,true)
local alpha=math.rad(self.holdingoffset)
local distance=UTILS.NMToMeters(19)
local coord=self:GetCoordinate():Translate(distance,radial)
local zone=ZONE_RADIUS:New("Zone Platform",coord:GetVec2(),radius)
return zone
end
function AIRBOSS:_GetZoneCorridor(case,l)
l=l or 31
local radial=self:GetRadial(case,false,false)
local offset=self:GetRadial(case,false,true)
local dx=7
local w=2
local w2=w/2
local d=12
local cv=self:GetCoordinate()
local c={}
c[1]=cv:Translate(-UTILS.NMToMeters(dx),radial)
if math.abs(self.holdingoffset)>=5 then
c[2]=c[1]:Translate(UTILS.NMToMeters(w2),radial-90)
c[3]=c[2]:Translate(UTILS.NMToMeters(d+dx+w2),radial)
c[4]=cv:Translate(UTILS.NMToMeters(15),offset):Translate(UTILS.NMToMeters(1),offset-90)
c[5]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset-90)
c[6]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset+90)
c[7]=cv:Translate(UTILS.NMToMeters(13),offset):Translate(UTILS.NMToMeters(1),offset+90)
c[8]=cv:Translate(UTILS.NMToMeters(11),radial):Translate(UTILS.NMToMeters(1),radial+90)
c[9]=c[1]:Translate(UTILS.NMToMeters(w2),radial+90)
else
c[2]=c[1]:Translate(UTILS.NMToMeters(w2),radial-90)
c[3]=c[2]:Translate(UTILS.NMToMeters(dx+l),radial)
c[4]=c[3]:Translate(UTILS.NMToMeters(w),radial+90)
c[5]=c[1]:Translate(UTILS.NMToMeters(w2),radial+90)
end
local p={}
for _i,_c in ipairs(c)do
if self.Debug then
end
p[_i]=_c:GetVec2()
end
local zone=ZONE_POLYGON_BASE:New("CASE II/III Approach Corridor",p)
return zone
end
function AIRBOSS:_GetZoneCarrierBox()
self.zoneCarrierbox=self.zoneCarrierbox or ZONE_POLYGON_BASE:New("Carrier Box Zone")
local S=self:_GetSternCoord()
local hdg=self:GetHeading(false)
local p={}
p[1]=S:Translate(self.carrierparam.totwidthstarboard,hdg+90)
p[2]=p[1]:Translate(self.carrierparam.totlength,hdg)
p[3]=p[2]:Translate(self.carrierparam.totwidthstarboard+self.carrierparam.totwidthport,hdg-90)
p[4]=p[3]:Translate(self.carrierparam.totlength,hdg-180)
local vec2={}
for _,coord in ipairs(p)do
table.insert(vec2,coord:GetVec2())
end
self.zoneCarrierbox:UpdateFromVec2(vec2)
return self.zoneCarrierbox
end
function AIRBOSS:_GetZoneRunwayBox()
self.zoneRunwaybox=self.zoneRunwaybox or ZONE_POLYGON_BASE:New("Landing Runway Zone")
local S=self:_GetSternCoord()
local FB=self:GetFinalBearing(false)
local p={}
p[1]=S:Translate(self.carrierparam.rwywidth*0.5,FB+90)
p[2]=p[1]:Translate(self.carrierparam.rwylength,FB)
p[3]=p[2]:Translate(self.carrierparam.rwywidth,FB-90)
p[4]=p[3]:Translate(self.carrierparam.rwylength,FB-180)
local vec2={}
for _,coord in ipairs(p)do
table.insert(vec2,coord:GetVec2())
end
self.zoneRunwaybox:UpdateFromVec2(vec2)
return self.zoneRunwaybox
end
function AIRBOSS:_GetZoneAbeamLandingSpot()
local S=self:_GetOptLandingCoordinate()
local FB=self:GetFinalBearing(false)
local p={}
p[1]=S:Translate(15,FB):Translate(15,FB+90)
p[2]=S:Translate(-45,FB):Translate(15,FB+90)
p[3]=S:Translate(-45,FB):Translate(15,FB-90)
p[4]=S:Translate(15,FB):Translate(15,FB-90)
local vec2={}
for _,coord in ipairs(p)do
table.insert(vec2,coord:GetVec2())
end
local zone=ZONE_POLYGON_BASE:New("Abeam Landing Spot Zone",vec2)
return zone
end
function AIRBOSS:_GetZoneLandingSpot()
local S=self:_GetLandingSpotCoordinate()
local FB=self:GetFinalBearing(false)
local p={}
p[1]=S:Translate(10,FB):Translate(10,FB+90)
p[2]=S:Translate(-10,FB):Translate(10,FB+90)
p[3]=S:Translate(-10,FB):Translate(10,FB-90)
p[4]=S:Translate(10,FB):Translate(10,FB-90)
local vec2={}
for _,coord in ipairs(p)do
table.insert(vec2,coord:GetVec2())
end
local zone=ZONE_POLYGON_BASE:New("Landing Spot Zone",vec2)
return zone
end
function AIRBOSS:_GetZoneHolding(case,stack)
local zoneHolding=nil
if stack<=0 then
self:E(self.lid.."ERROR: Stack <= 0 in _GetZoneHolding!")
self:E({case=case,stack=stack})
return nil
end
local patternalt,c1,c2=self:_GetMarshalAltitude(stack,case)
if case==1 then
local hdg=self:GetHeading()
local D=UTILS.NMToMeters(2.5)
local Post=self:GetCoordinate():Translate(D,hdg+270)
self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone",Post:GetVec2(),self.marshalradius)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone",self.carrier:GetVec2(),UTILS.NMToMeters(5))
end
else
local radial=self:GetRadial(case,false,true)
local p={}
p[1]=c2:Translate(UTILS.NMToMeters(1),radial-90):GetVec2()
p[2]=c1:Translate(UTILS.NMToMeters(1),radial-90):GetVec2()
p[3]=c1:Translate(UTILS.NMToMeters(7),radial+90):GetVec2()
p[4]=c2:Translate(UTILS.NMToMeters(7),radial+90):GetVec2()
self.zoneHolding=self.zoneHolding or ZONE_POLYGON_BASE:New("CASE II/III Holding Zone")
self.zoneHolding:UpdateFromVec2(p)
end
return self.zoneHolding
end
function AIRBOSS:_GetZoneCommence(case,stack)
local zone
if case==1 then
local hdg=self:GetHeading()
local D=UTILS.NMToMeters(4.75)
local R=UTILS.NMToMeters(1)
local Three=self:GetCoordinate():Translate(D,hdg+275)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
local Dx=UTILS.NMToMeters(2.25)
local Dz=UTILS.NMToMeters(2.25)
R=UTILS.NMToMeters(1)
Three=self:GetCoordinate():Translate(Dz,hdg-90):Translate(Dx,hdg-180)
end
self.zoneCommence=self.zoneCommence or ZONE_RADIUS:New("CASE I Commence Zone")
self.zoneCommence:UpdateFromVec2(Three:GetVec2(),R)
else
stack=stack or 1
local l=20+stack
local offset=self:GetRadial(case,false,true)
local cv=self:GetCoordinate()
local c={}
c[1]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset-90)
c[2]=cv:Translate(UTILS.NMToMeters(l+2.5),offset):Translate(UTILS.NMToMeters(1),offset-90)
c[3]=cv:Translate(UTILS.NMToMeters(l+2.5),offset):Translate(UTILS.NMToMeters(1),offset+90)
c[4]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset+90)
local p={}
for _i,_c in ipairs(c)do
p[_i]=_c:GetVec2()
end
self.zoneCommence=self.zoneCommence or ZONE_POLYGON_BASE:New("CASE II/III Commence Zone")
self.zoneCommence:UpdateFromVec2(p)
end
return self.zoneCommence
end
function AIRBOSS:_AttitudeMonitor(playerData)
local unit=playerData.unit
local aoa=unit:GetAoA()
local yaw=unit:GetYaw()
local roll=unit:GetRoll()
local pitch=unit:GetPitch()
local dist=playerData.unit:GetCoordinate():Get2DDistance(self:GetCoordinate())
local dx,dz,rho,phi=self:_GetDistances(unit)
local wind=unit:GetCoordinate():GetWindWithTurbulenceVec3()
local velo=unit:GetVelocityVec3()
local vabs=UTILS.VecNorm(velo)
local rwy=false
local step=playerData.step
if playerData.step==AIRBOSS.PatternStep.FINAL or
playerData.step==AIRBOSS.PatternStep.GROOVE_XX or
playerData.step==AIRBOSS.PatternStep.GROOVE_IM or
playerData.step==AIRBOSS.PatternStep.GROOVE_IC or
playerData.step==AIRBOSS.PatternStep.GROOVE_AR or
playerData.step==AIRBOSS.PatternStep.GROOVE_AL or
playerData.step==AIRBOSS.PatternStep.GROOVE_LC or
playerData.step==AIRBOSS.PatternStep.GROOVE_IW then
step=self:_GS(step,-1)
rwy=true
end
local relhead=self:_GetRelativeHeading(playerData.unit,rwy)
local text=string.format("Pattern step: %s",step)
text=text..string.format("\nAoA=%.1f° = %.1f Units | |V|=%.1f knots",aoa,self:_AoADeg2Units(playerData,aoa),UTILS.MpsToKnots(vabs))
if self.Debug then
text=text..string.format("\nVx=%.1f Vy=%.1f Vz=%.1f m/s",velo.x,velo.y,velo.z)
text=text..string.format("\nWind Vx=%.1f Vy=%.1f Vz=%.1f m/s",wind.x,wind.y,wind.z)
end
text=text..string.format("\nPitch=%.1f° | Roll=%.1f° | Yaw=%.1f°",pitch,roll,yaw)
text=text..string.format("\nClimb Angle=%.1f° | Rate=%d ft/min",unit:GetClimbAngle(),velo.y*196.85)
local dist=self:_GetOptLandingCoordinate():Get3DDistance(playerData.unit)
local vplayer=playerData.unit:GetVelocityKMH()
local vcarrier=self.carrier:GetVelocityKMH()
local dv=math.abs(vplayer-vcarrier)
local alt=self:_GetAltCarrier(playerData.unit)
text=text..string.format("\nDist=%.1f m Alt=%.1f m delta|V|=%.1f km/h",dist,alt,dv)
if playerData.step==AIRBOSS.PatternStep.FINAL or
playerData.step==AIRBOSS.PatternStep.GROOVE_XX or
playerData.step==AIRBOSS.PatternStep.GROOVE_IM or
playerData.step==AIRBOSS.PatternStep.GROOVE_IC or
playerData.step==AIRBOSS.PatternStep.GROOVE_AR or
playerData.step==AIRBOSS.PatternStep.GROOVE_AL or
playerData.step==AIRBOSS.PatternStep.GROOVE_LC or
playerData.step==AIRBOSS.PatternStep.GROOVE_IW then
local lue=self:_Lineup(playerData.unit,true)
local gle=self:_Glideslope(playerData.unit)
text=text..string.format("\nGamma=%.1f° | Rho=%.1f°",relhead,phi)
text=text..string.format("\nLineUp=%.2f° | GlideSlope=%.2f° | AoA=%.1f Units",lue,gle,self:_AoADeg2Units(playerData,aoa))
local grade,points,analysis=self:_LSOgrade(playerData)
text=text..string.format("\nTgroove=%.1f sec",self:_GetTimeInGroove(playerData))
text=text..string.format("\nGrade: %s %.1f PT - %s",grade,points,analysis)
else
text=text..string.format("\nR=%.2f NM | X=%d Z=%d m",UTILS.MetersToNM(rho),dx,dz)
text=text..string.format("\nGamma=%.1f° | Rho=%.1f°",relhead,phi)
end
MESSAGE:New(text,1,nil,true):ToClient(playerData.client)
end
function AIRBOSS:_Glideslope(unit,optangle)
if optangle==nil then
if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then
optangle=3.0
else
optangle=3.5
end
end
local landingcoord=self:_GetOptLandingCoordinate()
local x=unit:GetCoordinate():Get2DDistance(landingcoord)
local h=self:_GetAltCarrier(unit)
if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then
h=unit:GetAltitude()-(UTILS.FeetToMeters(50)+self.carrierparam.deckheight+2)
end
local glideslope=math.atan(h/x)
local gs=math.deg(glideslope)-optangle
return gs
end
function AIRBOSS:_Glideslope2(unit,optangle)
if optangle==nil then
if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then
optangle=3.0
else
optangle=3.5
end
end
local landingcoord=self:_GetOptLandingCoordinate()
local x=unit:GetCoordinate():Get3DDistance(landingcoord)
local h=self:_GetAltCarrier(unit)
if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then
h=unit:GetAltitude()-(UTILS.FeetToMeters(50)+self.carrierparam.deckheight+2)
end
local glideslope=math.asin(h/x)
local gs=math.deg(glideslope)-optangle
self:T3(self.lid..string.format("Glide slope error = %.1f, x=%.1f h=%.1f",gs,x,h))
return gs
end
function AIRBOSS:_Lineup(unit,runway)
local landingcoord=self:_GetOptLandingCoordinate()
local A=landingcoord:GetVec3()
local B=unit:GetVec3()
local C=UTILS.VecSubstract(A,B)
C.y=0.0
local X=self.carrier:GetOrientationX()
X.y=0.0
if runway then
X=UTILS.Rotate2D(X,-self.carrierparam.rwyangle)
end
local x=UTILS.VecDot(X,C)
local Z=self.carrier:GetOrientationZ()
Z.y=0.0
if runway then
Z=UTILS.Rotate2D(Z,-self.carrierparam.rwyangle)
end
local z=UTILS.VecDot(Z,C)
local lineup=math.deg(math.atan2(z,x))
return lineup
end
function AIRBOSS:_GetAltCarrier(unit)
local h=unit:GetAltitude()-self.carrierparam.deckheight-2
return h
end
function AIRBOSS:_GetOptLandingCoordinate()
self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord())
local FB=self:GetFinalBearing(false)
local case=self.case
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
if case==3 then
self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate())
self.landingcoord:SetAltitude(UTILS.FeetToMeters(120))
elseif case==2 or case==1 then
self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35,FB-90,true,true)
self.landingcoord:SetAltitude(UTILS.FeetToMeters(120))
end
else
if self.carrierparam.wire3 then
self.landingcoord:Translate(self.carrierparam.wire3,FB,true,true)
end
self.landingcoord.y=self.landingcoord.y+2
end
return self.landingcoord
end
function AIRBOSS:_GetLandingSpotCoordinate()
self.landingspotcoord:UpdateFromCoordinate(self:_GetSternCoord())
local hdg=self:GetHeading()
self.landingspotcoord:Translate(self.carrierparam.landingspot,hdg,true,true):SetAltitude(self.carrierparam.deckheight)
return self.landingspotcoord
end
function AIRBOSS:GetHeading(magnetic)
self:F3({magnetic=magnetic})
local hdg=self.carrier:GetHeading()
if magnetic then
hdg=hdg-self.magvar
end
if hdg<0 then
hdg=hdg+360
end
return hdg
end
function AIRBOSS:GetBRC()
return self:GetHeading(true)
end
function AIRBOSS:GetWind(alt,magnetic,coord)
local cv=coord or self:GetCoordinate()
local Wdir,Wspeed=cv:GetWind(alt or 18)
if magnetic then
Wdir=Wdir-self.magvar
if Wdir<0 then
Wdir=Wdir+360
end
end
return Wdir,Wspeed
end
function AIRBOSS:GetWindOnDeck(alt)
local cv=self:GetCoordinate()
local vc=self.carrier:GetVelocityVec3()
local xc=self.carrier:GetOrientationX()
local zc=self.carrier:GetOrientationZ()
xc=UTILS.Rotate2D(xc,-self.carrierparam.rwyangle)
zc=UTILS.Rotate2D(zc,-self.carrierparam.rwyangle)
local vw=cv:GetWindWithTurbulenceVec3(alt or 18)
local vT=UTILS.VecSubstract(vw,vc)
local vpa=UTILS.VecDot(vT,xc)
local vpp=UTILS.VecDot(vT,zc)
local vabs=UTILS.VecNorm(vT)
return-vpa,vpp,vabs
end
function AIRBOSS:GetHeadingIntoWind(magnetic,coord)
local windfrom,vwind=self:GetWind(nil,nil,coord)
local intowind=windfrom-self.carrierparam.rwyangle
if vwind<0.1 then
intowind=self:GetHeading()
end
if magnetic then
intowind=intowind-self.magvar
end
if intowind<0 then
intowind=intowind+360
end
return intowind
end
function AIRBOSS:GetBRCintoWind()
return self:GetHeadingIntoWind(true)
end
function AIRBOSS:GetFinalBearing(magnetic)
local fb=self:GetHeading(magnetic)
fb=fb+self.carrierparam.rwyangle
if fb<0 then
fb=fb+360
end
return fb
end
function AIRBOSS:GetRadial(case,magnetic,offset,inverse)
case=case or self.case
local radial
if case==1 then
radial=self:GetFinalBearing(magnetic)-180
elseif case==2 then
radial=self:GetHeading(magnetic)-180
if offset then
radial=radial+self.holdingoffset
end
elseif case==3 then
radial=self:GetFinalBearing(magnetic)-180
if offset then
radial=radial+self.holdingoffset
end
end
if radial<0 then
radial=radial+360
end
if inverse then
radial=radial-180
if radial<0 then
radial=radial+360
end
end
return radial
end
function AIRBOSS:_GetDeltaHeading(hdg1,hdg2)
local V={}
V.x=math.cos(math.rad(hdg1))
V.y=0
V.z=math.sin(math.rad(hdg1))
local W={}
W.x=math.cos(math.rad(hdg2))
W.y=0
W.z=math.sin(math.rad(hdg2))
local alpha=UTILS.VecAngle(V,W)
return alpha
end
function AIRBOSS:_GetRelativeHeading(unit,runway)
local vC=self.carrier:GetOrientationX()
if runway then
vC=UTILS.Rotate2D(vC,-self.carrierparam.rwyangle)
end
local vP=unit:GetOrientationX()
vC.y=0;
vP.y=0
local rhdg=UTILS.VecAngle(vC,vP)
return rhdg
end
function AIRBOSS:_GetRelativeVelocity(unit)
local vC=self.carrier:GetVelocityVec3()
local vP=unit:GetVelocityVec3()
vC.y=0;
vP.y=0
local v=UTILS.VecSubstract(vP,vC)
return UTILS.VecNorm(v),v
end
function AIRBOSS:_GetDistances(unit)
local a=self.carrier:GetVec3()
local b=unit:GetVec3()
local c={x=b.x-a.x,y=0,z=b.z-a.z}
local x=self.carrier:GetOrientationX()
local dx=UTILS.VecDot(x,c)
local z=self.carrier:GetOrientationZ()
local dz=UTILS.VecDot(z,c)
local rho=math.sqrt(dx*dx+dz*dz)
local phi=math.deg(math.atan2(dz,dx))
if phi<0 then
phi=phi+360
end
return dx,dz,rho,phi
end
function AIRBOSS:_CheckLimits(X,Z,check)
local nextXmin=check.LimitXmin==nil or(check.LimitXmin and(check.LimitXmin<0 and X<=check.LimitXmin or check.LimitXmin>=0 and X>=check.LimitXmin))
local nextXmax=check.LimitXmax==nil or(check.LimitXmax and(check.LimitXmax<0 and X>=check.LimitXmax or check.LimitXmax>=0 and X<=check.LimitXmax))
local nextZmin=check.LimitZmin==nil or(check.LimitZmin and(check.LimitZmin<0 and Z<=check.LimitZmin or check.LimitZmin>=0 and Z>=check.LimitZmin))
local nextZmax=check.LimitZmax==nil or(check.LimitZmax and(check.LimitZmax<0 and Z>=check.LimitZmax or check.LimitZmax>=0 and Z<=check.LimitZmax))
local next=nextXmin and nextXmax and nextZmin and nextZmax
local text=string.format("step=%s: next=%s: X=%d Xmin=%s Xmax=%s | Z=%d Zmin=%s Zmax=%s",check.name,tostring(next),X,tostring(check.LimitXmin),tostring(check.LimitXmax),Z,tostring(check.LimitZmin),tostring(check.LimitZmax))
self:T3(self.lid..text)
return next
end
function AIRBOSS:_LSOadvice(playerData,glideslopeError,lineupError)
local advice=0
if glideslopeError>self.gle.HIGH then
self:RadioTransmission(self.LSORadio,self.LSOCall.HIGH,true,nil,nil,true)
advice=advice+self.LSOCall.HIGH.duration
elseif glideslopeError>self.gle.High then
self:RadioTransmission(self.LSORadio,self.LSOCall.HIGH,false,nil,nil,true)
advice=advice+self.LSOCall.HIGH.duration
elseif glideslopeError<self.gle.LOW then
self:RadioTransmission(self.LSORadio,self.LSOCall.POWER,true,nil,nil,true)
advice=advice+self.LSOCall.POWER.duration
elseif glideslopeError<self.gle.Low then
self:RadioTransmission(self.LSORadio,self.LSOCall.POWER,false,nil,nil,true)
advice=advice+self.LSOCall.POWER.duration
else
end
if lineupError<self.lue.LEFT then
self:RadioTransmission(self.LSORadio,self.LSOCall.COMELEFT,true,nil,nil,true)
advice=advice+self.LSOCall.COMELEFT.duration
elseif lineupError<self.lue.Left then
self:RadioTransmission(self.LSORadio,self.LSOCall.COMELEFT,false,nil,nil,true)
advice=advice+self.LSOCall.COMELEFT.duration
elseif lineupError>self.lue.RIGHT then
self:RadioTransmission(self.LSORadio,self.LSOCall.RIGHTFORLINEUP,true,nil,nil,true)
advice=advice+self.LSOCall.RIGHTFORLINEUP.duration
elseif lineupError>self.lue.Right then
self:RadioTransmission(self.LSORadio,self.LSOCall.RIGHTFORLINEUP,false,nil,nil,true)
advice=advice+self.LSOCall.RIGHTFORLINEUP.duration
else
end
local AOA=playerData.unit:GetAoA()
local acaoa=self:_GetAircraftAoA(playerData)
if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then
if AOA>acaoa.SLOW then
self:RadioTransmission(self.LSORadio,self.LSOCall.SLOW,true,nil,nil,true)
advice=advice+self.LSOCall.SLOW.duration
elseif AOA>acaoa.Slow then
self:RadioTransmission(self.LSORadio,self.LSOCall.SLOW,false,nil,nil,true)
advice=advice+self.LSOCall.SLOW.duration
elseif AOA>acaoa.OnSpeedMax then
elseif AOA<acaoa.FAST then
self:RadioTransmission(self.LSORadio,self.LSOCall.FAST,true,nil,nil,true)
advice=advice+self.LSOCall.FAST.duration
elseif AOA<acaoa.Fast then
self:RadioTransmission(self.LSORadio,self.LSOCall.FAST,false,nil,nil,true)
advice=advice+self.LSOCall.FAST.duration
elseif AOA<acaoa.OnSpeedMin then
end
end
playerData.Tlso=timer.getTime()
end
function AIRBOSS:_EvalGrooveTime(playerData)
local t=playerData.Tgroove
local grade=""
if t<9 then
grade="_NESA_"
elseif t<15 then
grade="NESA"
elseif t<25 then
grade="OK Groove"
elseif t<=30 then
grade="(LIG)"
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<55 then
grade="FAST V/STOL Groove"
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<75 then
grade="OK V/STOL Groove"
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t>=76 then
grade="SLOW V/STOL Groove"
else
grade="LIG"
end
if t>=15 and t<25 then
grade="_OK_"
end
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and(t>=60.0 and t<=65.0)then
grade="_OK_ V/STOL"
end
return grade
end
function AIRBOSS:_LSOgrade(playerData)
local function count(base,pattern)
return select(2,string.gsub(base,pattern,""))
end
local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX)
local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM)
local GIC,nIC=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IC)
local GAR,nAR=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.AR)
local G=GXX.." "..GIM.." ".." "..GIC.." "..GAR
local N=nXX+nIM+nIC+nAR
local Nv=nXX+nIM
local nL=count(G,'_')/2
local nS=count(G,'%(')
local nN=N-nS-nL
local nNv=Nv-nS-nL
local Tgroove=playerData.Tgroove
local TgrooveUnicorn=Tgroove and(Tgroove>=15.0 and Tgroove<25.00)or false
local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false
local grade
local points
if N==0 and(TgrooveUnicorn or TgrooveVstolUnicorn or playerData.case==3)then
grade="_OK_"
points=5.0
G="Unicorn"
else
if nL>1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
grade="--"
points=2.0
elseif nNv>=1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
grade="(OK)"
points=3.0
elseif nNv<1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
grade="OK"
points=4.0
elseif nL>0 then
grade="--"
points=2.0
elseif nN>0 then
grade="(OK)"
points=3.0
else
grade="OK"
points=4.0
end
end
G=G:gsub("%)%(","")
G=G:gsub("__","")
local text="LSO grade:\n"
text=text..G.."\n"
text=text.."Grade = "..grade.." points = "..points.."\n"
text=text.."# of total deviations   = "..N.."\n"
text=text.."# of large deviations _ = "..nL.."\n"
text=text.."# of normal deviations  = "..nN.."\n"
text=text.."# of small deviations ( = "..nS.."\n"
self:T2(self.lid..text)
if playerData.wop then
if playerData.lig then
grade="WO"
points=1.0
G="LIG"
else
grade="WOP"
points=2.0
G="n/a"
end
elseif playerData.wofd then
if playerData.landed then
grade="CUT"
points=0.0
else
grade="WOFD"
points=-1.0
end
G="n/a"
elseif playerData.owo then
grade="OWO"
points=2.0
if N==0 then
G="n/a"
end
elseif playerData.waveoff then
if playerData.landed then
grade="CUT"
points=0.0
else
grade="WO"
points=1.0
end
elseif playerData.boltered then
grade="-- (BOLTER)"
points=2.5
elseif not playerData.hover and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
if playerData.landed then
grade="CUT"
points=0.0
end
end
return grade,points,G
end
function AIRBOSS:_Flightdata2Text(playerData,groovestep)
local function little(text)
return string.format("(%s)",text)
end
local function underline(text)
return string.format("_%s_",text)
end
local fdata=playerData.groove[groovestep]
if fdata==nil then
self:T3(self.lid.."Flight data is nil.")
return"",0
end
local step=fdata.Step
local AOA=fdata.AoA
local GSE=fdata.GSE
local LUE=fdata.LUE
local ROL=fdata.Roll
local acaoa=self:_GetAircraftAoA(playerData)
local P=nil
if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=4.0 and playerData.case<3 then
if LUE>self.lue.RIGHT then
P=underline("AA")
elseif LUE>self.lue.RightMed then
P="AA "
elseif LUE>self.lue.Right then
P=little("AA")
end
end
local O=nil
if step==AIRBOSS.PatternStep.GROOVE_XX then
if LUE<self.lue.LEFT then
O=underline("OS")
elseif LUE<self.lue.Left then
O="OS"
elseif LUE<self.lue._min then
O=little("OS")
end
end
local S=nil
if AOA>acaoa.SLOW then
S=underline("SLO")
elseif AOA>acaoa.Slow then
S="SLO"
elseif AOA>acaoa.OnSpeedMax then
S=little("SLO")
elseif AOA<acaoa.FAST then
S=underline("F")
elseif AOA<acaoa.Fast then
S="F"
elseif AOA<acaoa.OnSpeedMin then
S=little("F")
end
local A=nil
if GSE>self.gle.HIGH then
A=underline("H")
elseif GSE>self.gle.High then
A="H"
elseif GSE>self.gle._max then
A=little("H")
elseif GSE<self.gle.LOW then
A=underline("LO")
elseif GSE<self.gle.Low then
A="LO"
elseif GSE<self.gle._min then
A=little("LO")
end
local D=nil
if LUE>self.lue.RIGHT then
D=underline("LUL")
elseif LUE>self.lue.Right then
D="LUL"
elseif LUE>self.lue._max then
D=little("LUL")
elseif playerData.case<3 then
if LUE<self.lue.LEFT and step~=AIRBOSS.PatternStep.GROOVE_XX then
D=underline("LUR")
elseif LUE<self.lue.Left and step~=AIRBOSS.PatternStep.GROOVE_XX then
D="LUR"
elseif LUE<self.lue._min and step~=AIRBOSS.PatternStep.GROOVE_XX then
D=little("LUR")
end
elseif playerData.case==3 then
if LUE<self.lue.LEFT then
D=underline("LUR")
elseif LUE<self.lue.Left then
D="LUR"
elseif LUE<self.lue._min then
D=little("LUR")
end
end
local G=""
local n=0
if fdata.FlyThrough then
G=G..fdata.FlyThrough
end
if P then
G=G..P
n=n
end
if S then
G=G..S
n=n+1
end
if A then
G=G..A
n=n+1
end
if D then
G=G..D
n=n+1
end
if fdata.Drift then
G=G..fdata.Drift
n=n
end
if O then
G=G..O
n=n+1
end
local step=self:_GS(step)
step=step:gsub("XX","X")
if G~=""then
G=G..step
end
local text=string.format("LSO Grade at %s:\n",step)
text=text..string.format("AOA=%.1f\n",AOA)
text=text..string.format("GSE=%.1f\n",GSE)
text=text..string.format("LUE=%.1f\n",LUE)
text=text..string.format("ROL=%.1f\n",ROL)
text=text..G
self:T3(self.lid..text)
return G,n
end
function AIRBOSS:_GS(step,n)
local gp
n=n or 0
if step==AIRBOSS.PatternStep.FINAL then
gp=AIRBOSS.GroovePos.X0
if n==-1 then
gp=AIRBOSS.GroovePos.X0
elseif n==1 then
gp=AIRBOSS.GroovePos.XX
end
elseif step==AIRBOSS.PatternStep.GROOVE_XX then
gp=AIRBOSS.GroovePos.XX
if n==-1 then
gp=AIRBOSS.GroovePos.X0
elseif n==1 then
gp=AIRBOSS.GroovePos.IM
end
elseif step==AIRBOSS.PatternStep.GROOVE_IM then
gp=AIRBOSS.GroovePos.IM
if n==-1 then
gp=AIRBOSS.GroovePos.XX
elseif n==1 then
gp=AIRBOSS.GroovePos.IC
end
elseif step==AIRBOSS.PatternStep.GROOVE_IC then
gp=AIRBOSS.GroovePos.IC
if n==-1 then
gp=AIRBOSS.GroovePos.IM
elseif n==1 then
gp=AIRBOSS.GroovePos.AR
end
elseif step==AIRBOSS.PatternStep.GROOVE_AR then
gp=AIRBOSS.GroovePos.AR
if n==-1 then
gp=AIRBOSS.GroovePos.IC
elseif n==1 then
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
gp=AIRBOSS.GroovePos.AL
else
gp=AIRBOSS.GroovePos.IW
end
end
elseif step==AIRBOSS.PatternStep.GROOVE_AL then
gp=AIRBOSS.GroovePos.AL
if n==-1 then
gp=AIRBOSS.GroovePos.AR
elseif n==1 then
gp=AIRBOSS.GroovePos.LC
end
elseif step==AIRBOSS.PatternStep.GROOVE_LC then
gp=AIRBOSS.GroovePos.LC
if n==-1 then
gp=AIRBOSS.GroovePos.AL
elseif n==1 then
gp=AIRBOSS.GroovePos.LC
end
elseif step==AIRBOSS.PatternStep.GROOVE_IW then
gp=AIRBOSS.GroovePos.IW
if n==-1 then
gp=AIRBOSS.GroovePos.AR
elseif n==1 then
gp=AIRBOSS.GroovePos.IW
end
end
return gp
end
function AIRBOSS:_CheckAbort(X,Z,pos)
local abort=false
if pos.Xmin and X<pos.Xmin then
self:T(string.format("Xmin: X=%d < %d=Xmin",X,pos.Xmin))
abort=true
elseif pos.Xmax and X>pos.Xmax then
self:T(string.format("Xmax: X=%d > %d=Xmax",X,pos.Xmax))
abort=true
elseif pos.Zmin and Z<pos.Zmin then
self:T(string.format("Zmin: Z=%d < %d=Zmin",Z,pos.Zmin))
abort=true
elseif pos.Zmax and Z>pos.Zmax then
self:T(string.format("Zmax: Z=%d > %d=Zmax",Z,pos.Zmax))
abort=true
end
return abort
end
function AIRBOSS:_TooFarOutText(X,Z,posData)
local text="you are too "
local xtext=nil
if posData.Xmin and X<posData.Xmin then
if posData.Xmin<=0 then
xtext="far behind "
else
xtext="close to "
end
elseif posData.Xmax and X>posData.Xmax then
if posData.Xmax>=0 then
xtext="far ahead of "
else
xtext="close to "
end
end
local ztext=nil
if posData.Zmin and Z<posData.Zmin then
if posData.Zmin<=0 then
ztext="far port of "
else
ztext="close to "
end
elseif posData.Zmax and Z>posData.Zmax then
if posData.Zmax>=0 then
ztext="far starboard of "
else
ztext="too close to "
end
end
if xtext and ztext then
text=text..xtext.." and "..ztext
elseif xtext then
text=text..xtext
elseif ztext then
text=text..ztext
end
text=text.."the carrier."
if xtext==nil and ztext==nil then
text="you are too far from where you should be!"
end
return text
end
function AIRBOSS:_AbortPattern(playerData,X,Z,posData,patternwo)
local text=self:_TooFarOutText(X,Z,posData)
local dtext=string.format("Abort: X=%d Xmin=%s, Xmax=%s | Z=%d Zmin=%s Zmax=%s",X,tostring(posData.Xmin),tostring(posData.Xmax),Z,tostring(posData.Zmin),tostring(posData.Zmax))
self:T(self.lid..dtext)
self:MessageToPlayer(playerData,text,"LSO")
if patternwo then
playerData.wop=true
self:_AddToDebrief(playerData,string.format("Pattern wave off: %s",text))
self:RadioTransmission(self.LSORadio,self.LSOCall.DEPARTANDREENTER,false,3,nil,nil,true)
playerData.step=AIRBOSS.PatternStep.DEBRIEF
playerData.warning=nil
end
end
function AIRBOSS:_PlayerHint(playerData,delay,soundoff)
if not playerData.showhints then
return
end
local alt,aoa,dist,speed=self:_GetAircraftParameters(playerData)
local hintAlt,debriefAlt,callAlt=self:_AltitudeCheck(playerData,alt)
local hintSpeed,debriefSpeed,callSpeed=self:_SpeedCheck(playerData,speed)
local hintAoA,debriefAoA,callAoA=self:_AoACheck(playerData,aoa)
local hintDist,debriefDist,callDist=self:_DistanceCheck(playerData,dist)
local hint=""
if hintAlt and hintAlt~=""then
hint=hint.."\n"..hintAlt
end
if hintSpeed and hintSpeed~=""then
hint=hint.."\n"..hintSpeed
end
if hintAoA and hintAoA~=""then
hint=hint.."\n"..hintAoA
end
if hintDist and hintDist~=""then
hint=hint.."\n"..hintDist
end
local debrief=""
if debriefAlt and debriefAlt~=""then
debrief=debrief.."\n- "..debriefAlt
end
if debriefSpeed and debriefSpeed~=""then
debrief=debrief.."\n- "..debriefSpeed
end
if debriefAoA and debriefAoA~=""then
debrief=debrief.."\n- "..debriefAoA
end
if debriefDist and debriefDist~=""then
debrief=debrief.."\n- "..debriefDist
end
if debrief~=""then
self:_AddToDebrief(playerData,debrief)
end
delay=delay or 0
if not soundoff then
if callAlt then
self:Sound2Player(playerData,self.LSORadio,callAlt,false,delay)
delay=delay+callAlt.duration+0.5
end
if callSpeed then
self:Sound2Player(playerData,self.LSORadio,callSpeed,false,delay)
delay=delay+callSpeed.duration+0.5
end
if callAoA then
self:Sound2Player(playerData,self.LSORadio,callAoA,false,delay)
delay=delay+callAoA.duration+0.5
end
if callDist then
self:Sound2Player(playerData,self.LSORadio,callDist,false,delay)
delay=delay+callDist.duration+0.5
end
end
if playerData.step==AIRBOSS.PatternStep.ARCIN then
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
local radial=self:GetRadial(playerData.case,true,false,true)
local turn="right"
if self.holdingoffset<0 then
turn="left"
end
hint=hint..string.format("\nTurn %s and select TACAN %03d°.",turn,radial)
end
end
if playerData.step==AIRBOSS.PatternStep.DIRTYUP then
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
hint=hint.."\nFAF! Checks completed. Nozzles 50°."
else
hint=hint.."\nDirty up! Hook, gear and flaps down."
end
end
end
if playerData.step==AIRBOSS.PatternStep.BULLSEYE then
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
if playerData.actype==AIRBOSS.AircraftCarrier.HORNET
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER then
hint=hint..string.format("\nIntercept glideslope and follow the needles.")
else
hint=hint..string.format("\nIntercept glideslope.")
end
end
end
if hint~=""then
local text=string.format("%s%s",playerData.step,hint)
self:MessageToPlayer(playerData,hint,"AIRBOSS","")
end
end
function AIRBOSS:_StepHint(playerData,step)
step=step or playerData.step
if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then
local alt,aoa,dist,speed=self:_GetAircraftParameters(playerData,step)
local hint=""
if alt then
hint=hint..string.format("\nAltitude %d ft",UTILS.MetersToFeet(alt))
end
if aoa then
hint=hint..string.format("\nAoA %.1f",self:_AoADeg2Units(playerData,aoa))
end
if speed then
hint=hint..string.format("\nSpeed %d knots",UTILS.MpsToKnots(speed))
end
if dist then
hint=hint..string.format("\nDistance to the boat %.1f NM",UTILS.MetersToNM(dist))
end
if step==AIRBOSS.PatternStep.LATEBREAK then
if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
hint=hint.."\nWing Sweep 20°, Gear DOWN < 280 KIAS."
end
end
if step==AIRBOSS.PatternStep.ABEAM then
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
hint=hint.."\nNozzles 50°-60°. Antiskid OFF. Lights OFF."
elseif playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
hint=hint.."\nSlats/Flaps EXTENDED < 225 KIAS. DLC SELECTED. Auto Throttle IF DESIRED."
else
hint=hint.."\nDirty up! Gear DOWN, flaps DOWN. Check hook down."
end
end
if hint~=""then
local text=string.format("Optimal setup at next step %s:%s",step,hint)
self:MessageToPlayer(playerData,text,"AIRBOSS","",nil,false,1)
end
end
end
function AIRBOSS:_AltitudeCheck(playerData,altopt)
if altopt==nil then
return nil,nil
end
local altitude=playerData.unit:GetAltitude()
local lowscore,badscore=self:_GetGoodBadScore(playerData)
local _error=(altitude-altopt)/altopt*100
local radiocall=nil
local hint=""
if _error>badscore then
radiocall=self:_NewRadioCall(self.LSOCall.HIGH,"Paddles","")
elseif _error>lowscore then
radiocall=self:_NewRadioCall(self.LSOCall.HIGH,"Paddles","")
elseif _error<-badscore then
radiocall=self:_NewRadioCall(self.LSOCall.LOW,"Paddles","")
elseif _error<-lowscore then
radiocall=self:_NewRadioCall(self.LSOCall.LOW,"Paddles","")
else
hint=string.format("Good altitude. ")
end
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
hint=hint..string.format("Optimal altitude is %d ft.",UTILS.MetersToFeet(altopt))
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
hint=""
elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then
hint=""
end
local debrief=string.format("Altitude %d ft = %d%% deviation from %d ft.",UTILS.MetersToFeet(altitude),_error,UTILS.MetersToFeet(altopt))
return hint,debrief,radiocall
end
function AIRBOSS:_AoACheck(playerData,optaoa)
if optaoa==nil then
return nil,nil
end
local lowscore,badscore=self:_GetGoodBadScore(playerData)
local aoa=playerData.unit:GetAoA()
local _error=(aoa-optaoa)/optaoa*100
local aircraftaoa=self:_GetAircraftAoA(playerData)
local radiocall=nil
local hint=""
if aoa>=aircraftaoa.SLOW then
radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"Paddles","")
elseif aoa>=aircraftaoa.Slow then
radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"Paddles","")
elseif aoa>=aircraftaoa.OnSpeedMax then
hint="Your're a little slow. "
elseif aoa>=aircraftaoa.OnSpeedMin then
hint="You're on speed. "
elseif aoa>=aircraftaoa.Fast then
hint="You're a little fast. "
elseif aoa>=aircraftaoa.FAST then
radiocall=self:_NewRadioCall(self.LSOCall.FAST,"Paddles","")
else
radiocall=self:_NewRadioCall(self.LSOCall.FAST,"Paddles","")
end
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
hint=hint..string.format("Optimal AoA is %.1f.",self:_AoADeg2Units(playerData,optaoa))
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
hint=""
elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then
hint=""
end
local debrief=string.format("AoA %.1f = %d%% deviation from %.1f.",self:_AoADeg2Units(playerData,aoa),_error,self:_AoADeg2Units(playerData,optaoa))
return hint,debrief,radiocall
end
function AIRBOSS:_SpeedCheck(playerData,speedopt)
if speedopt==nil then
return nil,nil
end
local speed=playerData.unit:GetVelocityMPS()
local lowscore,badscore=self:_GetGoodBadScore(playerData)
local _error=(speed-speedopt)/speedopt*100
local radiocall=nil
local hint=""
if _error>badscore then
radiocall=self:_NewRadioCall(self.LSOCall.FAST,"AIRBOSS","")
elseif _error>lowscore then
radiocall=self:_NewRadioCall(self.LSOCall.FAST,"AIRBOSS","")
elseif _error<-badscore then
radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"AIRBOSS","")
elseif _error<-lowscore then
radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"AIRBOSS","")
else
hint=string.format("Good speed. ")
end
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
hint=hint..string.format("Optimal speed is %d knots.",UTILS.MpsToKnots(speedopt))
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
hint=""
elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then
hint=""
end
local debrief=string.format("Speed %d knots = %d%% deviation from %d knots.",UTILS.MpsToKnots(speed),_error,UTILS.MpsToKnots(speedopt))
return hint,debrief,radiocall
end
function AIRBOSS:_DistanceCheck(playerData,optdist)
if optdist==nil then
return nil,nil
end
local distance=playerData.unit:GetCoordinate():Get2DDistance(self:GetCoordinate())
local lowscore,badscore=self:_GetGoodBadScore(playerData)
local _error=(distance-optdist)/optdist*100
local hint
if _error>badscore then
hint=string.format("You're too far from the boat!")
elseif _error>lowscore then
hint=string.format("You're slightly too far from the boat.")
elseif _error<-badscore then
hint=string.format("You're too close to the boat!")
elseif _error<-lowscore then
hint=string.format("You're slightly too far from the boat.")
else
hint=string.format("Good distance to the boat.")
end
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
hint=hint..string.format(" Optimal distance is %.1f NM.",UTILS.MetersToNM(optdist))
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
hint=""
elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then
hint=""
end
local debrief=string.format("Distance %.1f NM = %d%% deviation from %.1f NM.",UTILS.MetersToNM(distance),_error,UTILS.MetersToNM(optdist))
return hint,debrief,nil
end
function AIRBOSS:_AddToDebrief(playerData,hint,step)
step=step or playerData.step
table.insert(playerData.debrief,{step=step,hint=hint})
end
function AIRBOSS:_Debrief(playerData)
self:F(self.lid..string.format("Debriefing of player %s.",playerData.name))
playerData.debriefschedulerID=nil
playerData.attitudemonitor=false
local grade,points,analysis=self:_LSOgrade(playerData)
if points and points>=0 then
table.insert(playerData.points,points)
end
local Points=0
if playerData.landed and not playerData.unit:InAir()then
for _,_points in pairs(playerData.points)do
Points=Points+_points
end
Points=Points/#playerData.points
playerData.points={}
else
Points=points
end
local mygrade={}
mygrade.grade=grade
mygrade.points=points
mygrade.details=analysis
mygrade.wire=playerData.wire
mygrade.Tgroove=playerData.Tgroove
if playerData.landed and not playerData.unit:InAir()then
mygrade.finalscore=Points
end
mygrade.case=playerData.case
local windondeck=self:GetWindOnDeck()
mygrade.wind=UTILS.Round(UTILS.MpsToKnots(windondeck),1)
mygrade.modex=playerData.onboard
mygrade.airframe=playerData.actype
mygrade.carriertype=self.carriertype
mygrade.carriername=self.alias
mygrade.carrierrwy=self.carrierparam.rwyangle
mygrade.theatre=self.theatre
mygrade.mitime=UTILS.SecondsToClock(timer.getAbsTime(),true)
mygrade.midate=UTILS.GetDCSMissionDate()
mygrade.osdate="n/a"
if os then
mygrade.osdate=os.date()
end
playerData.grade=mygrade
if playerData.trapon and self.trapsheet then
self:_SaveTrapSheet(playerData,mygrade)
end
table.insert(self.playerscores[playerData.name],mygrade)
self:LSOGrade(playerData,mygrade)
local text=string.format("%s %.1f PT - %s",grade,Points,analysis)
if Points==-1 then
text=string.format("%s n/a PT - Foul deck",grade,Points,analysis)
end
if not(playerData.wop or playerData.wofd)then
if playerData.wire and playerData.wire<=4 then
text=text..string.format(" %d-wire",playerData.wire)
end
if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then
text=text..string.format("\nTime in the groove %.1f seconds: %s",playerData.Tgroove,self:_EvalGrooveTime(playerData))
end
end
playerData.lastdebrief=UTILS.DeepCopy(playerData.debrief)
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
text=text..string.format("\nYour detailed debriefing can be found via the F10 radio menu.")
end
self:MessageToPlayer(playerData,text,"LSO","",30,true)
playerData.step=AIRBOSS.PatternStep.UNDEFINED
if playerData.wop then
if playerData.unit:IsAlive()then
local heading,distance
if playerData.case==1 or playerData.case==2 then
playerData.step=AIRBOSS.PatternStep.INITIAL
local initial=self:GetCoordinate():Translate(UTILS.NMToMeters(3.5),self:GetRadial(2,false,false,false))
heading=playerData.unit:GetCoordinate():HeadingTo(initial)
distance=playerData.unit:GetCoordinate():Get2DDistance(initial)
elseif playerData.case==3 then
playerData.step=AIRBOSS.PatternStep.BULLSEYE
local zone=self:_GetZoneBullseye(playerData.case)
heading=playerData.unit:GetCoordinate():HeadingTo(zone:GetCoordinate())
distance=playerData.unit:GetCoordinate():Get2DDistance(zone:GetCoordinate())
end
local text=string.format("fly heading %03d° for %d NM to re-enter the pattern.",heading,UTILS.MetersToNM(distance))
self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,5)
else
self:E(self.lid..string.format("ERROR: Player unit not alive!"))
end
elseif playerData.wofd then
if playerData.unit:InAir()then
playerData.step=AIRBOSS.PatternStep.BOLTER
else
self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD)
local text=string.format("deck was fouled but you landed anyway. Airboss wants to talk to you!")
self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3)
end
elseif playerData.owo then
if playerData.unit:InAir()then
playerData.step=AIRBOSS.PatternStep.BOLTER
else
self:E(self.lid.."ERROR: player landed when OWO was issues. This should not happen. Please report!")
self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD)
end
elseif playerData.waveoff then
if playerData.unit:InAir()then
playerData.step=AIRBOSS.PatternStep.BOLTER
else
self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD)
local text=string.format("you were waved off but landed anyway. Airboss wants to talk to you!")
self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3)
end
elseif playerData.boltered then
if playerData.unit:InAir()then
playerData.step=AIRBOSS.PatternStep.BOLTER
end
elseif playerData.landed then
if not playerData.unit:InAir()then
self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD)
end
else
self:MessageToPlayer(playerData,"Undefined state after landing! Please report.","ERROR",nil,20)
playerData.step=AIRBOSS.PatternStep.UNDEFINED
end
if playerData.landed and not playerData.unit:InAir()then
self:_RecoveredElement(playerData.unit)
self:_CheckSectionRecovered(playerData)
end
playerData.passes=playerData.passes+1
self:_StepHint(playerData)
self:_InitPlayer(playerData,playerData.step)
MESSAGE:New(string.format("Player step %s.",playerData.step),5,"DEBUG"):ToAllIf(self.Debug)
if self.autosave and mygrade.finalscore then
self:Save(self.autosavepath,self.autosavefile)
end
end
function AIRBOSS:_CheckCollisionCoord(coordto,coordfrom)
local dx=100
local d=0
if coordfrom then
d=0
else
d=250
coordfrom=self:GetCoordinate():Translate(d,self:GetHeading())
end
local dmax=coordfrom:Get2DDistance(coordto)
local direction=coordfrom:HeadingTo(coordto)
local clear=true
while d<=dmax do
local cp=coordfrom:Translate(d,direction)
if not cp:IsSurfaceTypeWater()then
if self.Debug then
local st=cp:GetSurfaceType()
cp:MarkToAll(string.format("Collision check surface type %d",st))
end
clear=false
break
end
d=d+dx
end
local text=""
if clear then
text=string.format("Path into direction %03d° is clear for the next %.1f NM.",direction,UTILS.MetersToNM(d))
else
text=string.format("Detected obstacle at distance %.1f NM into direction %03d°.",UTILS.MetersToNM(d),direction)
end
self:T2(self.lid..text)
return not clear,d
end
function AIRBOSS:_CheckFreePathToNextWP(fromcoord)
fromcoord=fromcoord or self:GetCoordinate():Translate(250,self:GetHeading())
local Nnextwp=math.min(self.currentwp+1,#self.waypoints)
local nextwp=self.waypoints[Nnextwp]
local collision=self:_CheckCollisionCoord(nextwp,fromcoord)
return collision
end
function AIRBOSS:_Pathfinder()
local hdg=self:GetHeading()
local cv=self:GetCoordinate()
local directions={-20,20,-30,30,-40,40,-50,50,-60,60,-70,70,-80,80,-90,90,-100,100}
for _,_direction in pairs(directions)do
local direction=hdg+_direction
local _,dfree=self:_CheckCollisionCoord(cv:Translate(UTILS.NMToMeters(20),direction),cv)
local distance=500
while distance<=dfree do
local fromcoord=cv:Translate(distance,direction)
local collision=self:_CheckFreePathToNextWP(fromcoord)
self:T2(self.lid..string.format("Pathfinder d=%.1f m, direction=%03d°, collision=%s",distance,direction,tostring(collision)))
if not collision then
self:CarrierDetour(fromcoord)
return
end
distance=distance+500
end
end
end
function AIRBOSS:CarrierResumeRoute(gotocoord)
AIRBOSS._ResumeRoute(self.carrier:GetGroup(),self,gotocoord)
return self
end
function AIRBOSS:CarrierDetour(coord,speed,uturn,uspeed,tcoord)
local pos0=self:GetCoordinate()
local vel0=self.carrier:GetVelocityKNOTS()
speed=speed or math.max(vel0,5)
local speedkmh=math.max(UTILS.KnotsToKmph(speed),UTILS.KnotsToKmph(2))
local cspeedkmh=math.max(self.carrier:GetVelocityKMH(),UTILS.KnotsToKmph(10))
local uspeedkmh=UTILS.KnotsToKmph(uspeed or speed)
local wp={}
table.insert(wp,pos0:WaypointGround(cspeedkmh))
if tcoord then
table.insert(wp,tcoord:WaypointGround(cspeedkmh))
end
table.insert(wp,coord:WaypointGround(speedkmh))
if uturn then
table.insert(wp,pos0:WaypointGround(uspeedkmh))
end
local group=self.carrier:GetGroup()
local TaskResumeRoute=group:TaskFunction("AIRBOSS._ResumeRoute",self)
group:SetTaskWaypoint(wp[#wp],TaskResumeRoute)
if self.Debug then
if tcoord then
tcoord:MarkToAll(string.format("Detour Turn Help WP. Speed %.1f knots",UTILS.KmphToKnots(cspeedkmh)))
end
coord:MarkToAll(string.format("Detour Waypoint. Speed %.1f knots",UTILS.KmphToKnots(speedkmh)))
if uturn then
pos0:MarkToAll(string.format("Detour U-turn WP. Speed %.1f knots",UTILS.KmphToKnots(uspeedkmh)))
end
end
self.detour=true
self.carrier:Route(wp)
end
function AIRBOSS:CarrierTurnIntoWind(time,vdeck,uturn)
local _,vwind=self:GetWind()
local vtot=math.max(vdeck-vwind,UTILS.KnotsToMps(2))
local dist=vtot*time
local speedknots=UTILS.MpsToKnots(vtot)
local distNM=UTILS.MetersToNM(dist)
self:I(self.lid..string.format("Carrier steaming into the wind (%.1f kts). Distance=%.1f NM, Speed=%.1f knots, Time=%d sec.",UTILS.MpsToKnots(vwind),distNM,speedknots,time))
local hiw=self:GetHeadingIntoWind()
local hdg=self:GetHeading()
local deltaH=self:_GetDeltaHeading(hdg,hiw)
local Cv=self:GetCoordinate()
local Ctiw=nil
local Csoo=nil
if deltaH<45 then
Csoo=Cv:Translate(750,hdg):Translate(750,hiw)
local hsw=self:GetHeadingIntoWind(false,Csoo)
Ctiw=Csoo:Translate(dist,hsw)
elseif deltaH<90 then
Csoo=Cv:Translate(900,hdg):Translate(900,hiw)
local hsw=self:GetHeadingIntoWind(false,Csoo)
Ctiw=Csoo:Translate(dist,hsw)
elseif deltaH<135 then
Csoo=Cv:Translate(1100,hdg-90):Translate(1000,hiw)
local hsw=self:GetHeadingIntoWind(false,Csoo)
Ctiw=Csoo:Translate(dist,hsw)
else
Csoo=Cv:Translate(1200,hdg-90):Translate(1000,hiw)
local hsw=self:GetHeadingIntoWind(false,Csoo)
Ctiw=Csoo:Translate(dist,hsw)
end
self.Creturnto=self:GetCoordinate()
local nextwp=self:_GetNextWaypoint()
local vdownwind=UTILS.MpsToKnots(nextwp:GetVelocity())
if vdownwind<1 then
vdownwind=10
end
self:CarrierDetour(Ctiw,speedknots,uturn,vdownwind,Csoo)
self.turnintowind=true
return self
end
function AIRBOSS:_GetNextWaypoint()
local Nextwp=nil
if self.currentwp==#self.waypoints then
Nextwp=1
else
Nextwp=self.currentwp+1
end
local text=string.format("Current WP=%d/%d, next WP=%d",self.currentwp,#self.waypoints,Nextwp)
self:T2(self.lid..text)
local nextwp=self.waypoints[Nextwp]
return nextwp,Nextwp
end
function AIRBOSS:_InitWaypoints()
local Waypoints=self.carrier:GetGroup():GetTemplateRoutePoints()
self.waypoints={}
for i,point in ipairs(Waypoints)do
local coord=COORDINATE:New(point.x,point.alt,point.y)
coord:SetVelocity(point.speed)
table.insert(self.waypoints,coord)
if self.Debug then
coord:MarkToAll(string.format("Carrier Waypoint %d, Speed=%.1f knots",i,UTILS.MpsToKnots(point.speed)))
end
end
return self
end
function AIRBOSS:_PatrolRoute(n)
local nextWP,N=self:_GetNextWaypoint()
n=n or N
local CarrierGroup=self.carrier:GetGroup()
local Waypoints={}
local wp=self:GetCoordinate():WaypointGround(CarrierGroup:GetVelocityKMH())
table.insert(Waypoints,wp)
for i=n,#self.waypoints do
local coord=self.waypoints[i]
local wp=coord:WaypointGround(UTILS.MpsToKmph(coord.Velocity))
local TaskPassingWP=CarrierGroup:TaskFunction("AIRBOSS._PassingWaypoint",self,i,#self.waypoints)
CarrierGroup:SetTaskWaypoint(wp,TaskPassingWP)
table.insert(Waypoints,wp)
end
CarrierGroup:Route(Waypoints)
return self
end
function AIRBOSS:_GetETAatNextWP()
local cwp=self.currentwp
local tnow=timer.getAbsTime()
local p=self:GetCoordinate()
local v=self.carrier:GetVelocityMPS()
local nextWP=self:_GetNextWaypoint()
local s=p:Get2DDistance(nextWP)
local t=s/v
local eta=t+tnow
return eta
end
function AIRBOSS:_CheckCarrierTurning()
local vNew=self.carrier:GetOrientationX()
local vLast=self.Corientlast
vNew.y=0;
vLast.y=0
local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast)))
self.Corientlast=vNew
local turning=math.abs(deltaLast)>=1
if self.turning and not turning then
local FB=self:GetFinalBearing(true)
self:_MarshalCallNewFinalBearing(FB)
end
if turning and not self.turning then
local hdg
if self.turnintowind then
hdg=self:GetHeadingIntoWind(false)
else
hdg=self:GetCoordinate():HeadingTo(self:_GetNextWaypoint())
end
hdg=hdg-self.magvar
if hdg<0 then
hdg=360+hdg
end
self:_MarshalCallCarrierTurnTo(hdg)
end
self.turning=turning
end
function AIRBOSS:_CheckPatternUpdate()
local dTPupdate=10*60
local Dupdate=UTILS.NMToMeters(2.5)
local Hupdate=5
local dt=timer.getTime()-self.Tpupdate
if dt<dTPupdate or self.turning then
return
end
local vNew=self.carrier:GetOrientationX()
local vOld=self.Corientation
vNew.y=0;
vOld.y=0
local deltaHeading=math.deg(math.acos(UTILS.VecDot(vNew,vOld)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vOld)))
local Hchange=false
if math.abs(deltaHeading)>=Hupdate then
self:T(self.lid..string.format("Carrier heading changed by %d°.",deltaHeading))
Hchange=true
end
local pos=self:GetCoordinate()
local dist=pos:Get2DDistance(self.Cposition)
local Dchange=false
if dist>=Dupdate then
self:T(self.lid..string.format("Carrier position changed by %.1f NM.",UTILS.MetersToNM(dist)))
Dchange=true
end
if Hchange or Dchange then
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
if flight.ai then
self:_MarshalAI(flight,flight.flag)
end
end
self.Corientation=vNew
self.Cposition=pos
self.Tpupdate=timer.getTime()
end
end
function AIRBOSS._PassingWaypoint(group,airboss,i,final)
local text=string.format("Group %s passing waypoint %d of %d.",group:GetName(),i,final)
if airboss.Debug and false then
local pos=group:GetCoordinate()
pos:SmokeRed()
local MarkerID=pos:MarkToAll(string.format("Group %s reached waypoint %d",group:GetName(),i))
end
MESSAGE:New(text,10):ToAllIf(airboss.Debug)
airboss:T(airboss.lid..text)
airboss.currentwp=i
airboss:PassingWaypoint(i)
if i==final and final>1 and airboss.adinfinitum then
airboss:_PatrolRoute()
end
end
function AIRBOSS._ResumeRoute(group,airboss,gotocoord)
local nextwp,Nextwp=airboss:_GetNextWaypoint()
local speedkmh=nextwp.Velocity*3.6
if speedkmh<1 then
speedkmh=UTILS.KnotsToKmph(10)
end
local waypoints={}
local c0=group:GetCoordinate()
local wp0=c0:WaypointGround(speedkmh)
table.insert(waypoints,wp0)
if gotocoord then
local headingto=c0:HeadingTo(gotocoord)
local hdg1=airboss:GetHeading()
local hdg2=c0:HeadingTo(gotocoord)
local delta=airboss:_GetDeltaHeading(hdg1,hdg2)
if delta>90 then
local turnradius=UTILS.NMToMeters(3)
local gotocoordh=c0:Translate(turnradius,hdg1+45)
local wp=gotocoordh:WaypointGround(speedkmh)
table.insert(waypoints,wp)
gotocoordh=c0:Translate(turnradius,hdg1+90)
wp=gotocoordh:WaypointGround(speedkmh)
table.insert(waypoints,wp)
end
local wp1=gotocoord:WaypointGround(speedkmh)
table.insert(waypoints,wp1)
end
local text=string.format("Carrier is resuming route. Next waypoint %d, Speed=%.1f knots.",Nextwp,UTILS.KmphToKnots(speedkmh))
MESSAGE:New(text,10):ToAllIf(airboss.Debug)
airboss:I(airboss.lid..text)
for i=Nextwp,#airboss.waypoints do
local coord=airboss.waypoints[i]
local speed=coord.Velocity*3.6
if speed<1 then
speed=UTILS.KnotsToKmph(10)
end
local wp=coord:WaypointGround(speed)
local TaskPassingWP=group:TaskFunction("AIRBOSS._PassingWaypoint",airboss,i,#airboss.waypoints)
group:SetTaskWaypoint(wp,TaskPassingWP)
table.insert(waypoints,wp)
end
airboss.turnintowind=false
airboss.detour=false
group:Route(waypoints)
end
function AIRBOSS._ReachedHoldingZone(group,airboss,flight)
local text=string.format("Flight %s reached holding zone.",group:GetName())
MESSAGE:New(text,10):ToAllIf(airboss.Debug)
airboss:T(airboss.lid..text)
if airboss.Debug then
group:GetCoordinate():MarkToAll(text)
end
if flight then
flight.holding=true
flight.time=timer.getAbsTime()
end
end
function AIRBOSS._TaskFunctionMarshalAI(group,airboss,flight)
local text=string.format("Flight %s is send to marshal.",group:GetName())
MESSAGE:New(text,10):ToAllIf(airboss.Debug)
airboss:T(airboss.lid..text)
local stack=airboss:_GetFreeStack(flight.ai)
if stack then
airboss:_MarshalAI(flight,stack)
else
if not airboss:_InQueue(airboss.Qwaiting,flight.group)then
airboss:_WaitAI(flight)
end
end
if flight.refueling==true then
airboss:I(airboss.lid..string.format("Flight group %s finished refueling task.",flight.groupname))
end
flight.refueling=false
end
function AIRBOSS:_GetACNickname(actype)
local nickname="unknown"
if actype==AIRBOSS.AircraftCarrier.A4EC then
nickname="Skyhawk"
elseif actype==AIRBOSS.AircraftCarrier.T45C then
nickname="Goshawk"
elseif actype==AIRBOSS.AircraftCarrier.AV8B then
nickname="Harrier"
elseif actype==AIRBOSS.AircraftCarrier.E2D then
nickname="Hawkeye"
elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B then
nickname="Tomcat"
elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then
nickname="Hornet"
elseif actype==AIRBOSS.AircraftCarrier.RHINOE or actype==AIRBOSS.AircraftCarrier.RHINOF then
nickname="Rhino"
elseif actype==AIRBOSS.AircraftCarrier.GROWLER then
nickname="Growler"
elseif actype==AIRBOSS.AircraftCarrier.S3B or actype==AIRBOSS.AircraftCarrier.S3BTANKER then
nickname="Viking"
end
return nickname
end
function AIRBOSS:_GetOnboardNumberPlayer(group)
return self:_GetOnboardNumbers(group,true)
end
function AIRBOSS:_GetOnboardNumbers(group,playeronly)
local groupname=group:GetName()
local text=string.format("Onboard numbers of group %s:",groupname)
local units=group:GetTemplate().units
local numbers={}
for _,unit in pairs(units)do
local n=tostring(unit.onboard_num)
local name=unit.name
local skill=unit.skill or"Unknown"
text=text..string.format("\n- unit %s: onboard #=%s  skill=%s",name,n,tostring(skill))
if playeronly and skill=="Client"or skill=="Player"then
return n
end
numbers[name]=n
end
self:T2(self.lid..text)
return numbers
end
function AIRBOSS:_GetTowerFrequency()
self.TowerFreq=0
local striketemplate=self.carrier:GetGroup():GetTemplate()
for _,unit in pairs(striketemplate.units)do
if self.carrier:GetName()==unit.name then
self.TowerFreq=unit.frequency/1000000
return
end
end
end
function AIRBOSS:_GetGoodBadScore(playerData)
local lowscore
local badscore
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
lowscore=10
badscore=20
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
lowscore=5
badscore=10
elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then
lowscore=2.5
badscore=5
end
return lowscore,badscore
end
function AIRBOSS:_IsCarrierAircraft(unit)
local aircrafttype=unit:GetTypeName()
if aircrafttype==AIRBOSS.AircraftCarrier.AV8B then
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
return true
else
return false
end
end
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
if aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then
return false
end
end
for _,actype in pairs(AIRBOSS.AircraftCarrier)do
if actype==aircrafttype then
return true
end
end
return false
end
function AIRBOSS:_IsHumanUnit(unit)
local playerunit=self:_GetPlayerUnitAndName(unit:GetName())
if playerunit then
return true
else
return false
end
end
function AIRBOSS:_IsHuman(group)
local units=group:GetUnits()
for _,_unit in pairs(units)do
local human=self:_IsHumanUnit(_unit)
if human then
return true
end
end
return false
end
function AIRBOSS:_GetFuelState(unit)
local fuel=unit:GetFuel()
local maxfuel=self:_GetUnitMasses(unit)
local fuelstate=fuel*maxfuel
self:T2(self.lid..string.format("Unit %s fuel state = %.1f kg = %.1f lbs",unit:GetName(),fuelstate,UTILS.kg2lbs(fuelstate)))
return UTILS.kg2lbs(fuelstate)
end
function AIRBOSS:_GetAngels(alt)
if alt then
local angels=UTILS.Round(UTILS.MetersToFeet(alt)/1000,0)
return angels
else
return 0
end
end
function AIRBOSS:_GetUnitMasses(unit)
local Desc=unit:GetDesc()
local massfuel=Desc.fuelMassMax or 0
local massempty=Desc.massEmpty or 0
local massmax=Desc.massMax or 0
local masscargo=massmax-massfuel-massempty
self:T2(self.lid..string.format("Unit %s mass fuel=%.1f kg, empty=%.1f kg, max=%.1f kg, cargo=%.1f kg",unit:GetName(),massfuel,massempty,massmax,masscargo))
return massfuel,massempty,massmax,masscargo
end
function AIRBOSS:_GetPlayerDataUnit(unit)
if unit:IsAlive()then
local unitname=unit:GetName()
local playerunit,playername=self:_GetPlayerUnitAndName(unitname)
if playerunit and playername then
return self.players[playername]
end
end
return nil
end
function AIRBOSS:_GetPlayerDataGroup(group)
local units=group:GetUnits()
for _,unit in pairs(units)do
local playerdata=self:_GetPlayerDataUnit(unit)
if playerdata then
return playerdata
end
end
return nil
end
function AIRBOSS:_GetPlayerUnit(_unitName)
for _,_player in pairs(self.players)do
local player=_player
if player.unit and player.unit:GetName()==_unitName then
self:T(self.lid..string.format("Found player=%s unit=%s in players table.",tostring(player.name),tostring(_unitName)))
return player.unit,player.name
end
end
return nil,nil
end
function AIRBOSS:_GetPlayerUnitAndName(_unitName)
self:F2(_unitName)
if _unitName~=nil then
local u,pn=self:_GetPlayerUnit(_unitName)
if u and pn then
return u,pn
end
local DCSunit=Unit.getByName(_unitName)
if DCSunit then
local playername=DCSunit:getPlayerName()
local unit=UNIT:Find(DCSunit)
self:T2({DCSunit=DCSunit,unit=unit,playername=playername})
if DCSunit and unit and playername then
self:T(self.lid..string.format("Found DCS unit %s with player %s.",tostring(_unitName),tostring(playername)))
return unit,playername
end
end
end
return nil,nil
end
function AIRBOSS:GetCoalition()
return self.carrier:GetCoalition()
end
function AIRBOSS:GetCoordinate()
return self.carrier:GetCoord()
end
function AIRBOSS:GetCoord()
return self.carrier:GetCoord()
end
function AIRBOSS:_GetStaticWeather()
local weather=env.mission.weather
local clouds=weather.clouds
local visibility=weather.visibility.distance
local dust=nil
if weather.enable_dust==true then
dust=weather.dust_density
end
local fog=nil
if weather.enable_fog==true then
fog=weather.fog
end
return clouds,visibility,fog,dust
end
function AIRBOSS._CheckRadioQueueT(param,time)
AIRBOSS._CheckRadioQueue(param.airboss,param.radioqueue,param.name)
return time+0.05
end
function AIRBOSS:_CheckRadioQueue(radioqueue,name)
if#radioqueue==0 then
if name=="LSO"then
self:T(self.lid..string.format("Stopping LSO radio queue."))
self.radiotimer:Stop(self.RQLid)
self.RQLid=nil
elseif name=="MARSHAL"then
self:T(self.lid..string.format("Stopping Marshal radio queue."))
self.radiotimer:Stop(self.RQMid)
self.RQMid=nil
end
return
end
local _time=timer.getAbsTime()
local playing=false
local next=nil
local _remove=nil
for i,_transmission in ipairs(radioqueue)do
local transmission=_transmission
if _time>=transmission.Tplay then
if transmission.isplaying then
if _time>=transmission.Tstarted+transmission.call.duration then
transmission.isplaying=false
_remove=i
if transmission.radio.alias=="LSO"then
self.TQLSO=_time
elseif transmission.radio.alias=="MARSHAL"then
self.TQMarshal=_time
end
else
playing=true
end
else
local Tlast=nil
if transmission.interval then
if transmission.radio.alias=="LSO"then
Tlast=self.TQLSO
elseif transmission.radio.alias=="MARSHAL"then
Tlast=self.TQMarshal
end
end
if transmission.interval==nil then
if next==nil then
next=transmission
end
else
if _time-Tlast>=transmission.interval then
next=transmission
else
end
end
if next or Tlast then
break
end
end
else
end
end
if next~=nil and not playing then
self:Broadcast(next.radio,next.call,next.loud)
next.isplaying=true
next.Tstarted=_time
end
if _remove then
table.remove(radioqueue,_remove)
end
return
end
function AIRBOSS:RadioTransmission(radio,call,loud,delay,interval,click,pilotcall)
self:F2({radio=radio,call=call,loud=loud,delay=delay,interval=interval,click=click})
if radio==nil or call==nil then
return
end
local transmission={}
transmission.radio=radio
transmission.call=call
transmission.Tplay=timer.getAbsTime()+(delay or 0)
transmission.interval=interval
transmission.isplaying=false
transmission.Tstarted=nil
transmission.loud=loud and call.loud
if self:_IsOnboard(call.modexsender)then
self:_Number2Radio(radio,call.modexsender,delay,0.3,pilotcall)
end
if self:_IsOnboard(call.modexreceiver)then
self:_Number2Radio(radio,call.modexreceiver,delay,0.3,pilotcall)
end
local caller=""
if radio.alias=="LSO"then
table.insert(self.RQLSO,transmission)
caller="LSOCall"
if not self.RQLid then
self:T(self.lid..string.format("Starting LSO radio queue."))
self.RQLid=self.radiotimer:Schedule(nil,AIRBOSS._CheckRadioQueue,{self,self.RQLSO,"LSO"},0.02,0.05)
end
elseif radio.alias=="MARSHAL"then
table.insert(self.RQMarshal,transmission)
caller="MarshalCall"
if not self.RQMid then
self:T(self.lid..string.format("Starting Marhal radio queue."))
self.RQMid=self.radiotimer:Schedule(nil,AIRBOSS._CheckRadioQueue,{self,self.RQMarshal,"MARSHAL"},0.02,0.05)
end
end
if click then
self:RadioTransmission(radio,self[caller].CLICK,false,delay)
end
end
function AIRBOSS:_NeedsSubtitle(call)
if call.file==self.MarshalCall.NOISE.file or call.file==self.LSOCall.NOISE.file then
return true
else
return false
end
end
function AIRBOSS:Broadcast(radio,call,loud)
self:F(call)
if not self.usersoundradio then
local sender=self:_GetRadioSender(radio)
local filename=self:_RadioFilename(call,loud,radio.alias)
local subtitle=self:_RadioSubtitle(radio,call,loud)
self:T({filename=filename,subtitle=subtitle})
if sender then
self:T(self.lid..string.format("Broadcasting from aircraft %s",sender:GetName()))
local commandFrequency={
id="SetFrequency",
params={
frequency=radio.frequency*1000000,
modulation=radio.modulation,
},
}
local commandTransmit={
id="TransmitMessage",
params={
file=filename,
duration=call.subduration or 5,
subtitle=subtitle,
loop=false,
},
}
sender:SetCommand(commandFrequency)
sender:SetCommand(commandTransmit)
else
self:T(self.lid..string.format("Broadcasting from carrier via trigger.action.radioTransmission()."))
local vec3=self.carrier:GetPositionVec3()
trigger.action.radioTransmission(filename,vec3,radio.modulation,false,radio.frequency*1000000,100)
for _,_player in pairs(self.players)do
local playerData=_player
if playerData.unit:IsInZone(self.zoneCCA)and playerData.actype~=AIRBOSS.AircraftCarrier.A4EC then
if playerData.subtitles or self:_NeedsSubtitle(call)then
if radio.alias=="MARSHAL"or(radio.alias=="LSO"and self:_InQueue(self.Qpattern,playerData.group))then
self:MessageToPlayer(playerData,subtitle,nil,"",call.subduration or 5)
end
end
end
end
end
end
for _,_player in pairs(self.players)do
local playerData=_player
if self.usersoundradio or playerData.actype==AIRBOSS.AircraftCarrier.A4EC then
if radio.alias=="MARSHAL"or(radio.alias=="LSO"and self:_InQueue(self.Qpattern,playerData.group))then
self:Sound2Player(playerData,radio,call,loud)
end
end
end
end
function AIRBOSS:Sound2Player(playerData,radio,call,loud,delay)
if playerData.unit:IsInZone(self.zoneCCA)and call then
local filename=self:_RadioFilename(call,loud,radio.alias)
local subtitle=self:_RadioSubtitle(radio,call,loud)
USERSOUND:New(filename):ToGroup(playerData.group,delay)
if playerData.subtitles or self:_NeedsSubtitle(call)then
self:MessageToPlayer(playerData,subtitle,nil,"",call.subduration,false,delay)
end
end
end
function AIRBOSS:_RadioSubtitle(radio,call,loud)
if call==nil or call.subtitle==nil or call.subtitle==""then
return""
end
local sender=call.sender or radio.alias
if call.modexsender then
sender=call.modexsender
end
local receiver=call.modexreceiver or""
local subtitle=string.format("%s: %s",sender,call.subtitle)
if receiver and receiver~=""then
subtitle=string.format("%s: %s, %s",sender,receiver,call.subtitle)
end
local lastchar=string.sub(subtitle,-1)
if loud then
if lastchar=="."or lastchar=="!"then
subtitle=string.sub(subtitle,1,-1)
end
subtitle=subtitle.."!"
else
if lastchar=="!"then
elseif lastchar=="."then
else
subtitle=subtitle.."."
end
end
return subtitle
end
function AIRBOSS:_RadioFilename(call,loud,channel)
local prefix=call.file or""
local suffix=call.suffix or"ogg"
local path=self.soundfolder or"l10n/DEFAULT/"
if string.find(call.file,"LSO-")and channel and(channel=="LSO"or channel=="LSOCall")then
path=self.soundfolderLSO or path
end
if string.find(call.file,"MARSHAL-")and channel and(channel=="MARSHAL"or channel=="MarshalCall")then
path=self.soundfolderMSH or path
end
if loud then
prefix=prefix.."_Loud"
end
local filename=string.format("%s%s.%s",path,prefix,suffix)
return filename
end
function AIRBOSS:MessageToPlayer(playerData,message,sender,receiver,duration,clear,delay)
if playerData and message and message~=""then
duration=duration or self.Tmessage
local text
if receiver and receiver==""then
text=string.format("%s",message)
else
receiver=receiver or playerData.onboard
text=string.format("%s, %s",receiver,message)
end
self:T(self.lid..text)
if delay and delay>0 then
self:ScheduleOnce(delay,self.MessageToPlayer,self,playerData,message,sender,receiver,duration,clear)
else
local wait=0
if receiver==playerData.onboard then
if sender and(sender=="LSO"or sender=="MARSHAL"or sender=="AIRBOSS")then
wait=wait+self:_Number2Sound(playerData,sender,receiver)
end
end
if string.find(text:lower(),"negative")then
local filename=self:_RadioFilename(self.MarshalCall.NEGATIVE,false,"MARSHAL")
USERSOUND:New(filename):ToGroup(playerData.group,wait)
wait=wait+self.MarshalCall.NEGATIVE.duration
end
if string.find(text:lower(),"affirm")then
local filename=self:_RadioFilename(self.MarshalCall.AFFIRMATIVE,false,"MARSHAL")
USERSOUND:New(filename):ToGroup(playerData.group,wait)
wait=wait+self.MarshalCall.AFFIRMATIVE.duration
end
if string.find(text:lower(),"roger")then
local filename=self:_RadioFilename(self.MarshalCall.ROGER,false,"MARSHAL")
USERSOUND:New(filename):ToGroup(playerData.group,wait)
wait=wait+self.MarshalCall.ROGER.duration
end
if wait>0 then
local filename=self:_RadioFilename(self.MarshalCall.CLICK)
USERSOUND:New(filename):ToGroup(playerData.group,wait)
end
if playerData.client then
MESSAGE:New(text,duration,sender,clear):ToClient(playerData.client)
end
end
end
end
function AIRBOSS:MessageToPattern(message,sender,receiver,duration,clear,delay)
local call=self:_NewRadioCall(self.LSOCall.NOISE,sender or"LSO",message,duration,receiver,sender)
self:RadioTransmission(self.LSORadio,call,false,delay,nil,true)
end
function AIRBOSS:MessageToMarshal(message,sender,receiver,duration,clear,delay)
local call=self:_NewRadioCall(self.MarshalCall.NOISE,sender or"MARSHAL",message,duration,receiver,sender)
self:RadioTransmission(self.MarshalRadio,call,false,delay,nil,true)
end
function AIRBOSS:_NewRadioCall(call,sender,subtitle,subduration,modexreceiver,modexsender)
local newcall=UTILS.DeepCopy(call)
newcall.sender=sender
newcall.subtitle=subtitle or call.subtitle
newcall.subduration=subduration or self.Tmessage
if self:_IsOnboard(modexreceiver)then
newcall.modexreceiver=modexreceiver
end
if self:_IsOnboard(modexsender)then
newcall.modexsender=modexsender
end
return newcall
end
function AIRBOSS:_GetRadioSender(radio)
local sender=nil
if self.senderac then
sender=UNIT:FindByName(self.senderac)
end
if radio.alias=="MARSHAL"then
if self.radiorelayMSH then
sender=UNIT:FindByName(self.radiorelayMSH)
end
end
if radio.alias=="LSO"then
if self.radiorelayLSO then
sender=UNIT:FindByName(self.radiorelayLSO)
end
end
if sender and sender:IsAlive()and sender:IsAir()then
return sender
end
return nil
end
function AIRBOSS:_IsOnboard(text)
if text==nil then
return false
end
if text=="99"then
return true
end
for _,_flight in pairs(self.flights)do
local flight=_flight
for _,onboard in pairs(flight.onboardnumbers)do
if text==onboard then
return true
end
end
end
return false
end
function AIRBOSS:_Number2Sound(playerData,sender,number,delay)
delay=delay or 0
local function _split(str)
local chars={}
for i=1,#str do
local c=str:sub(i,i)
table.insert(chars,c)
end
return chars
end
local Sender
if sender=="LSO"then
Sender="LSOCall"
elseif sender=="MARSHAL"or sender=="AIRBOSS"then
Sender="MarshalCall"
else
self:E(self.lid..string.format("ERROR: Unknown radio sender %s!",tostring(sender)))
return
end
local numbers=_split(number)
local wait=0
for i=1,#numbers do
local n=numbers[i]
local N=string.format("N%s",n)
local call=self[Sender][N]
local filename=self:_RadioFilename(call,false,Sender)
USERSOUND:New(filename):ToGroup(playerData.group,delay+wait)
wait=wait+call.duration
end
return wait
end
function AIRBOSS:_Number2Radio(radio,number,delay,interval,pilotcall)
local function _split(str)
local chars={}
for i=1,#str do
local c=str:sub(i,i)
table.insert(chars,c)
end
return chars
end
local Sender=""
if radio.alias=="LSO"then
Sender="LSOCall"
elseif radio.alias=="MARSHAL"then
Sender="MarshalCall"
else
self:E(self.lid..string.format("ERROR: Unknown radio alias %s!",tostring(radio.alias)))
end
if pilotcall then
Sender="PilotCall"
end
local numbers=_split(number)
local wait=0
for i=1,#numbers do
local n=numbers[i]
local N=string.format("N%s",n)
local call=self[Sender][N]
if interval and i==1 then
self:RadioTransmission(radio,call,false,delay,interval)
else
self:RadioTransmission(radio,call,false,delay)
end
wait=wait+call.duration
end
return wait
end
function AIRBOSS:_MarshallInboundCall(unit,modex)
local vectorCarrier=self:GetCoordinate():GetDirectionVec3(unit:GetCoordinate())
local bearing=UTILS.Round(unit:GetCoordinate():GetAngleDegrees(vectorCarrier),0)
local distance=UTILS.Round(UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())),0)
local angels=UTILS.Round(UTILS.MetersToFeet(unit:GetHeight()/1000),0)
local state=UTILS.Round(self:_GetFuelState(unit)/1000,1)
local text=string.format("Marshal, %s, marking mom's %d for %d, angels %d, state %.1f",modex,bearing,distance,angels,state)
self:T(self.lid..text)
local FS=UTILS.Split(string.format("%.1f",state),".")
local inboundcall=self:_NewRadioCall(self.MarshalCall.CLICK,unit.UnitName:upper(),text,self.Tmessage,nil,unit.UnitName:upper())
self:RadioTransmission(self.MarshalRadio,inboundcall)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.MARSHAL,nil,nil,nil,nil,true)
self:_Number2Radio(self.MarshalRadio,modex,nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.MARKINGMOMS,nil,nil,nil,nil,true)
self:_Number2Radio(self.MarshalRadio,tostring(bearing),nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.FOR,nil,nil,nil,nil,true)
self:_Number2Radio(self.MarshalRadio,tostring(distance),nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.ANGELS,nil,nil,nil,nil,true)
self:_Number2Radio(self.MarshalRadio,tostring(angels),nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.STATE,nil,nil,nil,nil,true)
self:_Number2Radio(self.MarshalRadio,FS[1],nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.POINT,nil,nil,nil,nil,true)
self:_Number2Radio(self.MarshalRadio,FS[2],nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.MarshalRadio.CLICK,nil,nil,nil,nil,true)
end
function AIRBOSS:_CommencingCall(unit,modex)
local text=string.format("%s, commencing",modex)
self:T(self.lid..text)
local commencingCall=self:_NewRadioCall(self.MarshalCall.CLICK,unit.UnitName:upper(),text,self.Tmessage,nil,unit.UnitName:upper())
self:RadioTransmission(self.MarshalRadio,commencingCall)
self:_Number2Radio(self.MarshalRadio,modex,nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.COMMENCING,nil,nil,nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.MarshalRadio.CLICK,nil,nil,nil,nil,true)
end
function AIRBOSS:_LSOCallAircraftBall(modex,nickname,fuelstate)
local text=string.format("%s Ball, %.1f.",nickname,fuelstate)
self:I(self.lid..text)
local NICKNAME=nickname:upper()
local FS=UTILS.Split(string.format("%.1f",fuelstate),".")
local call=self:_NewRadioCall(self.PilotCall[NICKNAME],modex,text,self.Tmessage,nil,modex)
self:RadioTransmission(self.LSORadio,call,nil,nil,nil,nil,true)
self:RadioTransmission(self.LSORadio,self.PilotCall.BALL,nil,nil,nil,nil,true)
self:_Number2Radio(self.LSORadio,FS[1],nil,nil,true)
self:RadioTransmission(self.LSORadio,self.PilotCall.POINT,nil,nil,nil,nil,true)
self:_Number2Radio(self.LSORadio,FS[2],nil,nil,true)
self:RadioTransmission(self.LSORadio,self.LSOCall.CLICK)
end
function AIRBOSS:_MarshalCallGasAtTanker(modex)
local text=string.format("Bingo fuel! Going for gas at the recovery tanker.")
self:I(self.lid..text)
local call=self:_NewRadioCall(self.PilotCall.BINGOFUEL,modex,text,self.Tmessage,nil,modex)
self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.GASATTANKER,nil,nil,nil,true,true)
end
function AIRBOSS:_MarshalCallGasAtDivert(modex,divertname)
local text=string.format("Bingo fuel! Going for gas at divert field %s.",divertname)
self:I(self.lid..text)
local call=self:_NewRadioCall(self.PilotCall.BINGOFUEL,modex,text,self.Tmessage,nil,modex)
self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.GASATDIVERT,nil,nil,nil,true,true)
end
function AIRBOSS:_MarshalCallRecoveryStopped(case)
local text=string.format("Case %d recovery ops are stopped. Deck is closed.",case)
self:I(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.CASE,"AIRBOSS",text,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call)
self:_Number2Radio(self.MarshalRadio,tostring(case))
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RECOVERYOPSSTOPPED,nil,nil,0.2)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DECKCLOSED,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallRecoveryPausedUntilFurtherNotice()
local call=self:_NewRadioCall(self.MarshalCall.RECOVERYPAUSEDNOTICE,"AIRBOSS",nil,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallRecoveryPausedResumedAt(clock)
local _clock=UTILS.Split(clock,"+")
local CT=UTILS.Split(_clock[1],":")
local text=string.format("aircraft recovery is paused and will be resumed at %s.",clock)
self:I(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.RECOVERYPAUSEDRESUMED,"AIRBOSS",text,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call)
self:_Number2Radio(self.MarshalRadio,CT[1])
self:_Number2Radio(self.MarshalRadio,CT[2])
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.HOURS,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallClearedForRecovery(modex,case)
local text=string.format("you're cleared for Case %d recovery.",case)
self:I(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.CLEAREDFORRECOVERY,"MARSHAL",text,self.Tmessage,modex)
local delay=2
self:RadioTransmission(self.MarshalRadio,call,nil,delay)
self:_Number2Radio(self.MarshalRadio,tostring(case),delay)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RECOVERY,nil,delay,nil,true)
end
function AIRBOSS:_MarshalCallResumeRecovery()
local call=self:_NewRadioCall(self.MarshalCall.RESUMERECOVERY,"AIRBOSS",nil,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallNewFinalBearing(FB)
local text=string.format("new final bearing %03d°.",FB)
self:I(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.NEWFB,"AIRBOSS",text,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call)
self:_Number2Radio(self.MarshalRadio,string.format("%03d",FB),nil,0.2)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallCarrierTurnTo(hdg)
local text=string.format("carrier is now starting turn to heading %03d°.",hdg)
self:I(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.CARRIERTURNTOHEADING,"AIRBOSS",text,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call)
self:_Number2Radio(self.MarshalRadio,string.format("%03d",hdg),nil,0.2)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallStackFull(modex,nwaiting)
local text=string.format("Marshal stack is currently full. Hold outside 10 NM zone and wait for further instructions. ")
if nwaiting==1 then
text=text..string.format("There is one flight ahead of you.")
elseif nwaiting>1 then
text=text..string.format("There are %d flights ahead of you.",nwaiting)
else
text=text..string.format("You are next in line.")
end
self:I(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.STACKFULL,"AIRBOSS",text,self.Tmessage,modex)
self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallRecoveryStart(case)
local radial=self:GetRadial(case,true,true,false)
local text=string.format("Starting aircraft recovery Case %d ops.",case)
if case==1 then
text=text..string.format(" BRC %03d°.",self:GetBRC())
elseif case==2 then
text=text..string.format(" Marshal radial %03d°. BRC %03d°.",radial,self:GetBRC())
elseif case==3 then
text=text..string.format(" Marshal radial %03d°. Final heading %03d°.",radial,self:GetFinalBearing(false))
end
self:T(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.STARTINGRECOVERY,"AIRBOSS",text,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call)
self:_Number2Radio(self.MarshalRadio,tostring(case),nil,0.1)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.OPS)
if case>1 then
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.MARSHALRADIAL)
self:_Number2Radio(self.MarshalRadio,string.format("%03d",radial),nil,0.2)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES,nil,nil,nil,true)
end
end
function AIRBOSS:_MarshalCallArrived(modex,case,brc,altitude,charlie,qfe)
self:F({modex=modex,case=case,brc=brc,altitude=altitude,charlie=charlie,qfe=qfe})
local angels=self:_GetAngels(altitude)
local QFE=UTILS.Split(string.format("%.2f",qfe),".")
local clock=UTILS.Split(charlie,"+")
local CT=UTILS.Split(clock[1],":")
local text=string.format("Case %d, expected BRC %03d°, hold at angels %d. Expected Charlie Time %s. Altimeter %.2f. Report see me.",case,brc,angels,charlie,qfe)
self:I(self.lid..text)
local casecall=self:_NewRadioCall(self.MarshalCall.CASE,"MARSHAL",text,self.Tmessage,modex)
self:RadioTransmission(self.MarshalRadio,casecall)
self:_Number2Radio(self.MarshalRadio,tostring(case))
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.EXPECTED,nil,nil,0.5)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.BRC)
self:_Number2Radio(self.MarshalRadio,string.format("%03d",brc))
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.HOLDATANGELS,nil,nil,0.5)
self:_Number2Radio(self.MarshalRadio,tostring(angels))
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.EXPECTED,nil,nil,0.5)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.CHARLIETIME)
self:_Number2Radio(self.MarshalRadio,CT[1])
self:_Number2Radio(self.MarshalRadio,CT[2])
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.HOURS)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.ALTIMETER,nil,nil,0.5)
self:_Number2Radio(self.MarshalRadio,QFE[1])
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.POINT)
self:_Number2Radio(self.MarshalRadio,QFE[2])
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.REPORTSEEME,nil,nil,0.5,true)
end
function AIRBOSS:_AddF10Commands(_unitName)
self:F(_unitName)
local _unit,playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and playername then
local group=_unit:GetGroup()
local gid=group:GetID()
if group and gid then
if not self.menuadded[gid]then
self.menuadded[gid]=true
local _rootPath=nil
if AIRBOSS.MenuF10Root then
if self.menusingle then
_rootPath=AIRBOSS.MenuF10Root
else
_rootPath=missionCommands.addSubMenuForGroup(gid,self.alias,AIRBOSS.MenuF10Root)
end
else
if AIRBOSS.MenuF10[gid]==nil then
AIRBOSS.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid,"Airboss")
end
if self.menusingle then
_rootPath=AIRBOSS.MenuF10[gid]
else
_rootPath=missionCommands.addSubMenuForGroup(gid,self.alias,AIRBOSS.MenuF10[gid])
end
end
local _helpPath=missionCommands.addSubMenuForGroup(gid,"Help",_rootPath)
if self.menumarkzones then
local _markPath=missionCommands.addSubMenuForGroup(gid,"Mark Zones",_helpPath)
if self.menusmokezones then
missionCommands.addCommandForGroup(gid,"Smoke Pattern Zones",_markPath,self._MarkCaseZones,self,_unitName,false)
end
missionCommands.addCommandForGroup(gid,"Flare Pattern Zones",_markPath,self._MarkCaseZones,self,_unitName,true)
if self.menusmokezones then
missionCommands.addCommandForGroup(gid,"Smoke Marshal Zone",_markPath,self._MarkMarshalZone,self,_unitName,false)
end
missionCommands.addCommandForGroup(gid,"Flare Marshal Zone",_markPath,self._MarkMarshalZone,self,_unitName,true)
end
local _skillPath=missionCommands.addSubMenuForGroup(gid,"Skill Level",_helpPath)
missionCommands.addCommandForGroup(gid,"Flight Student",_skillPath,self._SetDifficulty,self,_unitName,AIRBOSS.Difficulty.EASY)
missionCommands.addCommandForGroup(gid,"Naval Aviator",_skillPath,self._SetDifficulty,self,_unitName,AIRBOSS.Difficulty.NORMAL)
missionCommands.addCommandForGroup(gid,"TOPGUN Graduate",_skillPath,self._SetDifficulty,self,_unitName,AIRBOSS.Difficulty.HARD)
missionCommands.addCommandForGroup(gid,"Hints On/Off",_skillPath,self._SetHintsOnOff,self,_unitName)
missionCommands.addCommandForGroup(gid,"My Status",_helpPath,self._DisplayPlayerStatus,self,_unitName)
missionCommands.addCommandForGroup(gid,"Attitude Monitor",_helpPath,self._DisplayAttitude,self,_unitName)
missionCommands.addCommandForGroup(gid,"Radio Check LSO",_helpPath,self._LSORadioCheck,self,_unitName)
missionCommands.addCommandForGroup(gid,"Radio Check Marshal",_helpPath,self._MarshalRadioCheck,self,_unitName)
missionCommands.addCommandForGroup(gid,"Subtitles On/Off",_helpPath,self._SubtitlesOnOff,self,_unitName)
missionCommands.addCommandForGroup(gid,"Trapsheet On/Off",_helpPath,self._TrapsheetOnOff,self,_unitName)
local _kneeboardPath=missionCommands.addSubMenuForGroup(gid,"Kneeboard",_rootPath)
local _resultsPath=missionCommands.addSubMenuForGroup(gid,"Results",_kneeboardPath)
missionCommands.addCommandForGroup(gid,"Greenie Board",_resultsPath,self._DisplayScoreBoard,self,_unitName)
missionCommands.addCommandForGroup(gid,"My LSO Grades",_resultsPath,self._DisplayPlayerGrades,self,_unitName)
missionCommands.addCommandForGroup(gid,"Last Debrief",_resultsPath,self._DisplayDebriefing,self,_unitName)
if self.skipperMenu then
local _skipperPath=missionCommands.addSubMenuForGroup(gid,"Skipper",_kneeboardPath)
local _menusetspeed=missionCommands.addSubMenuForGroup(gid,"Set Speed",_skipperPath)
missionCommands.addCommandForGroup(gid,"10 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,10)
missionCommands.addCommandForGroup(gid,"15 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,15)
missionCommands.addCommandForGroup(gid,"20 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,20)
missionCommands.addCommandForGroup(gid,"25 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,25)
missionCommands.addCommandForGroup(gid,"30 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,30)
local _menusetrtime=missionCommands.addSubMenuForGroup(gid,"Set Time",_skipperPath)
missionCommands.addCommandForGroup(gid,"15 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,15)
missionCommands.addCommandForGroup(gid,"30 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,30)
missionCommands.addCommandForGroup(gid,"45 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,45)
missionCommands.addCommandForGroup(gid,"60 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,60)
missionCommands.addCommandForGroup(gid,"90 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,90)
local _menusetrtime=missionCommands.addSubMenuForGroup(gid,"Set Marshal Radial",_skipperPath)
missionCommands.addCommandForGroup(gid,"+30°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,30)
missionCommands.addCommandForGroup(gid,"+15°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,15)
missionCommands.addCommandForGroup(gid,"0°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,0)
missionCommands.addCommandForGroup(gid,"-15°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,-15)
missionCommands.addCommandForGroup(gid,"-30°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,-30)
missionCommands.addCommandForGroup(gid,"U-turn On/Off",_skipperPath,self._SkipperRecoveryUturn,self,_unitName)
missionCommands.addCommandForGroup(gid,"Start CASE I",_skipperPath,self._SkipperStartRecovery,self,_unitName,1)
missionCommands.addCommandForGroup(gid,"Start CASE II",_skipperPath,self._SkipperStartRecovery,self,_unitName,2)
missionCommands.addCommandForGroup(gid,"Start CASE III",_skipperPath,self._SkipperStartRecovery,self,_unitName,3)
missionCommands.addCommandForGroup(gid,"Stop Recovery",_skipperPath,self._SkipperStopRecovery,self,_unitName)
end
missionCommands.addCommandForGroup(gid,"Carrier Info",_kneeboardPath,self._DisplayCarrierInfo,self,_unitName)
missionCommands.addCommandForGroup(gid,"Weather Report",_kneeboardPath,self._DisplayCarrierWeather,self,_unitName)
missionCommands.addCommandForGroup(gid,"Set Section",_kneeboardPath,self._SetSection,self,_unitName)
missionCommands.addCommandForGroup(gid,"Marshal Queue",_kneeboardPath,self._DisplayQueue,self,_unitName,"Marshal")
missionCommands.addCommandForGroup(gid,"Pattern Queue",_kneeboardPath,self._DisplayQueue,self,_unitName,"Pattern")
missionCommands.addCommandForGroup(gid,"Waiting Queue",_kneeboardPath,self._DisplayQueue,self,_unitName,"Waiting")
missionCommands.addCommandForGroup(gid,"Request Marshal",_rootPath,self._RequestMarshal,self,_unitName)
missionCommands.addCommandForGroup(gid,"Request Commence",_rootPath,self._RequestCommence,self,_unitName)
missionCommands.addCommandForGroup(gid,"Request Refueling",_rootPath,self._RequestRefueling,self,_unitName)
missionCommands.addCommandForGroup(gid,"Spinning",_rootPath,self._RequestSpinning,self,_unitName)
missionCommands.addCommandForGroup(gid,"Emergency Landing",_rootPath,self._RequestEmergency,self,_unitName)
missionCommands.addCommandForGroup(gid,"[Reset My Status]",_rootPath,self._ResetPlayerStatus,self,_unitName)
end
else
self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.",_unitName))
end
else
self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.",_unitName))
end
end
function AIRBOSS:_SkipperStartRecovery(_unitName,case)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=string.format("affirm, Case %d recovery will start in 5 min for %d min. Wind on deck %d knots. U-turn=%s.",case,self.skipperTime,self.skipperSpeed,tostring(self.skipperUturn))
if case>1 then
text=text..string.format(" Marshal radial %d°.",self.skipperOffset)
end
if self:IsRecovering()then
text="negative, carrier is already recovering."
self:MessageToPlayer(playerData,text,"AIRBOSS")
return
end
self:MessageToPlayer(playerData,text,"AIRBOSS")
local t0=timer.getAbsTime()+5*60
local t9=t0+self.skipperTime*60
local C0=UTILS.SecondsToClock(t0)
local C9=UTILS.SecondsToClock(t9)
self:AddRecoveryWindow(C0,C9,case,self.skipperOffset,true,self.skipperSpeed,self.skipperUturn)
end
end
end
function AIRBOSS:_SkipperStopRecovery(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text="roger, stopping recovery right away."
if not self:IsRecovering()then
text="negative, carrier is currently not recovering."
self:MessageToPlayer(playerData,text,"AIRBOSS")
return
end
self:MessageToPlayer(playerData,text,"AIRBOSS")
self:RecoveryStop()
end
end
end
function AIRBOSS:_SkipperRecoveryOffset(_unitName,offset)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.",offset)
self:MessageToPlayer(playerData,text,"AIRBOSS")
self.skipperOffset=offset
end
end
end
function AIRBOSS:_SkipperRecoveryTime(_unitName,time)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=string.format("roger, manual recovery time set to %d min.",time)
self:MessageToPlayer(playerData,text,"AIRBOSS")
self.skipperTime=time
end
end
end
function AIRBOSS:_SkipperRecoverySpeed(_unitName,speed)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=string.format("roger, wind on deck set to %d knots.",speed)
self:MessageToPlayer(playerData,text,"AIRBOSS")
self.skipperSpeed=speed
end
end
end
function AIRBOSS:_SkipperRecoveryUturn(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
self.skipperUturn=not self.skipperUturn
local text=string.format("roger, U-turn is now %s.",tostring(self.skipperUturn))
self:MessageToPlayer(playerData,text,"AIRBOSS")
end
end
end
function AIRBOSS:_ResetPlayerStatus(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text="roger, status reset executed! You have been removed from all queues."
self:MessageToPlayer(playerData,text,"AIRBOSS")
self:_RemoveFlight(playerData)
if playerData.debriefschedulerID and self.Scheduler then
self.Scheduler:Stop(playerData.debriefschedulerID)
end
self:_InitPlayer(playerData)
end
end
end
function AIRBOSS:_RequestMarshal(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
if self.xtVoiceOvers then
self:_MarshallInboundCall(_unit,playerData.onboard)
end
local inCCA=playerData.unit:IsInZone(self.zoneCCA)
if inCCA then
if self:_InQueue(self.Qmarshal,playerData.group)then
local text=string.format("negative, you are already in the Marshal queue. New marshal request denied!")
self:MessageToPlayer(playerData,text,"MARSHAL")
elseif self:_InQueue(self.Qpattern,playerData.group)then
local text=string.format("negative, you are already in the Pattern queue. Marshal request denied!")
self:MessageToPlayer(playerData,text,"MARSHAL")
elseif self:_InQueue(self.Qwaiting,playerData.group)then
local text=string.format("negative, you are in the Waiting queue with %d flights ahead of you. Marshal request denied!",#self.Qwaiting)
self:MessageToPlayer(playerData,text,"MARSHAL")
elseif not _unit:InAir()then
local text=string.format("negative, you are not airborne. Marshal request denied!")
self:MessageToPlayer(playerData,text,"MARSHAL")
elseif playerData.name~=playerData.seclead then
local text=string.format("negative, your section lead %s needs to request Marshal.",playerData.seclead)
self:MessageToPlayer(playerData,text,"MARSHAL")
else
local freestack=self:_GetFreeStack(playerData.ai)
if freestack then
self:_MarshalPlayer(playerData,freestack)
else
self:_WaitPlayer(playerData)
end
end
else
local text=string.format("negative, you are not inside CCA. Marshal request denied!")
self:MessageToPlayer(playerData,text,"MARSHAL")
end
end
end
end
function AIRBOSS:_RequestEmergency(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=""
if not self.emergency then
text="negative, no emergency landings on my carrier. We are currently busy. See how you get along!"
elseif not _unit:InAir()then
local zone=self:_GetZoneCarrierBox()
if playerData.unit:IsInZone(zone)then
text="roger, you are now technically in the bolter pattern. Your next step after takeoff is abeam!"
local lead=self:_GetFlightLead(playerData)
self:_SetPlayerStep(lead,AIRBOSS.PatternStep.BOLTER)
for _,sec in pairs(lead.section)do
local sectionmember=sec
self:_SetPlayerStep(sectionmember,AIRBOSS.PatternStep.BOLTER)
end
self:_RemoveFlightFromQueue(self.Qwaiting,lead)
if self:_InQueue(self.Qmarshal,lead.group)then
self:_RemoveFlightFromMarshalQueue(lead)
else
if not self:_InQueue(self.Qpattern,lead.group)then
self:_AddFlightToPatternQueue(lead)
end
end
else
text=string.format("negative, you are not airborne. Request denied!")
end
else
text="affirmative, you can bypass the pattern and are cleared for final approach!"
local lead=self:_GetFlightLead(playerData)
self:_SetPlayerStep(lead,AIRBOSS.PatternStep.EMERGENCY)
for _,sec in pairs(lead.section)do
local sectionmember=sec
self:_SetPlayerStep(sectionmember,AIRBOSS.PatternStep.EMERGENCY)
self:_RemoveFlightFromQueue(self.Qspinning,sectionmember)
end
self:_RemoveFlightFromQueue(self.Qwaiting,lead)
if self:_InQueue(self.Qmarshal,lead.group)then
self:_RemoveFlightFromMarshalQueue(lead)
else
if not self:_InQueue(self.Qpattern,lead.group)then
self:_AddFlightToPatternQueue(lead)
end
end
end
self:MessageToPlayer(playerData,text,"AIRBOSS")
end
end
end
function AIRBOSS:_RequestSpinning(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=""
if not self:_InQueue(self.Qpattern,playerData.group)then
text="negative, you have to be in the pattern to spin it!"
elseif playerData.step==AIRBOSS.PatternStep.SPINNING then
text="negative, you are already spinning."
elseif not(playerData.step==AIRBOSS.PatternStep.BREAKENTRY or
playerData.step==AIRBOSS.PatternStep.EARLYBREAK or
playerData.step==AIRBOSS.PatternStep.LATEBREAK)then
text="negative, you have to be in the right step to spin it!"
else
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.SPINNING)
table.insert(self.Qspinning,playerData)
local call=self:_NewRadioCall(self.LSOCall.SPINIT,"AIRBOSS","Spin it!",self.Tmessage,playerData.onboard)
self:RadioTransmission(self.LSORadio,call,nil,nil,nil,true)
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
local text="Climb to 1200 feet and proceed to the initial again."
self:MessageToPlayer(playerData,text,"INSTRUCTOR","")
end
return
end
self:MessageToPlayer(playerData,text,"AIRBOSS")
end
end
end
function AIRBOSS:_RequestCommence(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
if self.xtVoiceOvers then
self:_CommencingCall(_unit,playerData.onboard)
end
local text=""
local cleared=false
if _unit:IsInZone(self.zoneCCA)then
local stack=playerData.flag
local _,npattern=self:_GetQueueInfo(self.Qpattern)
if self:_InQueue(self.Qpattern,playerData.group)then
text=string.format("negative, %s, you are already in the Pattern queue.",playerData.name)
elseif not _unit:InAir()then
text=string.format("negative, %s, you are not airborne.",playerData.name)
elseif playerData.seclead~=playerData.name then
text=string.format("negative, %s, your section leader %s has to request commence!",playerData.name,playerData.seclead)
elseif stack>1 then
text=string.format("negative, %s, it's not your turn yet! You are in stack no. %s.",playerData.name,stack)
elseif npattern>=self.Nmaxpattern then
text=string.format("negative ghostrider, pattern is full!\nThere are %d aircraft currently in the pattern.",npattern)
elseif self:IsRecovering()==false and not self.airbossnice then
if self.recoverywindow then
local clock=UTILS.SecondsToClock(self.recoverywindow.START)
text=string.format("negative, carrier is currently not recovery. Next window will open at %s.",clock)
else
text=string.format("negative, carrier is not recovering. No future windows planned.")
end
elseif not self:_InQueue(self.Qmarshal,playerData.group)and not self.airbossnice then
text="negative, you have to request Marshal before you can commence."
else
text=text.."roger."
if not self:IsRecovering()then
text=text.." Carrier is not recovering currently! However, you are cleared anyway as I have a nice day."
end
if not self:_InQueue(self.Qmarshal,playerData.group)then
playerData.case=self.case
if self.TACANon and playerData.difficulty~=AIRBOSS.Difficulty.HARD then
local radial=self:GetRadial(playerData.case,true,true,true)
if playerData.case==1 then
radial=self:GetBRC()
end
text=text..string.format("\nSelect TACAN %03d°, Channel %d%s (%s).\n",radial,self.TACANchannel,self.TACANmode,self.TACANmorse)
end
for _,flight in pairs(playerData.section)do
flight.case=playerData.case
end
self:_AddFlightToPatternQueue(playerData)
end
cleared=true
end
else
text=string.format("negative, %s, you are not inside the CCA!",playerData.name)
end
self:T(self.lid..text)
self:MessageToPlayer(playerData,text,"MARSHAL")
if cleared then
self:_Commencing(playerData,false)
end
end
end
end
function AIRBOSS:_RequestRefueling(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text
if self.tanker then
if _unit:IsInZone(self.zoneCCA)then
if self.tanker:IsRunning()or self.tanker:IsRefueling()then
local angels=self:_GetAngels(self.tanker.altitude)
text=string.format("affirmative, proceed to tanker at angels %d.",angels)
if self.tanker.TACANon then
text=text..string.format("\nTanker TACAN channel %d%s (%s).",self.tanker.TACANchannel,self.tanker.TACANmode,self.tanker.TACANmorse)
text=text..string.format("\nRadio frequency %.3f MHz AM.",self.tanker.RadioFreq)
end
if self.tanker:IsRefueling()then
text=text.."\nTanker is currently refueling. You might have to queue up."
end
self:_RemoveFlightFromMarshalQueue(playerData,true)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.REFUELING)
for _,sec in pairs(playerData.section)do
local sectext="follow your section leader to the tanker."
self:MessageToPlayer(sec,sectext,"MARSHAL")
self:_SetPlayerStep(sec,AIRBOSS.PatternStep.REFUELING)
end
elseif self.tanker:IsReturning()then
text="negative, tanker is RTB. Request denied!\nWait for the tanker to be back on station if you can."
end
else
text="negative, you are not inside the CCA yet."
end
else
text="negative, no refueling tanker available."
end
self:MessageToPlayer(playerData,text,"MARSHAL")
end
end
end
function AIRBOSS:_RemoveSectionMember(playerData,sectionmember)
for i,_flight in pairs(playerData.section)do
local flight=_flight
if flight.name==sectionmember.name then
table.remove(playerData.section,i)
return true
end
end
return false
end
function AIRBOSS:_SetSection(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local mycoord=_unit:GetCoordinate()
local dmax=500
local text
if self.NmaxSection==0 then
text=string.format("negative, setting sections is disabled in this mission. You stay alone.")
elseif self:_InQueue(self.Qmarshal,playerData.group)then
text=string.format("negative, you are already in the Marshal queue. Setting section not possible any more!")
elseif self:_InQueue(self.Qpattern,playerData.group)then
text=string.format("negative, you are already in the Pattern queue. Setting section not possible any more!")
else
if playerData.seclead~=playerData.name then
local lead=self.players[playerData.seclead]
if lead then
local removed=self:_RemoveSectionMember(lead,playerData)
if removed then
self:MessageToPlayer(lead,string.format("Flight %s has been removed from your section.",playerData.name),"AIRBOSS","",5)
self:MessageToPlayer(playerData,string.format("You have been removed from %s's section.",lead.name),"AIRBOSS","",5)
end
end
end
local section={}
for _,_flight in pairs(self.flights)do
local flight=_flight
if flight.ai==false and flight.groupname~=playerData.groupname and ((#flight.section==0 and flight.seclead==flight.name) or (flight.seclead==playerData.name)) then
local distance=flight.group:GetCoordinate():Get3DDistance(mycoord)
if distance<dmax then
self:T(self.lid..string.format("Found potential section member %s for lead %s at distance %.1f m.",flight.name,playerData.name,distance))
table.insert(section,{flight=flight,distance=distance})
end
end
end
table.sort(section,function(a,b)
return a.distance<b.distance
end)
playerData.seclead=playerData.name
for _,_flight in pairs(playerData.section)do
local flight=_flight
local gotit=false
for _,_new in pairs(section)do
local newflight=_new.flight
if newflight.name==flight.name then
gotit=true
end
end
if not gotit then
self:MessageToPlayer(flight,string.format("you were removed from %s's section and are on your own now.",playerData.name),"AIRBOSS","",5)
flight.seclead=flight.name
self:_RemoveSectionMember(playerData,flight)
end
end
for i,_new in pairs(section)do
local newflight=_new.flight
for _,_flight in pairs(playerData.section)do
local currentflight=_flight
if newflight.name==currentflight.name then
table.remove(section,i)
end
end
end
text=string.format("Registered flight section:")
text=text..string.format("\n- %s (lead)",playerData.seclead)
for _,_flight in pairs(playerData.section)do
local flight=_flight
text=text..string.format("\n- %s",flight.name)
end
for i=1,math.min(self.NmaxSection-#playerData.section,#section)do
local flight=section[i].flight
text=text..string.format("\n- %s",flight.name)
flight.seclead=playerData.name
flight.case=playerData.case
self:MessageToPlayer(flight,string.format("your section lead is now %s.",playerData.name),"AIRBOSS")
table.insert(playerData.section,flight)
end
if#playerData.section==0 then
text=text..string.format("\n- No other human flights found within radius of %.1f meters!",dmax)
end
end
self:MessageToPlayer(playerData,text,"MARSHAL")
end
end
end
function AIRBOSS:_DisplayScoreBoard(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local _playerResults={}
for playerName,playerGrades in pairs(self.playerscores)do
if playerGrades then
local Paverage=0
local n=0
for _,_grade in pairs(playerGrades)do
local grade=_grade
if grade.finalscore then
Paverage=Paverage+grade.finalscore
n=n+1
else
end
end
if n>0 then
_playerResults[playerName]=Paverage/n
end
end
end
local text=string.format("Greenie Board (top ten):")
local i=1
for _playerName,_points in UTILS.spairs(_playerResults,function(t,a,b)
return t[b]<t[a]
end)do
text=text..string.format("\n[%d] %s %.1f||",i,_playerName,_points)
local playerGrades=self.playerscores[_playerName]
for _,_grade in pairs(playerGrades)do
local grade=_grade
if grade.finalscore then
text=text..string.format("%.1f|",grade.points)
elseif grade.points>=0 then
text=text..string.format("(%.1f)",grade.points)
end
end
i=i+1
if i>10 then
break
end
end
if i==1 then
text=text.."\nNo results yet."
end
local playerData=self.players[_playername]
if playerData.client then
MESSAGE:New(text,30,nil,true):ToClient(playerData.client)
end
end
end
function AIRBOSS:_DisplayPlayerGrades(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=string.format("Your last 10 grades, %s:",_playername)
local playerGrades=self.playerscores[_playername]or{}
local p=0
local n=0
local m=0
for i=#playerGrades,1,-1 do
local grade=playerGrades[i]
if grade.points>=0 then
local points=grade.finalscore or grade.points
if m<10 then
text=text..string.format("\n[%d] %s %.1f PT - %s",i,grade.grade,points,grade.details)
if grade.wire and grade.wire<=4 then
text=text..string.format(" %d-wire",grade.wire)
end
if grade.Tgroove and grade.Tgroove<=360 then
text=text..string.format(" Tgroove=%.1f s",grade.Tgroove)
end
end
if grade.finalscore then
p=p+grade.finalscore
n=n+1
end
m=m+1
end
end
if n>0 then
text=text..string.format("\nAverage points = %.1f",p/n)
else
text=text..string.format("\nNo data available.")
end
if playerData.client then
MESSAGE:New(text,30,nil,true):ToClient(playerData.client)
end
end
end
end
function AIRBOSS:_DisplayDebriefing(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=string.format("Debriefing:")
if#playerData.lastdebrief>0 then
text=text..string.format("\n================================\n")
for _,_data in pairs(playerData.lastdebrief)do
local step=_data.step
local comment=_data.hint
text=text..string.format("* %s:",step)
text=text..string.format("%s\n",comment)
end
else
text=text.." Nothing to show yet."
end
self:MessageToPlayer(playerData,text,nil,"",30,true)
end
end
end
function AIRBOSS:_DisplayQueue(_unitname,qname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
local queue=nil
if qname=="Marshal"then
queue=self.Qmarshal
elseif qname=="Pattern"then
queue=self.Qpattern
elseif qname=="Waiting"then
queue=self.Qwaiting
end
local Nqueue,nqueue=self:_GetQueueInfo(queue,playerData.case)
local text=string.format("%s Queue:",qname)
if#queue==0 then
text=text.." empty"
else
local N=0
if qname=="Marshal"then
for i,_flight in pairs(queue)do
local flight=_flight
local charlie=self:_GetCharlieTime(flight)
local Charlie=UTILS.SecondsToClock(charlie)
local stack=flight.flag
local angels=self:_GetAngels(self:_GetMarshalAltitude(stack,flight.case))
local _,nunit,nsec=self:_GetFlightUnits(flight,true)
local nick=self:_GetACNickname(flight.actype)
N=N+nunit
text=text..string.format("\n[Stack %d] %s (%s*%d+%d): Case %d, Angels %d, Charlie %s",stack,flight.onboard,nick,nunit,nsec,flight.case,angels,tostring(Charlie))
end
elseif qname=="Pattern"or qname=="Waiting"then
for i,_flight in pairs(queue)do
local flight=_flight
local _,nunit,nsec=self:_GetFlightUnits(flight,true)
local nick=self:_GetACNickname(flight.actype)
local ptime=UTILS.SecondsToClock(timer.getAbsTime()-flight.time)
N=N+nunit
text=text..string.format("\n[%d] %s (%s*%d+%d): Case %d, T=%s",i,flight.onboard,nick,nunit,nsec,flight.case,ptime)
end
end
text=text..string.format("\nTotal AC: %d (airborne %d)",N,nqueue)
end
self:MessageToPlayer(playerData,text,nil,"",nil,true)
end
end
end
function AIRBOSS:_DisplayCarrierInfo(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
local coord=self:GetCoordinate()
local carrierheading=self.carrier:GetHeading()
local carrierspeed=UTILS.MpsToKnots(self.carrier:GetVelocityMPS())
local tacan="unknown"
local icls="unknown"
if self.TACANon and self.TACANchannel~=nil then
tacan=string.format("%d%s (%s)",self.TACANchannel,self.TACANmode,self.TACANmorse)
end
if self.ICLSon and self.ICLSchannel~=nil then
icls=string.format("%d (%s)",self.ICLSchannel,self.ICLSmorse)
end
local wind=UTILS.MpsToKnots(select(1,self:GetWindOnDeck()))
local Nmarshal,nmarshal=self:_GetQueueInfo(self.Qmarshal,playerData.case)
local Npattern,npattern=self:_GetQueueInfo(self.Qpattern)
local Nspinning,nspinning=self:_GetQueueInfo(self.Qspinning)
local Nwaiting,nwaiting=self:_GetQueueInfo(self.Qwaiting)
local Ntotal,ntotal=self:_GetQueueInfo(self.flights)
local Tabs=timer.getAbsTime()
local recoverytext="Recovery time windows (max 5):"
if#self.recoverytimes==0 then
recoverytext=recoverytext.." none."
else
local rw=0
for _,_recovery in pairs(self.recoverytimes)do
local recovery=_recovery
if Tabs<recovery.STOP then
recoverytext=recoverytext..string.format("\n* %s - %s: Case %d (%d°)",UTILS.SecondsToClock(recovery.START),UTILS.SecondsToClock(recovery.STOP),recovery.CASE,recovery.OFFSET)
if recovery.WIND then
recoverytext=recoverytext..string.format(" @ %.1f kts wind",recovery.SPEED)
end
rw=rw+1
if rw>=5 then
break
end
end
end
end
local tankertext=nil
if self.tanker then
tankertext=string.format("Recovery tanker frequency %.3f MHz\n",self.tanker.RadioFreq)
if self.tanker.TACANon then
tankertext=tankertext..string.format("Recovery tanker TACAN %d%s (%s)",self.tanker.TACANchannel,self.tanker.TACANmode,self.tanker.TACANmorse)
else
tankertext=tankertext.."Recovery tanker TACAN n/a"
end
end
local state=self:GetState()
if state=="Idle"then
state="Deck closed"
end
if self.turning then
state=state.." (turning currently)"
end
local text=string.format("%s info:\n",self.alias)
text=text..string.format("================================\n")
text=text..string.format("Carrier state: %s\n",state)
if self.case==1 then
text=text..string.format("Case %d recovery ops\n",self.case)
else
local radial=self:GetRadial(self.case,true,true,false)
text=text..string.format("Case %d recovery ops\nMarshal radial %03d°\n",self.case,radial)
end
text=text..string.format("BRC %03d° - FB %03d°\n",self:GetBRC(),self:GetFinalBearing(true))
text=text..string.format("Speed %.1f kts - Wind on deck %.1f kts\n",carrierspeed,wind)
text=text..string.format("Tower frequency %.3f MHz\n",self.TowerFreq)
text=text..string.format("Marshal radio %.3f MHz\n",self.MarshalFreq)
text=text..string.format("LSO radio %.3f MHz\n",self.LSOFreq)
text=text..string.format("TACAN Channel %s\n",tacan)
text=text..string.format("ICLS Channel %s\n",icls)
if tankertext then
text=text..tankertext.."\n"
end
text=text..string.format("# A/C total %d (%d)\n",Ntotal,ntotal)
text=text..string.format("# A/C marshal %d (%d)\n",Nmarshal,nmarshal)
text=text..string.format("# A/C pattern %d (%d) - spinning %d (%d)\n",Npattern,npattern,Nspinning,nspinning)
text=text..string.format("# A/C waiting %d (%d)\n",Nwaiting,nwaiting)
text=text..string.format(recoverytext)
self:T2(self.lid..text)
self:MessageToPlayer(playerData,text,nil,"",30,true)
else
self:E(self.lid..string.format("ERROR: Could not get player data for player %s.",playername))
end
end
end
function AIRBOSS:_DisplayCarrierWeather(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local text=""
local coord=self:GetCoordinate()
local T=coord:GetTemperature()
local P=coord:GetPressure()
local Wd,Ws=self:GetWind(nil,true)
local Bn,Bd=UTILS.BeaufortScale(Ws)
local WodPA,WodPP=self:GetWindOnDeck()
local WodPA=UTILS.MpsToKnots(WodPA)
local WodPP=UTILS.MpsToKnots(WodPP)
local WD=string.format('%03d°',Wd)
local Ts=string.format("%d°C",T)
local tT=string.format("%d°C",T)
local tW=string.format("%.1f knots",UTILS.MpsToKnots(Ws))
local tP=string.format("%.2f inHg",UTILS.hPa2inHg(P))
text=text..string.format("Weather Report at Carrier %s:\n",self.alias)
text=text..string.format("================================\n")
text=text..string.format("Temperature %s\n",tT)
text=text..string.format("Wind from %s at %s (%s)\n",WD,tW,Bd)
text=text..string.format("Wind on deck || %.1f kts, == %.1f kts\n",WodPA,WodPP)
text=text..string.format("QFE %.1f hPa = %s",P,tP)
if self.staticweather then
local clouds,visibility,fog,dust=self:_GetStaticWeather()
text=text..string.format("\nVisibility %.1f NM",UTILS.MetersToNM(visibility))
text=text..string.format("\nCloud base %d ft",UTILS.MetersToFeet(clouds.base))
text=text..string.format("\nCloud thickness %d ft",UTILS.MetersToFeet(clouds.thickness))
text=text..string.format("\nCloud density %d",clouds.density)
text=text..string.format("\nPrecipitation %d",clouds.iprecptns)
if fog then
text=text..string.format("\nFog thickness %d ft",UTILS.MetersToFeet(fog.thickness))
text=text..string.format("\nFog visibility %d ft",UTILS.MetersToFeet(fog.visibility))
else
text=text..string.format("\nNo fog")
end
if dust then
text=text..string.format("\nDust density %d",dust)
else
text=text..string.format("\nNo dust")
end
end
self:T2(self.lid..text)
self:MessageToPlayer(self.players[playername],text,nil,"",30,true)
else
self:E(self.lid..string.format("ERROR! Could not find player unit in CarrierWeather! Unit name = %s",_unitname))
end
end
function AIRBOSS:_SetDifficulty(_unitname,difficulty)
self:T2({difficulty=difficulty,unitname=_unitname})
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.difficulty=difficulty
local text=string.format("roger, your skill level is now: %s.",difficulty)
self:MessageToPlayer(playerData,text,nil,playerData.name,5)
else
self:E(self.lid..string.format("ERROR: Could not get player data for player %s.",playername))
end
if playerData.difficulty==AIRBOSS.Difficulty.HARD then
playerData.showhints=false
else
playerData.showhints=true
end
end
end
function AIRBOSS:_SetHintsOnOff(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.showhints=not playerData.showhints
local text=""
if playerData.showhints==true then
text=string.format("roger, hints are now ON.")
else
text=string.format("affirm, hints are now OFF.")
end
self:MessageToPlayer(playerData,text,nil,playerData.name,5)
end
end
end
function AIRBOSS:_DisplayAttitude(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.attitudemonitor=not playerData.attitudemonitor
end
end
end
function AIRBOSS:_SubtitlesOnOff(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.subtitles=not playerData.subtitles
local text=""
if playerData.subtitles==true then
text=string.format("roger, subtitiles are now ON.")
elseif playerData.subtitles==false then
text=string.format("affirm, subtitiles are now OFF.")
end
self:MessageToPlayer(playerData,text,nil,playerData.name,5)
end
end
end
function AIRBOSS:_TrapsheetOnOff(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
local text=""
if self.trapsheet then
playerData.trapon=not playerData.trapon
if playerData.trapon==true then
text=string.format("roger, your trapsheets are now SAVED.")
else
text=string.format("affirm, your trapsheets are NOT SAVED.")
end
else
text="negative, trap sheet data recorder is broken on this carrier."
end
self:MessageToPlayer(playerData,text,nil,playerData.name,5)
end
end
end
function AIRBOSS:_DisplayPlayerStatus(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local steptext=playerData.step
if playerData.step==AIRBOSS.PatternStep.HOLDING then
if playerData.holding==nil then
steptext="Transit to Marshal"
elseif playerData.holding==false then
steptext="Marshal (outside zone)"
elseif playerData.holding==true then
steptext="Marshal Stack Holding"
end
end
local stack=playerData.flag
local stacktext=nil
if stack>0 then
local stackalt=self:_GetMarshalAltitude(stack)
local angels=self:_GetAngels(stackalt)
stacktext=string.format("Marshal Stack %d, Angels %d\n",stack,angels)
if playerData.step==AIRBOSS.PatternStep.HOLDING and playerData.case>1 then
local radial=self:GetRadial(playerData.case,true,true,true)
stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n",radial,angels+15)
end
end
local fuel=playerData.unit:GetFuel()*100
local fuelstate=self:_GetFuelState(playerData.unit)
local _,nunitsGround=self:_GetFlightUnits(playerData,true)
local _,nunitsAirborne=self:_GetFlightUnits(playerData,false)
local text=string.format("Status of player %s (%s)\n",playerData.name,playerData.callsign)
text=text..string.format("================================\n")
text=text..string.format("Step: %s\n",steptext)
if stacktext then
text=text..stacktext
end
text=text..string.format("Recovery Case: %d\n",playerData.case)
text=text..string.format("Skill Level: %s\n",playerData.difficulty)
text=text..string.format("Modex: %s (%s)\n",playerData.onboard,self:_GetACNickname(playerData.actype))
text=text..string.format("Fuel State: %.1f lbs/1000 (%.1f %%)\n",fuelstate/1000,fuel)
text=text..string.format("# units: %d (%d airborne)\n",nunitsGround,nunitsAirborne)
text=text..string.format("Section Lead: %s (%d/%d)",tostring(playerData.seclead),#playerData.section+1,self.NmaxSection+1)
for _,_sec in pairs(playerData.section)do
local sec=_sec
text=text..string.format("\n- %s",sec.name)
end
if playerData.step==AIRBOSS.PatternStep.INITIAL then
local zoneinitial=self:GetCoordinate():Translate(UTILS.NMToMeters(3.5),self:GetRadial(2,false,false,false))
local flyhdg=playerData.unit:GetCoordinate():HeadingTo(zoneinitial)
local flydist=UTILS.MetersToNM(playerData.unit:GetCoordinate():Get2DDistance(zoneinitial))
local brc=self:GetBRC()
text=text..string.format("\nTo Initial: Fly heading %03d° for %.1f NM and turn to BRC %03d°",flyhdg,flydist,brc)
elseif playerData.step==AIRBOSS.PatternStep.PLATFORM then
local zoneplatform=self:_GetZonePlatform(playerData.case):GetCoordinate()
local flyhdg=playerData.unit:GetCoordinate():HeadingTo(zoneplatform)
local flydist=UTILS.MetersToNM(playerData.unit:GetCoordinate():Get2DDistance(zoneplatform))
local hdg=self:GetRadial(playerData.case,true,true,true)
text=text..string.format("\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°",flyhdg,flydist,hdg)
end
self:MessageToPlayer(playerData,text,nil,"",30,true)
else
self:E(self.lid..string.format("ERROR: playerData=nil. Unit name=%s, player name=%s",_unitName,_playername))
end
else
self:E(self.lid..string.format("ERROR: could not find player for unit %s",_unitName))
end
end
function AIRBOSS:_MarkMarshalZone(_unitName,flare)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local stack=playerData.flag
local case=playerData.case
local text=""
if stack>0 then
local zoneHolding=self:_GetZoneHolding(case,stack)
local zoneThree=self:_GetZoneCommence(case,stack)
local patternalt=self:_GetMarshalAltitude(stack,case)
patternalt=5
text="roger, marking"
if flare then
text=text..string.format("\n* Marshal zone stack %d with WHITE flares.",stack)
zoneHolding:FlareZone(FLARECOLOR.White,45,nil,patternalt)
text=text.."\n* Commence zone with RED flares."
zoneThree:FlareZone(FLARECOLOR.Red,45,nil,patternalt)
else
text=text..string.format("\n* Marshal zone stack %d with WHITE smoke.",stack)
zoneHolding:SmokeZone(SMOKECOLOR.White,45,patternalt)
text=text.."\n* Commence zone with RED smoke."
zoneThree:SmokeZone(SMOKECOLOR.Red,45,patternalt)
end
else
text="negative, you are currently not in a Marshal stack. No zones will be marked!"
end
self:MessageToPlayer(playerData,text,"MARSHAL",playerData.name)
end
end
end
function AIRBOSS:_MarkCaseZones(_unitName,flare)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local case=playerData.case
local text=string.format("affirm, marking CASE %d zones",case)
if flare then
if case==1 or case==2 then
text=text.."\n* initial with GREEN flares"
self:_GetZoneInitial(case):FlareZone(FLARECOLOR.Green,45)
end
if case==2 or case==3 then
text=text.."\n* approach corridor with GREEN flares"
self:_GetZoneCorridor(case):FlareZone(FLARECOLOR.Green,45)
end
if case==2 or case==3 then
text=text.."\n* platform with RED flares"
self:_GetZonePlatform(case):FlareZone(FLARECOLOR.Red,45)
end
if case==3 then
text=text.."\n* dirty up with YELLOW flares"
self:_GetZoneDirtyUp(case):FlareZone(FLARECOLOR.Yellow,45)
end
if case==2 or case==3 then
if math.abs(self.holdingoffset)>0 then
self:_GetZoneArcIn(case):FlareZone(FLARECOLOR.White,45)
text=text.."\n* arc turn in with WHITE flares"
self:_GetZoneArcOut(case):FlareZone(FLARECOLOR.White,45)
text=text.."\n* arc trun out with WHITE flares"
end
end
if case==3 then
text=text.."\n* bullseye with GREEN flares"
self:_GetZoneBullseye(case):FlareZone(FLARECOLOR.Green,45)
end
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
text=text.."\n* abeam landing stop with RED flares"
local ALSPT=self:_GetZoneAbeamLandingSpot()
ALSPT:FlareZone(FLARECOLOR.Red,5,nil,UTILS.FeetToMeters(110))
text=text.."\n* primary landing spot with GREEN flares"
local LSPT=self:_GetZoneLandingSpot()
LSPT:FlareZone(FLARECOLOR.Green,5,nil,self.carrierparam.deckheight)
end
else
if case==1 or case==2 then
text=text.."\n* initial with GREEN smoke"
self:_GetZoneInitial(case):SmokeZone(SMOKECOLOR.Green,45)
end
if case==2 or case==3 then
text=text.."\n* approach corridor with GREEN smoke"
self:_GetZoneCorridor(case):SmokeZone(SMOKECOLOR.Green,45)
end
if case==2 or case==3 then
text=text.."\n* platform with RED smoke"
self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Red,45)
end
if case==2 or case==3 then
if math.abs(self.holdingoffset)>0 then
self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue,45)
text=text.."\n* arc turn in with BLUE smoke"
self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue,45)
text=text.."\n* arc trun out with BLUE smoke"
end
end
if case==3 then
text=text.."\n* dirty up with ORANGE smoke"
self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange,45)
end
if case==3 then
text=text.."\n* bullseye with GREEN smoke"
self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.Green,45)
end
end
self:MessageToPlayer(playerData,text,"MARSHAL",playerData.name)
end
end
end
function AIRBOSS:_LSORadioCheck(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
self:RadioTransmission(self.LSORadio,self.LSOCall.RADIOCHECK,nil,nil,nil,true)
end
end
end
function AIRBOSS:_MarshalRadioCheck(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RADIOCHECK,nil,nil,nil,true)
end
end
end
function AIRBOSS:_SaveTrapSheet(playerData,grade)
if playerData.trapsheet==nil or#playerData.trapsheet==0 or not io then
return
end
local function _savefile(filename,data)
local f=io.open(filename,"wb")
if f then
f:write(data)
f:close()
else
self:E(self.lid..string.format("ERROR: could not save trap sheet to file %s.\nFile may contain invalid characters.",tostring(filename)))
end
end
local path=self.trappath
if lfs then
path=path or lfs.writedir()
end
local filename=nil
for i=1,9999 do
if self.trapprefix then
filename=string.format("%s_%s-%04d.csv",self.trapprefix,playerData.actype,i)
else
local name=UTILS.ReplaceIllegalCharacters(playerData.name,"_")
filename=string.format("AIRBOSS-%s_Trapsheet-%s_%s-%04d.csv",self.alias,name,playerData.actype,i)
end
if path~=nil then
filename=path.."\\"..filename
end
local _exists=UTILS.FileExists(filename)
if not _exists then
break
end
end
local text=string.format("Saving player %s trapsheet to file %s",playerData.name,filename)
self:I(self.lid..text)
local data="#Time,Rho,X,Z,Alt,AoA,GSE,LUE,Vtot,Vy,Gamma,Pitch,Roll,Yaw,Step,Grade,Points,Details\n"
local g0=playerData.trapsheet[1]
local T0=g0.Time
for i=1,#playerData.trapsheet do
local groove=playerData.trapsheet[i]
local t=groove.Time-T0
local a=UTILS.MetersToNM(groove.Rho or 0)
local b=-groove.X or 0
local c=groove.Z or 0
local d=UTILS.MetersToFeet(groove.Alt or 0)
local e=groove.AoA or 0
local f=groove.GSE or 0
local g=-groove.LUE or 0
local h=UTILS.MpsToKnots(groove.Vel or 0)
local i=(groove.Vy or 0)*196.85
local j=groove.Gamma or 0
local k=groove.Pitch or 0
local l=groove.Roll or 0
local m=groove.Yaw or 0
local n=self:_GS(groove.Step,-1)or"n/a"
local o=groove.Grade or"n/a"
local p=groove.GradePoints or 0
local q=groove.GradeDetail or"n/a"
data=data..string.format("%.2f,%.3f,%.1f,%.1f,%.1f,%.2f,%.2f,%.2f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%s,%s,%.1f,%s\n",t,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q)
end
_savefile(filename,data)
end
function AIRBOSS:onbeforeSave(From,Event,To,path,filename)
if not io then
self:E(self.lid.."ERROR: io not desanitized. Can't save player grades.")
return false
end
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
return true
end
function AIRBOSS:onafterSave(From,Event,To,path,filename)
local function _savefile(filename,data)
local f=assert(io.open(filename,"wb"))
f:write(data)
f:close()
end
if lfs then
path=path or lfs.writedir()
end
filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias)
if path~=nil then
filename=path.."\\"..filename
end
local scores="Name,Pass,Points Final,Points Pass,Grade,Details,Wire,Tgroove,Case,Wind,Modex,Airframe,Carrier Type,Carrier Name,Theatre,Mission Time,Mission Date,OS Date\n"
local n=0
for playername,grades in pairs(self.playerscores)do
for i,_grade in pairs(grades)do
local grade=_grade
local wire="n/a"
if grade.wire and grade.wire<=4 then
wire=tostring(grade.wire)
end
local Tgroove="n/a"
if grade.Tgroove and grade.Tgroove<=360 and grade.case<3 then
Tgroove=tostring(UTILS.Round(grade.Tgroove,1))
end
local finalscore="n/a"
if grade.finalscore then
finalscore=tostring(UTILS.Round(grade.finalscore,1))
end
scores=scores..string.format("%s,%d,%s,%.1f,%s,%s,%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",playername,i,finalscore,grade.points,grade.grade,grade.details,wire,Tgroove,grade.case,grade.wind,grade.modex,grade.airframe,grade.carriertype,grade.carriername,grade.theatre,grade.mitime,grade.midate,grade.osdate)
n=n+1
end
end
local text=string.format("Saving %d player LSO grades to file %s",n,filename)
self:I(self.lid..text)
_savefile(filename,scores)
end
function AIRBOSS:onbeforeLoad(From,Event,To,path,filename)
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
if not io then
self:E(self.lid.."WARNING: io not desanitized. Can't load player grades.")
return false
end
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
if lfs then
path=path or lfs.writedir()
end
filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias)
if path~=nil then
filename=path.."\\"..filename
end
local exists=_fileexists(filename)
if exists then
return true
else
self:E(self.lid..string.format("WARNING: Player LSO grades file %s does not exist.",filename))
return false
end
end
function AIRBOSS:onafterLoad(From,Event,To,path,filename)
local function _loadfile(filename)
local f=assert(io.open(filename,"rb"))
local data=f:read("*all")
f:close()
return data
end
if lfs then
path=path or lfs.writedir()
end
filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias)
if path~=nil then
filename=path.."\\"..filename
end
local text=string.format("Loading player LSO grades from file %s",filename)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:I(self.lid..text)
local data=_loadfile(filename)
local playergrades=UTILS.Split(data,"\n")
table.remove(playergrades,1)
self.playerscores={}
local n=0
for _,gradeline in pairs(playergrades)do
local gradedata=UTILS.Split(gradeline,",")
self:T2(gradedata)
local grade={}
local playername=gradedata[1]
if gradedata[3]~=nil and gradedata[3]~="n/a"then
grade.finalscore=tonumber(gradedata[3])
end
grade.points=tonumber(gradedata[4])
grade.grade=tostring(gradedata[5])
grade.details=tostring(gradedata[6])
if gradedata[7]~=nil and gradedata[7]~="n/a"then
grade.wire=tonumber(gradedata[7])
end
if gradedata[8]~=nil and gradedata[8]~="n/a"then
grade.Tgroove=tonumber(gradedata[8])
end
grade.case=tonumber(gradedata[9])
grade.wind=gradedata[10]or"n/a"
grade.modex=gradedata[11]or"n/a"
grade.airframe=gradedata[12]or"n/a"
grade.carriertype=gradedata[13]or"n/a"
grade.carriername=gradedata[14]or"n/a"
grade.theatre=gradedata[15]or"n/a"
grade.mitime=gradedata[16]or"n/a"
grade.midate=gradedata[17]or"n/a"
grade.osdate=gradedata[18]or"n/a"
self.playerscores[playername]=self.playerscores[playername]or{}
table.insert(self.playerscores[playername],grade)
n=n+1
self:T2({playername,self.playerscores[playername]})
end
local text=string.format("Loaded %d player LSO grades from file %s",n,filename)
self:I(self.lid..text)
end
function AIRBOSS:onafterLSOGrade(From,Event,To,playerData,grade)
if self.funkmanSocket then
local trapsheet={};trapsheet.X={};trapsheet.Z={};trapsheet.AoA={};trapsheet.Alt={}
for i=1,#playerData.trapsheet do
local ts=playerData.trapsheet[i]
table.insert(trapsheet.X,UTILS.Round(ts.X,1))
table.insert(trapsheet.Z,UTILS.Round(ts.Z,1))
table.insert(trapsheet.AoA,UTILS.Round(ts.AoA,2))
table.insert(trapsheet.Alt,UTILS.Round(ts.Alt,1))
end
local result={}
result.command=SOCKET.DataType.LSOGRADE
result.name=playerData.name
result.trapsheet=trapsheet
result.airframe=grade.airframe
result.mitime=grade.mitime
result.midate=grade.midate
result.wind=grade.wind
result.carriertype=grade.carriertype
result.carriername=grade.carriername
result.carrierrwy=grade.carrierrwy
result.landingdist=self.carrierparam.landingdist
result.theatre=grade.theatre
result.case=playerData.case
result.Tgroove=grade.Tgroove
result.wire=grade.wire
result.grade=grade.grade
result.points=grade.points
result.details=grade.details
self:T(self.lid.."Result onafterLSOGrade")
self:T(result)
self.funkmanSocket:SendTable(result)
end
end
RECOVERYTANKER={
ClassName="RECOVERYTANKER",
Debug=false,
lid=nil,
carrier=nil,
carriertype=nil,
tankergroupname=nil,
tanker=nil,
airbase=nil,
beacon=nil,
TACANchannel=nil,
TACANmode=nil,
TACANmorse=nil,
TACANon=nil,
RadioFreq=nil,
RadioModu=nil,
altitude=nil,
speed=nil,
distStern=nil,
distBow=nil,
dTupdate=nil,
Dupdate=nil,
Hupdate=nil,
Tupdate=nil,
takeoff=nil,
lowfuel=nil,
respawn=nil,
respawninair=nil,
uncontrolledac=nil,
orientation=nil,
orientlast=nil,
position=nil,
alias=nil,
uid=0,
awacs=nil,
callsignname=nil,
callsignnumber=nil,
modex=nil,
eplrs=nil,
recovery=nil,
terminaltype=nil,
}
_RECOVERYTANKERID=0
RECOVERYTANKER.version="1.0.9"
function RECOVERYTANKER:New(carrierunit,tankergroupname)
local self=BASE:Inherit(self,FSM:New())
if type(carrierunit)=="string"then
self.carrier=UNIT:FindByName(carrierunit)
else
self.carrier=carrierunit
end
self.carriertype=self.carrier:GetTypeName()
self.tankergroupname=tankergroupname
_RECOVERYTANKERID=_RECOVERYTANKERID+1
self.uid=_RECOVERYTANKERID
self.carrier:SetState(self.carrier,string.format("RECOVERYTANKER_%d",self.uid),self)
self.alias=string.format("%s_%s_%02d",self.carrier:GetName(),self.tankergroupname,_RECOVERYTANKERID)
self.lid=string.format("RECOVERYTANKER %s | ",self.alias)
self:SetAltitude()
self:SetSpeed()
self:SetRacetrackDistances()
self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName()))
self:SetTakeoffHot()
self:SetLowFuelThreshold()
self:SetRespawnOnOff()
self:SetTACAN()
self:SetRadio()
self:SetPatternUpdateDistance()
self:SetPatternUpdateHeading()
self:SetPatternUpdateInterval()
self:SetAWACS(false)
self:SetRecoveryAirboss(false)
self.terminaltype=AIRBASE.TerminalType.OpenMedOrBig
if false then
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
end
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","RefuelStart","Refueling")
self:AddTransition("*","RefuelStop","Running")
self:AddTransition("*","Run","Running")
self:AddTransition("Running","RTB","Returning")
self:AddTransition("Returning","Returned","Returned")
self:AddTransition("*","Status","*")
self:AddTransition("Running","PatternUpdate","*")
self:AddTransition("*","Stop","Stopped")
return self
end
function RECOVERYTANKER:SetSpeed(speed)
self.speed=UTILS.KnotsToMps(speed or 274)
return self
end
function RECOVERYTANKER:SetAltitude(altitude)
self.altitude=UTILS.FeetToMeters(altitude or 6000)
return self
end
function RECOVERYTANKER:SetRacetrackDistances(distbow,diststern)
self.distBow=UTILS.NMToMeters(distbow or 10)
self.distStern=-UTILS.NMToMeters(diststern or 4)
return self
end
function RECOVERYTANKER:SetPatternUpdateInterval(interval)
self.dTupdate=(interval or 10)*60
return self
end
function RECOVERYTANKER:SetPatternUpdateDistance(distancechange)
self.Dupdate=UTILS.NMToMeters(distancechange or 5)
return self
end
function RECOVERYTANKER:SetPatternUpdateHeading(headingchange)
self.Hupdate=headingchange or 5
return self
end
function RECOVERYTANKER:SetLowFuelThreshold(fuelthreshold)
self.lowfuel=fuelthreshold or 10
return self
end
function RECOVERYTANKER:SetHomeBase(airbase,terminaltype)
if type(airbase)=="string"then
self.airbase=AIRBASE:FindByName(airbase)
else
self.airbase=airbase
end
if not self.airbase then
self:E(self.lid.."ERROR: Airbase is nil!")
end
if terminaltype then
self.terminaltype=terminaltype
end
return self
end
function RECOVERYTANKER:SetRecoveryAirboss(switch)
if switch==true or switch==nil then
self.recovery=true
else
self.recovery=false
end
return self
end
function RECOVERYTANKER:SetAWACS(switch,eplrs)
if switch==nil or switch==true then
self.awacs=true
else
self.awacs=false
end
if eplrs==nil or eplrs==true then
self.eplrs=true
else
self.eplrs=false
end
return self
end
function RECOVERYTANKER:SetCallsign(callsignname,callsignnumber)
self.callsignname=callsignname
self.callsignnumber=callsignnumber
return self
end
function RECOVERYTANKER:SetModex(modex)
self.modex=modex
return self
end
function RECOVERYTANKER:SetTakeoff(takeofftype)
self.takeoff=takeofftype
return self
end
function RECOVERYTANKER:SetTakeoffHot()
self:SetTakeoff(SPAWN.Takeoff.Hot)
return self
end
function RECOVERYTANKER:SetTakeoffCold()
self:SetTakeoff(SPAWN.Takeoff.Cold)
return self
end
function RECOVERYTANKER:SetTakeoffAir()
self:SetTakeoff(SPAWN.Takeoff.Air)
return self
end
function RECOVERYTANKER:SetRespawnOn()
self.respawn=true
return self
end
function RECOVERYTANKER:SetRespawnOff()
self.respawn=false
return self
end
function RECOVERYTANKER:SetRespawnOnOff(switch)
if switch==nil or switch==true then
self.respawn=true
else
self.respawn=false
end
return self
end
function RECOVERYTANKER:SetRespawnInAir()
self.respawninair=true
return self
end
function RECOVERYTANKER:SetUseUncontrolledAircraft()
self.uncontrolledac=true
return self
end
function RECOVERYTANKER:SetTACANoff()
self.TACANon=false
return self
end
function RECOVERYTANKER:SetTACAN(channel,morse,mode)
self.TACANchannel=channel or 1
self.TACANmode=mode or"Y"
self.TACANmorse=morse or"TKR"
self.TACANon=true
return self
end
function RECOVERYTANKER:SetRadio(frequency,modulation)
self.RadioFreq=frequency or 251
self.RadioModu=modulation or"AM"
return self
end
function RECOVERYTANKER:SetDebugModeON()
self.Debug=true
return self
end
function RECOVERYTANKER:SetDebugModeOFF()
self.Debug=false
return self
end
function RECOVERYTANKER:IsReturning()
return self:is("Returning")
end
function RECOVERYTANKER:IsReturned()
return self:is("Returned")
end
function RECOVERYTANKER:IsRunning()
return self:is("Running")
end
function RECOVERYTANKER:IsRefueling()
return self:is("Refueling")
end
function RECOVERYTANKER:IsStopped()
return self:is("Stopped")
end
function RECOVERYTANKER:GetAlias()
return self.alias
end
function RECOVERYTANKER:GetUnitName()
local unit=self.tanker:GetUnit(1)
if unit then
return unit:GetName()
end
return nil
end
function RECOVERYTANKER:onafterStart(From,Event,To)
self:I(string.format("Starting Recovery Tanker v%s for carrier unit %s of type %s for tanker group %s.",RECOVERYTANKER.version,self.carrier:GetName(),self.carriertype,self.tankergroupname))
self:HandleEvent(EVENTS.EngineShutdown)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.Refueling,self._RefuelingStart)
self:HandleEvent(EVENTS.RefuelingStop,self._RefuelingStop)
self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrDead)
self:HandleEvent(EVENTS.Dead,self._OnEventCrashOrDead)
local Spawn=SPAWN:NewWithAlias(self.tankergroupname,self.alias)
Spawn:InitRadioCommsOnOff(true)
Spawn:InitRadioFrequency(self.RadioFreq)
Spawn:InitRadioModulation(self.RadioModu)
Spawn:InitModex(self.modex)
if self.takeoff==SPAWN.Takeoff.Air then
local hdg=self.carrier:GetHeading()
local dist=-self.distStern+UTILS.NMToMeters(4)
local Carrier=self.carrier:GetCoordinate():Translate(dist,hdg+190):SetAltitude(self.altitude)
Spawn:InitHeading(hdg+10)
self.tanker=Spawn:SpawnFromCoordinate(Carrier)
else
if self.uncontrolledac then
self.tanker=GROUP:FindByName(self.tankergroupname)
if self.tanker:IsAlive()then
self.tanker:StartUncontrolled()
else
self:E(string.format("ERROR: No uncontrolled (alive) tanker group with name %s could be found!",self.tankergroupname))
return
end
else
self.tanker=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,self.terminaltype)
end
end
self:ScheduleOnce(1,self._InitRoute,self,-self.distStern+UTILS.NMToMeters(3))
if self.TACANon then
self:_ActivateTACAN(2)
end
if self.callsignname then
self.tanker:CommandSetCallsign(self.callsignname,self.callsignnumber,2)
end
if self.eplrs then
self.tanker:CommandEPLRS(true,3)
end
self.orientation=self.carrier:GetOrientationX()
self.orientlast=self.carrier:GetOrientationX()
self.position=self.carrier:GetCoordinate()
self:__Status(10)
end
function RECOVERYTANKER:onafterStatus(From,Event,To)
local time=timer.getTime()
if self.tanker and self.tanker:IsAlive()then
local fuel=self.tanker:GetFuel()*100
local life=self.tanker:GetUnit(1):GetLife()
local life0=self.tanker:GetUnit(1):GetLife0()
local lifeR=self.tanker:GetUnit(1):GetLifeRelative()
local text=string.format("Recovery tanker %s: state=%s fuel=%.1f, life=%.1f/%.1f=%d",self.tanker:GetName(),self:GetState(),fuel,life,life0,lifeR*100)
self:T(self.lid..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
if self:IsRunning()then
if fuel<self.lowfuel then
if(self.takeoff==SPAWN.Takeoff.Air or self.respawninair)and not self.recovery then
if self.respawn then
local text=string.format("Respawning recovery tanker %s in air.",self.tanker:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self.tanker:InitHeading(self.tanker:GetHeading())
self.tanker:InitRadioCommsOnOff(true)
self.tanker:InitRadioFrequency(self.RadioFreq)
self.tanker:InitRadioModulation(self.RadioModu)
self.tanker:InitModex(self.modex)
self.tanker=self.tanker:Respawn(nil,true)
self:__PatternUpdate(2)
if self.TACANon then
self:_ActivateTACAN(3)
end
if self.callsignname then
self.tanker:CommandSetCallsign(self.callsignname,self.callsignnumber,3)
end
if self.eplrs then
self.tanker:CommandEPLRS(true,4)
end
end
else
self:RTB(self.airbase)
end
else
if self.Tupdate then
local dt=time-self.Tupdate
local updatepattern=self:_CheckPatternUpdate(dt)
if updatepattern then
self:PatternUpdate()
end
end
end
elseif self:IsReturning()then
self:T2(self.lid.."Tanker is returning.")
elseif self:IsReturned()then
self:T2(self.lid.."Tanker returned. waiting for engine shutdown.")
end
if not self:IsStopped()then
self:__Status(-30)
end
else
if not self:IsStopped()then
self:E(self.lid.."Recovery tanker is NOT alive (and not stopped)!")
self:Stop()
if self.respawn then
self:__Start(5)
end
end
end
end
function RECOVERYTANKER:onafterPatternUpdate(From,Event,To)
local text=string.format("Updating recovery tanker %s racetrack pattern.",self.tanker:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
local hdg=self.carrier:GetHeading()
local Carrier=self.carrier:GetCoordinate()
local p0=self.tanker:GetCoordinate():Translate(UTILS.NMToMeters(1),self.tanker:GetHeading(),true)
local p1=Carrier:Translate(self.distStern,hdg):SetAltitude(self.altitude)
local p2=Carrier:Translate(self.distBow,hdg):SetAltitude(self.altitude)
local taskorbit=self.tanker:TaskOrbit(p1,self.altitude,self.speed,p2)
if self.Debug then
p0:MarkToAll("Waypoint P0 "..self.tanker:GetName())
p1:MarkToAll("Racetrack P1 "..self.tanker:GetName())
p2:MarkToAll("Racetrack P2 "..self.tanker:GetName())
end
local wp={}
wp[1]=self.tanker:GetCoordinate():WaypointAirTurningPoint(nil,UTILS.MpsToKmph(self.speed),{},"Current Position")
wp[2]=p0:WaypointAirTurningPoint(nil,UTILS.MpsToKmph(self.speed),{taskorbit},"Tanker Orbit")
self.tanker:WayPointInitialize(wp)
local taskroll=self.tanker:EnRouteTaskTanker()
if self.awacs then
taskroll=self.tanker:EnRouteTaskAWACS()
end
local taskroute=self.tanker:TaskRoute(wp)
local taskcombo=self.tanker:TaskCombo({taskroll,taskroute})
self.tanker:SetTask(taskcombo,1)
self.Tupdate=timer.getTime()
end
function RECOVERYTANKER:onafterRTB(From,Event,To,airbase)
airbase=airbase or self.airbase
local text=string.format("Recovery tanker %s returning to airbase %s.",self.tanker:GetName(),airbase:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
local wp={}
local speed=self.tanker:GetSpeedMax()*0.75
wp[1]=self.tanker:GetCoordinate():WaypointAirTurningPoint(nil,speed,{},"Current Position")
if self.recovery then
wp[2]=self.tanker:GetCoordinate():Translate(UTILS.NMToMeters(10),self.tanker:GetHeading(),true):WaypointAirTurningPoint(nil,speed,{},"WP")
else
wp[2]=airbase:GetCoordinate():SetAltitude(500):WaypointAirLanding(speed,airbase,nil,"Land at airbase")
end
self.tanker:WayPointInitialize(wp)
self.tanker:Route(wp,1)
end
function RECOVERYTANKER:onafterReturned(From,Event,To,airbase)
if airbase then
local airbasename=airbase:GetName()
self:I(self.lid..string.format("Tanker returned to airbase %s",tostring(airbasename)))
else
self:E(self.lid..string.format("WARNING: Tanker landed but airbase (EventData.Place) is nil!"))
end
end
function RECOVERYTANKER:onafterStop(From,Event,To)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.Refueling)
self:UnHandleEvent(EVENTS.RefuelingStop)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Crash)
self.CallScheduler:Clear()
if self.tanker and self.tanker:IsAlive()then
self:I(self.lid.."Stopping FSM and despawning tanker.")
self.tanker:Destroy()
else
self:I(self.lid.."Stopping FSM. Tanker was not alive.")
end
end
function RECOVERYTANKER:OnEventLand(EventData)
local group=EventData.IniGroup
if group and group:IsAlive()then
local groupname=group:GetName()
if groupname==self.tanker:GetName()then
local airbase=nil
local airbasename="unknown"
if EventData.Place then
airbase=EventData.Place
airbasename=airbase:GetName()
end
local text=string.format("Recovery tanker group %s landed at airbase %s.",group:GetName(),airbasename)
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self:__Returned(1,airbase)
end
end
end
function RECOVERYTANKER:OnEventEngineShutdown(EventData)
local group=EventData.IniGroup
if group and group:IsAlive()then
local groupname=group:GetName()
if groupname==self.tanker:GetName()and self.respawn then
local text=string.format("Respawning recovery tanker group %s.",group:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
group:InitRadioCommsOnOff(true)
group:InitRadioFrequency(self.RadioFreq)
group:InitRadioModulation(self.RadioModu)
group:InitModex(self.modex)
self:ScheduleOnce(1,GROUP.RespawnAtCurrentAirbase,group)
if self.TACANon then
self:_ActivateTACAN(3)
end
if self.callsignname then
self.tanker:CommandSetCallsign(self.callsignname,self.callsignnumber,3)
end
if self.eplrs then
self.tanker:CommandEPLRS(true,4)
end
self:ScheduleOnce(2,RECOVERYTANKER._InitRoute,self,-self.distStern+UTILS.NMToMeters(3))
end
end
end
function RECOVERYTANKER:_RefuelingStart(EventData)
if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive()then
local receiver=EventData.IniUnit
local dist=receiver:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate())
if dist>100 then
return
end
local text=string.format("Recovery tanker %s started refueling unit %s",self.tanker:GetName(),receiver:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self:RefuelStart(receiver)
end
end
function RECOVERYTANKER:_RefuelingStop(EventData)
if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive()then
local receiver=EventData.IniUnit
local dist=receiver:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate())
if dist>100 then
return
end
local text=string.format("Recovery tanker %s stopped refueling unit %s",self.tanker:GetName(),receiver:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self:RefuelStop(receiver)
end
end
function RECOVERYTANKER:_OnEventCrashOrDead(EventData)
self:F2({eventdata=EventData})
if EventData and EventData.IniUnit then
local unit=EventData.IniUnit
local unitname=tostring(EventData.IniUnitName)
if EventData.IniGroupName==self.tanker:GetName()then
self:E(self.lid..string.format("Recovery tanker %s crashed!",unitname))
self:Stop()
if self.respawn then
self:__Start(5)
end
end
end
end
function RECOVERYTANKER:_InitPatternTaskFunction()
local carriername=self.carrier:GetName()
local DCSScript={}
DCSScript[#DCSScript+1]=string.format('local mycarrier = UNIT:FindByName(\"%s\") ',carriername)
DCSScript[#DCSScript+1]=string.format('local mytanker  = mycarrier:GetState(mycarrier, \"RECOVERYTANKER_%d\") ',self.uid)
DCSScript[#DCSScript+1]=string.format('mytanker:PatternUpdate()')
local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript)))
return DCSTask
end
function RECOVERYTANKER:_InitRoute(dist,delay)
dist=dist or UTILS.NMToMeters(8)
delay=delay or 1
self:T(self.lid..string.format("Initializing route of recovery tanker %s.",self.tanker:GetName()))
local Carrier=self.carrier:GetCoordinate()
local hdg=self.carrier:GetHeading()
local p=Carrier:Translate(dist,hdg+190):SetAltitude(self.altitude)
local speed=self.tanker:GetSpeedMax()*0.8
if self.Debug then
p:MarkToAll(string.format("Enter Pattern WP: alt=%d ft, speed=%d kts",UTILS.MetersToFeet(self.altitude),speed*0.539957))
end
local task=self:_InitPatternTaskFunction()
local wp={}
if self.takeoff==SPAWN.Takeoff.Air then
wp[#wp+1]=self.tanker:GetCoordinate():SetAltitude(self.altitude):WaypointAirTurningPoint(nil,speed,{},"Spawn Position")
else
wp[#wp+1]=Carrier:WaypointAirTakeOffParking()
end
wp[#wp+1]=p:WaypointAirTurningPoint(nil,speed,{task},"Enter Pattern")
self.tanker:Route(wp,delay)
self:__Run(1)
self.Tupdate=nil
end
function RECOVERYTANKER:_CheckPatternUpdate(dt)
local pos=self.carrier:GetCoordinate()
local vNew=self.carrier:GetOrientationX()
local vOld=self.orientation
local vLast=self.orientlast
vNew.y=0;vOld.y=0;vLast.y=0
local deltaHeading=math.deg(math.acos(UTILS.VecDot(vNew,vOld)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vOld)))
local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast)))
self.orientlast=vNew
local turning=deltaLast>=1
if turning then
self:T2(self.lid..string.format("Carrier is turning. Delta Heading = %.1f",deltaLast))
end
local Hchange=false
if math.abs(deltaHeading)>=self.Hupdate then
self:T(self.lid..string.format("Carrier heading changed by %d degrees. Turning=%s.",deltaHeading,tostring(turning)))
Hchange=true
end
local dist=pos:Get2DDistance(self.position)
local Dchange=false
if dist>self.Dupdate then
self:T(self.lid..string.format("Carrier position changed by %.1f NM. Turning=%s.",UTILS.MetersToNM(dist),tostring(turning)))
Dchange=true
end
local update=false
if self:IsRunning()and dt>self.dTupdate and not turning then
if Hchange or Dchange then
local text=string.format("Updating tanker %s pattern due to carrier position=%s or heading=%s change.",self.tanker:GetName(),tostring(Dchange),tostring(Hchange))
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self.orientation=vNew
self.position=pos
update=true
end
end
return update
end
function RECOVERYTANKER:_ActivateTACAN(delay)
if delay and delay>0 then
self:ScheduleOnce(delay,RECOVERYTANKER._ActivateTACAN,self)
else
local unit=self.tanker:GetUnit(1)
if unit and unit:IsAlive()then
local text=string.format("Activating TACAN beacon: channel=%d mode=%s, morse=%s.",self.TACANchannel,self.TACANmode,self.TACANmorse)
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self.beacon=BEACON:New(unit)
self.beacon:ActivateTACAN(self.TACANchannel,self.TACANmode,self.TACANmorse,true)
else
self:E(self.lid.."ERROR: Recovery tanker is not alive!")
end
end
end
function RECOVERYTANKER:_Pattern()
local hdg=self.carrier:GetHeading()
local alt=self.altitude
local Carrier=self.carrier:GetCoordinate()
local width=UTILS.NMToMeters(8)
local p={}
p[1]=self.tanker:GetCoordinate()
p[2]=Carrier:SetAltitude(alt)
p[3]=p[2]:Translate(self.distBow,hdg)
p[4]=p[3]:Translate(width/math.sqrt(2),hdg-45)
p[5]=p[3]:Translate(width,hdg-90)
p[6]=p[5]:Translate(self.distStern-self.distBow,hdg)
p[7]=p[2]:Translate(self.distStern,hdg)
local wp={}
for i=1,#p do
local coord=p[i]
coord:MarkToAll(string.format("Waypoint %d",i))
table.insert(wp,coord:WaypointAirTurningPoint(nil,UTILS.MpsToKmph(self.speed)))
end
return wp
end
RESCUEHELO={
ClassName="RESCUEHELO",
Debug=false,
lid=nil,
carrier=nil,
carriertype=nil,
helogroupname=nil,
helo=nil,
airbase=nil,
takeoff=nil,
followset=nil,
formation=nil,
lowfuel=nil,
altitude=nil,
offsetX=nil,
offsetZ=nil,
rescuezone=nil,
respawn=nil,
respawninair=nil,
uncontrolledac=nil,
rescueon=nil,
rescueduration=nil,
rescuespeed=nil,
rescuestopboat=nil,
HeloFuel0=nil,
rtb=nil,
carrierstop=nil,
alias=nil,
uid=0,
modex=nil,
dtFollow=nil,
}
_RESCUEHELOID=0
RESCUEHELO.version="1.1.0"
function RESCUEHELO:New(carrierunit,helogroupname)
local self=BASE:Inherit(self,FSM:New())
if type(carrierunit)=="string"then
self.carrier=UNIT:FindByName(carrierunit)
else
self.carrier=carrierunit
end
self.carriertype=self.carrier:GetTypeName()
self.helogroupname=helogroupname
_RESCUEHELOID=_RESCUEHELOID+1
self.uid=_RESCUEHELOID
self.carrier:SetState(self.carrier,string.format("RESCUEHELO_%d",self.uid),self)
self.alias=string.format("%s_%s_%02d",self.carrier:GetName(),self.helogroupname,_RESCUEHELOID)
self.lid=string.format("RESCUEHELO %s | ",self.alias)
self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName()))
self:SetTakeoffHot()
self:SetLowFuelThreshold()
self:SetAltitude()
self:SetOffsetX()
self:SetOffsetZ()
self:SetRespawnOn()
self:SetRescueOn()
self:SetRescueZone()
self:SetRescueHoverSpeed()
self:SetRescueDuration()
self:SetFollowTimeInterval()
self:SetRescueStopBoatOff()
self.rtb=false
self.carrierstop=false
if false then
self.Debug=true
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
end
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("Running","Rescue","Rescuing")
self:AddTransition("Running","RTB","Returning")
self:AddTransition("Rescuing","RTB","Returning")
self:AddTransition("Returning","Returned","Returned")
self:AddTransition("Running","Run","Running")
self:AddTransition("Returned","Run","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","Stop","Stopped")
return self
end
function RESCUEHELO:SetLowFuelThreshold(threshold)
self.lowfuel=threshold or 5
return self
end
function RESCUEHELO:SetHomeBase(airbase)
if type(airbase)=="string"then
self.airbase=AIRBASE:FindByName(airbase)
else
self.airbase=airbase
end
if not self.airbase then
self:E(self.lid.."ERROR: Airbase is nil!")
end
return self
end
function RESCUEHELO:SetRescueZone(radius)
radius=UTILS.NMToMeters(radius or 15)
self.rescuezone=ZONE_UNIT:New("Rescue Zone",self.carrier,radius)
return self
end
function RESCUEHELO:SetRescueHoverSpeed(speed)
self.rescuespeed=UTILS.KnotsToMps(speed or 5)
return self
end
function RESCUEHELO:SetRescueDuration(duration)
self.rescueduration=(duration or 5)*60
return self
end
function RESCUEHELO:SetRescueOn()
self.rescueon=true
return self
end
function RESCUEHELO:SetRescueOff()
self.rescueon=false
return self
end
function RESCUEHELO:SetRescueStopBoatOn()
self.rescuestopboat=true
return self
end
function RESCUEHELO:SetRescueStopBoatOff()
self.rescuestopboat=false
return self
end
function RESCUEHELO:SetTakeoff(takeofftype)
self.takeoff=takeofftype or SPAWN.Takeoff.Hot
return self
end
function RESCUEHELO:SetTakeoffHot()
self:SetTakeoff(SPAWN.Takeoff.Hot)
return self
end
function RESCUEHELO:SetTakeoffCold()
self:SetTakeoff(SPAWN.Takeoff.Cold)
return self
end
function RESCUEHELO:SetTakeoffAir()
self:SetTakeoff(SPAWN.Takeoff.Air)
return self
end
function RESCUEHELO:SetAltitude(alt)
self.altitude=alt or 70
return self
end
function RESCUEHELO:SetOffsetX(distance)
self.offsetX=distance or 200
return self
end
function RESCUEHELO:SetOffsetZ(distance)
self.offsetZ=distance or 240
return self
end
function RESCUEHELO:SetRespawnOn()
self.respawn=true
return self
end
function RESCUEHELO:SetRespawnOff()
self.respawn=false
return self
end
function RESCUEHELO:SetRespawnOnOff(switch)
if switch==nil or switch==true then
self.respawn=true
else
self.respawn=false
end
return self
end
function RESCUEHELO:SetRespawnInAir()
self.respawninair=true
return self
end
function RESCUEHELO:SetModex(modex)
self.modex=modex
return self
end
function RESCUEHELO:SetFollowTimeInterval(dt)
self.dtFollow=dt or 1.0
return self
end
function RESCUEHELO:SetUseUncontrolledAircraft()
self.uncontrolledac=true
return self
end
function RESCUEHELO:SetDebugModeON()
self.Debug=true
return self
end
function RESCUEHELO:SetDebugModeOFF()
self.Debug=false
return self
end
function RESCUEHELO:IsReturning()
return self:is("Returning")
end
function RESCUEHELO:IsRunning()
return self:is("Running")
end
function RESCUEHELO:IsRescuing()
return self:is("Rescuing")
end
function RESCUEHELO:IsStopped()
return self:is("Stopped")
end
function RESCUEHELO:GetAlias()
return self.alias
end
function RESCUEHELO:GetUnitName()
local unit=self.helo:GetUnit(1)
if unit then
return unit:GetName()
end
return nil
end
function RESCUEHELO:OnEventLand(EventData)
local group=EventData.IniGroup
if group and group:IsAlive()then
local groupname=group:GetName()
if groupname==self.helo:GetName()then
local airbase=nil
local airbasename="unknown"
if EventData.Place then
airbase=EventData.Place
airbasename=airbase:GetName()
end
local text=string.format("Rescue helo group %s landed at airbase %s.",groupname,airbasename)
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
if self:IsRescuing()then
self:T(self.lid..string.format("Rescue helo %s returned from rescue operation.",groupname))
end
if self.takeoff==SPAWN.Takeoff.Air or self.respawninair then
if not self:IsRescuing()then
self:E(self.lid..string.format("WARNING: Rescue helo %s landed. This should not happen for Takeoff=Air or respawninair=true and no rescue operation in progress.",groupname))
end
end
self:__Returned(3,airbase)
end
end
end
function RESCUEHELO:_OnEventCrashOrEject(EventData)
self:F2({eventdata=EventData})
if EventData and EventData.IniUnit then
local unit=EventData.IniUnit
local unitname=tostring(EventData.IniUnitName)
if EventData.IniGroupName~=self.helo:GetName()then
local text=string.format("Unit %s crashed or ejected.",unitname)
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
local coord=unit:GetCoordinate()
if coord and self.rescuezone:IsCoordinateInZone(coord)then
if self.Debug then
coord:MarkToCoalition(self.lid..string.format("Crash site of unit %s.",unitname),self.helo:GetCoalition())
end
local rightcoalition=EventData.IniGroup:GetCoalition()==self.helo:GetCoalition()
if self:IsRunning()and self.rescueon and rightcoalition then
self:Rescue(coord)
end
end
else
self:E(self.lid..string.format("Rescue helo %s crashed!",unitname))
self:Stop()
if self.respawn then
self:__Start(5)
end
end
end
end
function RESCUEHELO:onafterStart(From,Event,To)
local text=string.format("Starting Rescue Helo Formation v%s for carrier unit %s of type %s.",RESCUEHELO.version,self.carrier:GetName(),self.carriertype)
self:I(self.lid..text)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrEject)
self:HandleEvent(EVENTS.Ejection,self._OnEventCrashOrEject)
local delay=120
local Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias)
Spawn:InitModex(self.modex)
if self.takeoff==SPAWN.Takeoff.Air then
local hdg=self.carrier:GetHeading()
local dist=UTILS.NMToMeters(0.2)
local Carrier=self.carrier:GetCoordinate():Translate(dist,hdg):SetAltitude(math.max(100,self.altitude))
Spawn:InitHeading(hdg)
self.helo=Spawn:SpawnFromCoordinate(Carrier)
delay=1
else
if self.uncontrolledac then
self.helo=GROUP:FindByName(self.helogroupname)
if self.helo and self.helo:IsAlive()then
self.helo:StartUncontrolled()
delay=60
else
self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname))
return
end
else
self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable)
if self.takeoff==SPAWN.Takeoff.Runway then
delay=5
elseif self.takeoff==SPAWN.Takeoff.Hot then
delay=30
elseif self.takeoff==SPAWN.Takeoff.Cold then
delay=60
end
end
end
self.followset=SET_GROUP:New()
self.followset:AddGroup(self.helo)
self.HeloFuel0=self.helo:GetFuel()
self.formation=AI_FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier","Follow Carrier at given parameters.")
self.formation:FormationCenterWing(-self.offsetX,50,math.abs(self.altitude),50,self.offsetZ,50)
self.formation:SetFollowTimeInterval(self.dtFollow)
self.formation:SetFlightModeFormation(self.helo)
self.formation:__Start(delay)
self:__Status(1)
end
function RESCUEHELO:onafterStatus(From,Event,To)
local time=timer.getTime()
if self.helo and self.helo:IsAlive()then
local fuel=self.helo:GetFuel()*100
local fuelrel=fuel/self.HeloFuel0
local life=self.helo:GetUnit(1):GetLife()
local life0=self.helo:GetUnit(1):GetLife0()
local lifeR=self.helo:GetUnit(1):GetLifeRelative()
local text=string.format("Rescue Helo %s: state=%s fuel=%.1f, rel.fuel=%.1f, life=%.1f/%.1f=%d",self.helo:GetName(),self:GetState(),fuel,fuelrel,life,life0,lifeR*100)
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
if self:IsRunning()then
if fuel<self.lowfuel then
if self.takeoff==SPAWN.Takeoff.Air or self.respawninair then
if self.respawn then
self.helo:InitModex(self.modex)
self.helo=self.helo:Respawn(nil,true)
end
else
self:RTB(self.airbase)
end
end
elseif self:IsRescuing()then
end
if not self:IsStopped()then
self:__Status(-30)
end
else
if not self:IsStopped()then
self:E(self.lid.."Rescue helo is NOT alive (and not stopped)!")
self:Stop()
if self.respawn then
self:__Start(5)
end
end
end
end
function RESCUEHELO:onafterRun(From,Event,To)
if self.formation:Is("Stopped")then
local text=string.format("Restarting formation of rescue helo %s.",self.helo:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self.formation:Start()
end
end
function RESCUEHELO:_TaskRTB()
local carriername=self.carrier:GetName()
local DCSScript={}
DCSScript[#DCSScript+1]=string.format('local mycarrier = UNIT:FindByName(\"%s\") ',carriername)
DCSScript[#DCSScript+1]=string.format('local myhelo    = mycarrier:GetState(mycarrier, \"RESCUEHELO_%d\") ',self.uid)
DCSScript[#DCSScript+1]=string.format('myhelo:RTB()')
local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript)))
return DCSTask
end
function RESCUEHELO:onafterRescue(From,Event,To,RescueCoord)
local text=string.format("Helo %s is send to rescue mission.",self.helo:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text)
local wp={}
local RescueTask={}
RescueTask.id="ControlledTask"
RescueTask.params={}
RescueTask.params.task=self.helo:TaskOrbit(RescueCoord,20,self.rescuespeed)
RescueTask.params.stopCondition={duration=self.rescueduration}
local TaskRTB=self:_TaskRTB()
local speed=self.helo:GetSpeedMax()*0.9
wp[1]=self.helo:GetCoordinate():WaypointAirTurningPoint(nil,speed,{},"Current Position")
wp[2]=RescueCoord:SetAltitude(50):WaypointAirTurningPoint(nil,speed,{RescueTask,TaskRTB},"Crash Site")
wp[3]=self.airbase:GetCoordinate():SetAltitude(50):WaypointAirTurningPoint(nil,speed,{},"RTB")
self.helo:WayPointInitialize(wp)
self.helo:Route(wp,1)
self.formation:Stop()
end
function RESCUEHELO:onafterRTB(From,Event,To,airbase)
airbase=airbase or self.airbase
local text=string.format("Rescue helo %s is returning to airbase %s.",self.helo:GetName(),airbase:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
if From=="Running"then
self.formation:Stop()
end
self:RouteRTB(airbase)
end
function RESCUEHELO:onafterReturned(From,Event,To,airbase)
if airbase then
local airbasename=airbase:GetName()
self:I(self.lid..string.format("Helo returned to airbase %s",tostring(airbasename)))
else
self:E(self.lid..string.format("WARNING: Helo landed but airbase (EventData.Place) is nil!"))
end
if self.respawn then
self.helo:InitModex(self.modex)
if self.helo and self.helo:IsAlive()then
self:ScheduleOnce(5,self.helo.RespawnAtCurrentAirbase,self.helo)
end
self:__Run(10)
end
end
function RESCUEHELO:onafterStop(From,Event,To)
self.formation:Stop()
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Ejection)
self.CallScheduler:Clear()
if self.helo and self.helo:IsAlive()then
self:I(self.lid.."Stopping FSM and despawning helo.")
self.helo:Destroy()
else
self:I(self.lid.."Stopping FSM. Helo was not alive.")
end
end
function RESCUEHELO:RouteRTB(RTBAirbase,Speed)
local Speed=Speed or self.helo:GetSpeedMax()*0.8
local coord=self.helo:GetCoordinate()
local PointFrom=coord:WaypointAirTurningPoint(nil,Speed,{},"Current")
local PointLanding=RTBAirbase:GetCoordinate():SetAltitude(20):WaypointAirLanding(Speed,RTBAirbase,{},"Landing")
local Points={PointFrom,PointLanding}
local Template=self.helo:GetTemplate()
Template.route.points=Points
self.helo:InitModex(self.modex)
self.helo=self.helo:Respawn(Template,true)
self.helo:Route(Points,1)
return self
end
ATIS={
ClassName="ATIS",
lid=nil,
theatre=nil,
airbasename=nil,
airbase=nil,
frequency=nil,
modulation=nil,
power=nil,
radioqueue=nil,
soundpath=nil,
relayunitname=nil,
towerfrequency=nil,
activerunway=nil,
subduration=nil,
metric=nil,
PmmHg=nil,
qnhonly=false,
TDegF=nil,
zuludiff=nil,
zulutimeonly=false,
magvar=nil,
ils={},
ndbinner={},
ndbouter={},
vor=nil,
tacan=nil,
rsbn=nil,
prmg={},
rwylength=nil,
elevation=nil,
runwaymag={},
runwaym2t=nil,
windtrue=nil,
altimeterQNH=nil,
usemarker=nil,
markerid=nil,
relHumidity=nil,
ReportmBar=false,
TransmitOnlyWithPlayers=false,
ATISforFARPs=false,
}
ATIS.Alphabet={
[1]="Alfa",
[2]="Bravo",
[3]="Charlie",
[4]="Delta",
[5]="Echo",
[6]="Delta",
[7]="Echo",
[8]="Foxtrot",
[9]="Golf",
[10]="Hotel",
[11]="India",
[12]="Juliett",
[13]="Kilo",
[14]="Lima",
[15]="Mike",
[16]="November",
[17]="Oscar",
[18]="Papa",
[19]="Quebec",
[20]="Romeo",
[21]="Sierra",
[22]="Tango",
[23]="Uniform",
[24]="Victor",
[25]="Whiskey",
[26]="Xray",
[27]="Yankee",
[28]="Zulu",
}
ATIS.RunwayM2T={
Caucasus=0,
Nevada=12,
Normandy=-10,
PersianGulf=2,
TheChannel=-10,
Syria=5,
MarianaIslands=2,
}
ATIS.ICAOPhraseology={
Caucasus=true,
Nevada=false,
Normandy=true,
PersianGulf=true,
TheChannel=true,
Syria=true,
MarianaIslands=true
}
ATIS.Sound={
ActiveRunway={filename="ActiveRunway.ogg",duration=0.99},
AdviceOnInitial={filename="AdviceOnInitial.ogg",duration=3.00},
Airport={filename="Airport.ogg",duration=0.66},
Altimeter={filename="Altimeter.ogg",duration=0.68},
At={filename="At.ogg",duration=0.41},
CloudBase={filename="CloudBase.ogg",duration=0.82},
CloudCeiling={filename="CloudCeiling.ogg",duration=0.61},
CloudsBroken={filename="CloudsBroken.ogg",duration=1.07},
CloudsFew={filename="CloudsFew.ogg",duration=0.99},
CloudsNo={filename="CloudsNo.ogg",duration=1.01},
CloudsNotAvailable={filename="CloudsNotAvailable.ogg",duration=2.35},
CloudsOvercast={filename="CloudsOvercast.ogg",duration=0.83},
CloudsScattered={filename="CloudsScattered.ogg",duration=1.18},
Decimal={filename="Decimal.ogg",duration=0.54},
DegreesCelsius={filename="DegreesCelsius.ogg",duration=1.27},
DegreesFahrenheit={filename="DegreesFahrenheit.ogg",duration=1.23},
DewPoint={filename="DewPoint.ogg",duration=0.65},
Dust={filename="Dust.ogg",duration=0.54},
Elevation={filename="Elevation.ogg",duration=0.78},
EndOfInformation={filename="EndOfInformation.ogg",duration=1.15},
Feet={filename="Feet.ogg",duration=0.45},
Fog={filename="Fog.ogg",duration=0.47},
Gusting={filename="Gusting.ogg",duration=0.55},
HectoPascal={filename="HectoPascal.ogg",duration=1.15},
Hundred={filename="Hundred.ogg",duration=0.47},
InchesOfMercury={filename="InchesOfMercury.ogg",duration=1.16},
Information={filename="Information.ogg",duration=0.85},
Kilometers={filename="Kilometers.ogg",duration=0.78},
Knots={filename="Knots.ogg",duration=0.59},
Left={filename="Left.ogg",duration=0.54},
MegaHertz={filename="MegaHertz.ogg",duration=0.87},
Meters={filename="Meters.ogg",duration=0.59},
MetersPerSecond={filename="MetersPerSecond.ogg",duration=1.14},
Miles={filename="Miles.ogg",duration=0.60},
MillimetersOfMercury={filename="MillimetersOfMercury.ogg",duration=1.53},
Minus={filename="Minus.ogg",duration=0.64},
N0={filename="N-0.ogg",duration=0.55},
N1={filename="N-1.ogg",duration=0.41},
N2={filename="N-2.ogg",duration=0.37},
N3={filename="N-3.ogg",duration=0.41},
N4={filename="N-4.ogg",duration=0.37},
N5={filename="N-5.ogg",duration=0.43},
N6={filename="N-6.ogg",duration=0.55},
N7={filename="N-7.ogg",duration=0.43},
N8={filename="N-8.ogg",duration=0.38},
N9={filename="N-9.ogg",duration=0.55},
NauticalMiles={filename="NauticalMiles.ogg",duration=1.04},
None={filename="None.ogg",duration=0.43},
QFE={filename="QFE.ogg",duration=0.63},
QNH={filename="QNH.ogg",duration=0.71},
Rain={filename="Rain.ogg",duration=0.41},
Right={filename="Right.ogg",duration=0.44},
Snow={filename="Snow.ogg",duration=0.48},
SnowStorm={filename="SnowStorm.ogg",duration=0.82},
StatuteMiles={filename="StatuteMiles.ogg",duration=1.15},
SunriseAt={filename="SunriseAt.ogg",duration=0.92},
SunsetAt={filename="SunsetAt.ogg",duration=0.95},
Temperature={filename="Temperature.ogg",duration=0.64},
Thousand={filename="Thousand.ogg",duration=0.55},
ThunderStorm={filename="ThunderStorm.ogg",duration=0.81},
TimeLocal={filename="TimeLocal.ogg",duration=0.90},
TimeZulu={filename="TimeZulu.ogg",duration=0.86},
TowerFrequency={filename="TowerFrequency.ogg",duration=1.19},
Visibilty={filename="Visibility.ogg",duration=0.79},
WeatherPhenomena={filename="WeatherPhenomena.ogg",duration=1.07},
WindFrom={filename="WindFrom.ogg",duration=0.60},
ILSFrequency={filename="ILSFrequency.ogg",duration=1.30},
InnerNDBFrequency={filename="InnerNDBFrequency.ogg",duration=1.56},
OuterNDBFrequency={filename="OuterNDBFrequency.ogg",duration=1.59},
RunwayLength={filename="RunwayLength.ogg",duration=0.91},
VORFrequency={filename="VORFrequency.ogg",duration=1.38},
TACANChannel={filename="TACANChannel.ogg",duration=0.88},
PRMGChannel={filename="PRMGChannel.ogg",duration=1.18},
RSBNChannel={filename="RSBNChannel.ogg",duration=1.14},
Zulu={filename="Zulu.ogg",duration=0.62},
}
_ATIS={}
ATIS.version="0.9.14"
function ATIS:New(AirbaseName,Frequency,Modulation)
local self=BASE:Inherit(self,FSM:New())
self.airbasename=AirbaseName
self.airbase=AIRBASE:FindByName(AirbaseName)
if self.airbase==nil then
self:E("ERROR: Airbase %s for ATIS could not be found!",tostring(AirbaseName))
return nil
end
self.frequency=Frequency or 143.00
self.modulation=Modulation or 0
self.theatre=env.mission.theatre
self.lid=string.format("ATIS %s | ",self.airbasename)
_ATIS[#_ATIS+1]=self
self:SetSoundfilesPath()
self:SetSubtitleDuration()
self:SetMagneticDeclination()
self:SetRunwayCorrectionMagnetic2True()
self:SetRadioPower()
self:SetAltimeterQNH(true)
self:SetMapMarks(false)
self:SetRelativeHumidity()
self:SetQueueUpdateTime()
self:SetReportmBar(false)
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","Broadcast","*")
self:AddTransition("*","CheckQueue","*")
self:AddTransition("*","Report","*")
self:AddTransition("*","Stop","Stopped")
return self
end
function ATIS:SetSoundfilesPath(path)
self.soundpath=tostring(path or"ATIS Soundfiles/")
self:T(self.lid..string.format("Setting sound files path to %s",self.soundpath))
return self
end
function ATIS:SetRadioRelayUnitName(unitname)
self.relayunitname=unitname
self:T(self.lid..string.format("Setting radio relay unit to %s",self.relayunitname))
return self
end
function ATIS:SetTowerFrequencies(freqs)
if type(freqs)=="table"then
else
freqs={freqs}
end
self.towerfrequency=freqs
return self
end
function ATIS:SetTransmitOnlyWithPlayers(Switch)
self.TransmitOnlyWithPlayers=Switch
if self.msrsQ then
self.msrsQ:SetTransmitOnlyWithPlayers(Switch)
end
return self
end
function ATIS:SetActiveRunway(runway)
self.activerunway=tostring(runway)
local prefer=nil
if string.find(string.lower(runway),"l")then
prefer=true
elseif string.find(string.lower(runway),"r")then
prefer=false
end
self.airbase:SetActiveRunway(runway,prefer)
return self
end
function ATIS:SetActiveRunwayLanding(runway,preferleft)
self.airbase:SetActiveRunwayLanding(runway,preferleft)
return self
end
function ATIS:SetActiveRunwayTakeoff(runway,preferleft)
self.airbase:SetActiveRunwayTakeoff(runway,preferleft)
return self
end
function ATIS:SetRunwayLength()
self.rwylength=true
return self
end
function ATIS:SetRunwayLength()
self.rwylength=true
return self
end
function ATIS:SetElevation()
self.elevation=true
return self
end
function ATIS:SetRadioPower(power)
self.power=power or 100
return self
end
function ATIS:SetMapMarks(switch)
if switch==nil or switch==true then
self.usemarker=true
else
self.usemarker=false
end
return self
end
function ATIS:GetSRSText()
return self.SRSText
end
function ATIS:SetRunwayHeadingsMagnetic(headings)
if type(headings)=="table"then
else
headings={headings}
end
for _,heading in pairs(headings)do
if type(heading)=="number"then
heading=string.format("%02d",heading)
end
self:T(self.lid..string.format("Adding user specified magnetic runway heading %s",heading))
table.insert(self.runwaymag,heading)
local h=self:GetRunwayWithoutLR(heading)
local head2=tonumber(h)-18
if head2<0 then
head2=head2+36
end
head2=string.format("%02d",head2)
local left=self:GetRunwayLR(heading)
if left==true then
head2=head2.."L"
elseif left==false then
head2=head2.."R"
end
self:T(self.lid..string.format("Adding user specified magnetic runway heading %s (inverse)",head2))
table.insert(self.runwaymag,head2)
end
return self
end
function ATIS:SetSubtitleDuration(duration)
self.subduration=tonumber(duration or 10)
return self
end
function ATIS:SetMetricUnits()
self.metric=true
return self
end
function ATIS:SetImperialUnits()
self.metric=false
return self
end
function ATIS:SetPressureMillimetersMercury()
self.PmmHg=true
return self
end
function ATIS:SetTemperatureFahrenheit()
self.TDegF=true
return self
end
function ATIS:SetRelativeHumidity(Humidity)
self.relHumidity=Humidity or 50
return self
end
function ATIS:SetAltimeterQNH(switch)
if switch==true or switch==nil then
self.altimeterQNH=true
else
self.altimeterQNH=false
end
return self
end
function ATIS:SetReportmBar(switch)
if switch==true or switch==nil then
self.ReportmBar=true
else
self.ReportmBar=false
end
return self
end
function ATIS:SetAdditionalInformation(text)
self.AdditionalInformation=text
return self
end
function ATIS:ReportQNHOnly()
self.qnhonly=true
return self
end
function ATIS:SetMagneticDeclination(magvar)
self.magvar=magvar or UTILS.GetMagneticDeclination()
return self
end
function ATIS:SetRunwayCorrectionMagnetic2True(correction)
self.runwaym2t=correction or ATIS.RunwayM2T[UTILS.GetDCSMap()]
return self
end
function ATIS:SetReportWindTrue()
self.windtrue=true
return self
end
function ATIS:SetZuluTimeDifference(delta)
self.zuludiff=delta
return self
end
function ATIS:ReportZuluTimeOnly()
self.zulutimeonly=true
return self
end
function ATIS:AddILS(frequency,runway)
local ils={}
ils.frequency=tonumber(frequency)
ils.runway=runway and tostring(runway)or nil
table.insert(self.ils,ils)
return self
end
function ATIS:SetVOR(frequency)
self.vor=frequency
return self
end
function ATIS:AddNDBouter(frequency,runway)
local ndb={}
ndb.frequency=tonumber(frequency)
ndb.runway=runway and tostring(runway)or nil
table.insert(self.ndbouter,ndb)
return self
end
function ATIS:AddNDBinner(frequency,runway)
local ndb={}
ndb.frequency=tonumber(frequency)
ndb.runway=runway and tostring(runway)or nil
table.insert(self.ndbinner,ndb)
return self
end
function ATIS:SetTACAN(channel)
self.tacan=channel
return self
end
function ATIS:SetRSBN(channel)
self.rsbn=channel
return self
end
function ATIS:AddPRMG(channel,runway)
local ndb={}
ndb.frequency=tonumber(channel)
ndb.runway=runway and tostring(runway)or nil
table.insert(self.prmg,ndb)
return self
end
function ATIS:MarkRunways(markall)
local airbases=AIRBASE.GetAllAirbases()
for _,_airbase in pairs(airbases)do
local airbase=_airbase
if(not markall and airbase:GetName()==self.airbasename)or markall==true then
airbase:GetRunwayData(self.runwaym2t,true)
end
end
end
function ATIS:SetSRS(PathToSRS,Gender,Culture,Voice,Port,GoogleKey)
if PathToSRS then
self.useSRS=true
self.msrs=MSRS:New(PathToSRS,self.frequency,self.modulation)
self.msrs:SetGender(Gender)
self.msrs:SetCulture(Culture)
self.msrs:SetVoice(Voice)
self.msrs:SetPort(Port)
self.msrs:SetCoalition(self:GetCoalition())
self.msrs:SetLabel("ATIS")
self.msrs:SetGoogle(GoogleKey)
self.msrsQ=MSRSQUEUE:New("ATIS")
self.msrsQ:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers)
if self.dTQueueCheck<=10 then
self:SetQueueUpdateTime(90)
end
else
self:E(self.lid..string.format("ERROR: No SRS path specified!"))
end
return self
end
function ATIS:SetQueueUpdateTime(TimeInterval)
self.dTQueueCheck=TimeInterval or 5
end
function ATIS:GetCoalition()
local coal=self.airbase and self.airbase:GetCoalition()or nil
return coal
end
function ATIS:onafterStart(From,Event,To)
if self.airbase:GetAirbaseCategory()==Airbase.Category.SHIP then
self:E(self.lid..string.format("ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT SHIPS.",self.airbasename))
return
end
if self.airbase:GetAirbaseCategory()==Airbase.Category.HELIPAD then
self:E(self.lid..string.format("EXPERIMENTAL: Starting ATIS for Helipad %s! SRS must be ON",self.airbasename))
self.ATISforFARPs=true
self.useSRS=true
end
self:I(self.lid..string.format("Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d",ATIS.version,self.airbasename,self.frequency,self.modulation))
if not self.useSRS then
self.radioqueue=RADIOQUEUE:New(self.frequency,self.modulation,string.format("ATIS %s",self.airbasename))
self.radioqueue:SetSenderCoordinate(self.airbase:GetCoordinate())
self.radioqueue:SetSenderUnitName(self.relayunitname)
self.radioqueue:SetRadioPower(self.power)
self.radioqueue:SetDigit(0,ATIS.Sound.N0.filename,ATIS.Sound.N0.duration,self.soundpath)
self.radioqueue:SetDigit(1,ATIS.Sound.N1.filename,ATIS.Sound.N1.duration,self.soundpath)
self.radioqueue:SetDigit(2,ATIS.Sound.N2.filename,ATIS.Sound.N2.duration,self.soundpath)
self.radioqueue:SetDigit(3,ATIS.Sound.N3.filename,ATIS.Sound.N3.duration,self.soundpath)
self.radioqueue:SetDigit(4,ATIS.Sound.N4.filename,ATIS.Sound.N4.duration,self.soundpath)
self.radioqueue:SetDigit(5,ATIS.Sound.N5.filename,ATIS.Sound.N5.duration,self.soundpath)
self.radioqueue:SetDigit(6,ATIS.Sound.N6.filename,ATIS.Sound.N6.duration,self.soundpath)
self.radioqueue:SetDigit(7,ATIS.Sound.N7.filename,ATIS.Sound.N7.duration,self.soundpath)
self.radioqueue:SetDigit(8,ATIS.Sound.N8.filename,ATIS.Sound.N8.duration,self.soundpath)
self.radioqueue:SetDigit(9,ATIS.Sound.N9.filename,ATIS.Sound.N9.duration,self.soundpath)
self.radioqueue:Start(1,0.1)
end
self:HandleEvent(EVENTS.BaseCaptured)
self:__Status(-2)
self:__CheckQueue(-3)
end
function ATIS:onafterStatus(From,Event,To)
local fsmstate=self:GetState()
local relayunitstatus="N/A"
if self.relayunitname then
local ru=UNIT:FindByName(self.relayunitname)
if ru then
relayunitstatus=tostring(ru:IsAlive())
end
end
local text=string.format("State %s: Freq=%.3f MHz %s",fsmstate,self.frequency,UTILS.GetModulationName(self.modulation))
if self.useSRS then
text=text..string.format(", SRS path=%s (%s), gender=%s, culture=%s, voice=%s",tostring(self.msrs.path),tostring(self.msrs.port),tostring(self.msrs.gender),tostring(self.msrs.culture),tostring(self.msrs.voice))
else
text=text..string.format(", Relay unit=%s (alive=%s)",tostring(self.relayunitname),relayunitstatus)
end
self:T(self.lid..text)
if not self:Is("Stopped")then
self:__Status(-60)
end
end
function ATIS:onafterCheckQueue(From,Event,To)
if self.useSRS then
self:Broadcast()
else
if#self.radioqueue.queue==0 then
self:T(self.lid..string.format("Radio queue empty. Repeating message."))
self:Broadcast()
else
self:T2(self.lid..string.format("Radio queue %d transmissions queued.",#self.radioqueue.queue))
end
end
if not self:Is("Stopped")then
self:__CheckQueue(-math.abs(self.dTQueueCheck))
end
end
function ATIS:onafterBroadcast(From,Event,To)
local coord=self.airbase:GetCoordinate()
local height=coord:GetLandHeight()
local qfe=coord:GetPressure(height)
local qnh=coord:GetPressure(0)
if self.altimeterQNH then
local L=-0.0065
local R=8.31446
local g=9.80665
local M=0.0289644
local T0=coord:GetTemperature(0)+273.15
local TS=288.15
local q=qnh*100
local P=q*(1+L*height/T0)^(-g*M/(R*L))
local Q=P/(1+L*height/TS)^(-g*M/(R*L))
local A=(T0/L)*((P/q)^(((-R*L)/(g*M)))-1)
self:T2(self.lid..string.format("height=%.1f, A=%.1f, T0=%.1f, QFE=%.1f, QNH=%.1f, P=%.1f, Q=%.1f hPa = %.2f",height,A,T0-273.15,qfe,qnh,P/100,Q/100,UTILS.hPa2inHg(Q/100)))
qnh=Q/100
end
local mBarqnh=qnh
local mBarqfe=qfe
if self.PmmHg then
qfe=UTILS.hPa2mmHg(qfe)
qnh=UTILS.hPa2mmHg(qnh)
else
if not self.metric then
qfe=UTILS.hPa2inHg(qfe)
qnh=UTILS.hPa2inHg(qnh)
end
end
local QFE=UTILS.Split(string.format("%.2f",qfe),".")
local QNH=UTILS.Split(string.format("%.2f",qnh),".")
if self.PmmHg then
QFE=UTILS.Split(string.format("%.1f",qfe),".")
QNH=UTILS.Split(string.format("%.1f",qnh),".")
else
if self.metric then
QFE=UTILS.Split(string.format("%.1f",qfe),".")
QNH=UTILS.Split(string.format("%.1f",qnh),".")
end
end
local windFrom,windSpeed=coord:GetWind(height+10)
local magvar=self.magvar
if self.windtrue then
magvar=0
end
windFrom=windFrom-magvar
if windFrom<0 then
windFrom=windFrom+360
end
local WINDFROM=string.format("%03d",windFrom)
local WINDSPEED=string.format("%d",UTILS.MpsToKnots(windSpeed))
if WINDFROM=="000"then
WINDFROM="360"
end
if self.metric then
WINDSPEED=string.format("%d",windSpeed)
end
local runwayLanding,rwyLandingLeft
local runwayTakeoff,rwyTakeoffLeft
if self.airbase:GetAirbaseCategory()==Airbase.Category.HELIPAD then
runwayLanding,rwyLandingLeft="PAD 01",false
runwayTakeoff,rwyTakeoffLeft="PAD 02",false
else
runwayLanding,rwyLandingLeft=self:GetActiveRunway()
runwayTakeoff,rwyTakeoffLeft=self:GetActiveRunway(true)
end
local time=timer.getAbsTime()
if self.zuludiff then
time=time-self.zuludiff*60*60
else
time=time-UTILS.GMTToLocalTimeDifference()*60*60
end
if time<0 then
time=24*60*60+time
end
local clock=UTILS.SecondsToClock(time)
local zulu=UTILS.Split(clock,":")
local ZULU=string.format("%s%s",zulu[1],zulu[2])
if self.useSRS then
ZULU=string.format("%s hours",zulu[1])
end
local NATO=ATIS.Alphabet[tonumber(zulu[1])+1]
self:T3(string.format("clock=%s",tostring(clock)))
self:T3(string.format("zulu1=%s",tostring(zulu[1])))
self:T3(string.format("zulu2=%s",tostring(zulu[2])))
self:T3(string.format("ZULU =%s",tostring(ZULU)))
self:T3(string.format("NATO =%s",tostring(NATO)))
local sunrise=coord:GetSunrise()
sunrise=UTILS.Split(sunrise,":")
local SUNRISE=string.format("%s%s",sunrise[1],sunrise[2])
if self.useSRS then
SUNRISE=string.format("%s %s hours",sunrise[1],sunrise[2])
end
local sunset=coord:GetSunset()
sunset=UTILS.Split(sunset,":")
local SUNSET=string.format("%s%s",sunset[1],sunset[2])
if self.useSRS then
SUNSET=string.format("%s %s hours",sunset[1],sunset[2])
end
local temperature=coord:GetTemperature(height+5)
local dewpoint=temperature-(100-self.relHumidity)/5
if self.TDegF then
temperature=UTILS.CelsiusToFahrenheit(temperature)
dewpoint=UTILS.CelsiusToFahrenheit(dewpoint)
end
local TEMPERATURE=string.format("%d",math.abs(temperature))
local DEWPOINT=string.format("%d",math.abs(dewpoint))
local clouds,visibility,turbulence,fog,dust,static=self:GetMissionWeather()
if fog and fog.thickness<height+25 then
fog=nil
end
if dust and height+25>UTILS.FeetToMeters(1500)then
dust=nil
end
local visibilitymin=visibility
if fog then
if fog.visibility<visibilitymin then
visibilitymin=fog.visibility
end
end
if dust then
if dust<visibilitymin then
visibilitymin=dust
end
end
local VISIBILITY=""
if self.metric then
local reportedviz=UTILS.Round(visibilitymin/1000)
if reportedviz>10 then
reportedviz=10
end
VISIBILITY=string.format("%d",reportedviz)
else
local reportedviz=UTILS.Round(UTILS.MetersToSM(visibilitymin))
if reportedviz>10 then
reportedviz=10
end
VISIBILITY=string.format("%d",reportedviz)
end
local cloudbase=clouds.base
local cloudceil=clouds.base+clouds.thickness
local clouddens=clouds.density
local cloudspreset=clouds.preset or"Nothing"
local precepitation=0
if cloudspreset:find("Preset10")then
clouddens=4
elseif cloudspreset:find("Preset11")then
clouddens=4
elseif cloudspreset:find("Preset12")then
clouddens=4
elseif cloudspreset:find("Preset13")then
clouddens=7
elseif cloudspreset:find("Preset14")then
clouddens=7
elseif cloudspreset:find("Preset15")then
clouddens=7
elseif cloudspreset:find("Preset16")then
clouddens=7
elseif cloudspreset:find("Preset17")then
clouddens=7
elseif cloudspreset:find("Preset18")then
clouddens=7
elseif cloudspreset:find("Preset19")then
clouddens=7
elseif cloudspreset:find("Preset20")then
clouddens=7
elseif cloudspreset:find("Preset21")then
clouddens=9
elseif cloudspreset:find("Preset22")then
clouddens=9
elseif cloudspreset:find("Preset23")then
clouddens=9
elseif cloudspreset:find("Preset24")then
clouddens=9
elseif cloudspreset:find("Preset25")then
clouddens=9
elseif cloudspreset:find("Preset26")then
clouddens=9
elseif cloudspreset:find("Preset27")then
clouddens=9
elseif cloudspreset:find("Preset1")then
clouddens=1
elseif cloudspreset:find("Preset2")then
clouddens=1
elseif cloudspreset:find("Preset3")then
clouddens=4
elseif cloudspreset:find("Preset4")then
clouddens=4
elseif cloudspreset:find("Preset5")then
clouddens=4
elseif cloudspreset:find("Preset6")then
clouddens=4
elseif cloudspreset:find("Preset7")then
clouddens=4
elseif cloudspreset:find("Preset8")then
clouddens=4
elseif cloudspreset:find("Preset9")then
clouddens=4
elseif cloudspreset:find("RainyPreset")then
clouddens=9
if temperature>5 then
precepitation=1
else
precepitation=3
end
elseif cloudspreset:find("RainyPreset1")then
clouddens=9
if temperature>5 then
precepitation=1
else
precepitation=3
end
elseif cloudspreset:find("RainyPreset2")then
clouddens=9
if temperature>5 then
precepitation=1
else
precepitation=3
end
elseif cloudspreset:find("RainyPreset3")then
clouddens=9
if temperature>5 then
precepitation=1
else
precepitation=3
end
end
local CLOUDBASE=string.format("%d",UTILS.MetersToFeet(cloudbase))
local CLOUDCEIL=string.format("%d",UTILS.MetersToFeet(cloudceil))
if self.metric then
CLOUDBASE=string.format("%d",cloudbase)
CLOUDCEIL=string.format("%d",cloudceil)
end
local CLOUDBASE1000,CLOUDBASE0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudbase))
local CLOUDCEIL1000,CLOUDCEIL0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudceil))
if self.metric then
CLOUDBASE1000,CLOUDBASE0100=self:_GetThousandsAndHundreds(cloudbase)
CLOUDCEIL1000,CLOUDCEIL0100=self:_GetThousandsAndHundreds(cloudceil)
end
local CloudCover={}
CloudCover=ATIS.Sound.CloudsNotAvailable
local CLOUDSsub="Cloud coverage information not available"
if static then
if clouddens>=9 then
CloudCover=ATIS.Sound.CloudsOvercast
CLOUDSsub="Overcast"
elseif clouddens>=7 then
CloudCover=ATIS.Sound.CloudsBroken
CLOUDSsub="Broken clouds"
elseif clouddens>=4 then
CloudCover=ATIS.Sound.CloudsScattered
CLOUDSsub="Scattered clouds"
elseif clouddens>=1 then
CloudCover=ATIS.Sound.CloudsFew
CLOUDSsub="Few clouds"
else
CLOUDBASE=nil
CLOUDCEIL=nil
CloudCover=ATIS.Sound.CloudsNo
CLOUDSsub="No clouds"
end
end
local subtitle=""
subtitle=string.format("%s",self.airbasename)
if(not self.ATISforFARPs)and self.airbasename:find("AFB")==nil and self.airbasename:find("Airport")==nil and self.airbasename:find("Airstrip")==nil and self.airbasename:find("airfield")==nil and self.airbasename:find("AB")==nil then
subtitle=subtitle.." Airport"
end
if not self.useSRS then
self.radioqueue:NewTransmission(string.format("%s/%s.ogg",self.theatre,self.airbasename),3.0,self.soundpath,nil,nil,subtitle,self.subduration)
end
local alltext=subtitle
subtitle=string.format("Information %s",NATO)
local _INFORMATION=subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.Information,0.5,subtitle)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg",NATO),0.75,self.soundpath)
end
alltext=alltext..";\n"..subtitle
subtitle=string.format("%s Zulu",ZULU)
if not self.useSRS then
self.radioqueue:Number2Transmission(ZULU,nil,0.5)
self:Transmission(ATIS.Sound.Zulu,0.2,subtitle)
end
alltext=alltext..";\n"..subtitle
if not self.zulutimeonly then
subtitle=string.format("Sunrise at %s local time",SUNRISE)
if not self.useSRS then
self:Transmission(ATIS.Sound.SunriseAt,0.5,subtitle)
self.radioqueue:Number2Transmission(SUNRISE,nil,0.2)
self:Transmission(ATIS.Sound.TimeLocal,0.2)
end
alltext=alltext..";\n"..subtitle
subtitle=string.format("Sunset at %s local time",SUNSET)
if not self.useSRS then
self:Transmission(ATIS.Sound.SunsetAt,0.5,subtitle)
self.radioqueue:Number2Transmission(SUNSET,nil,0.5)
self:Transmission(ATIS.Sound.TimeLocal,0.2)
end
alltext=alltext..";\n"..subtitle
end
if self.useSRS then
WINDFROM=string.gsub(WINDFROM,".","%1 ")
end
if self.metric then
subtitle=string.format("Wind from %s at %s m/s",WINDFROM,WINDSPEED)
else
subtitle=string.format("Wind from %s at %s knots",WINDFROM,WINDSPEED)
end
if turbulence>0 then
subtitle=subtitle..", gusting"
end
local _WIND=subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.WindFrom,1.0,subtitle)
self.radioqueue:Number2Transmission(WINDFROM)
self:Transmission(ATIS.Sound.At,0.2)
self.radioqueue:Number2Transmission(WINDSPEED)
if self.metric then
self:Transmission(ATIS.Sound.MetersPerSecond,0.2)
else
self:Transmission(ATIS.Sound.Knots,0.2)
end
if turbulence>0 then
self:Transmission(ATIS.Sound.Gusting,0.2)
end
end
alltext=alltext..";\n"..subtitle
if self.metric then
subtitle=string.format("Visibility %s km",VISIBILITY)
else
subtitle=string.format("Visibility %s SM",VISIBILITY)
end
if not self.useSRS then
self:Transmission(ATIS.Sound.Visibilty,1.0,subtitle)
self.radioqueue:Number2Transmission(VISIBILITY)
if self.metric then
self:Transmission(ATIS.Sound.Kilometers,0.2)
else
self:Transmission(ATIS.Sound.StatuteMiles,0.2)
end
end
alltext=alltext..";\n"..subtitle
subtitle=""
local wp=false
local wpsub=""
if precepitation==1 then
wp=true
wpsub=wpsub.." rain"
elseif precepitation==2 then
if wp then
wpsub=wpsub..","
end
wpsub=wpsub.." thunderstorm"
wp=true
elseif precepitation==3 then
wpsub=wpsub.." snow"
wp=true
elseif precepitation==4 then
wpsub=wpsub.." snowstorm"
wp=true
end
if fog then
if wp then
wpsub=wpsub..","
end
wpsub=wpsub.." fog"
wp=true
end
if dust then
if wp then
wpsub=wpsub..","
end
wpsub=wpsub.." dust"
wp=true
end
if wp then
subtitle=string.format("Weather phenomena:%s",wpsub)
if not self.useSRS then
self:Transmission(ATIS.Sound.WeatherPhenomena,1.0,subtitle)
if precepitation==1 then
self:Transmission(ATIS.Sound.Rain,0.5)
elseif precepitation==2 then
self:Transmission(ATIS.Sound.ThunderStorm,0.5)
elseif precepitation==3 then
self:Transmission(ATIS.Sound.Snow,0.5)
elseif precepitation==4 then
self:Transmission(ATIS.Sound.SnowStorm,0.5)
end
if fog then
self:Transmission(ATIS.Sound.Fog,0.5)
end
if dust then
self:Transmission(ATIS.Sound.Dust,0.5)
end
end
alltext=alltext..";\n"..subtitle
end
if not self.useSRS then
self:Transmission(CloudCover,1.0,CLOUDSsub)
end
if CLOUDBASE and static then
local cbase=tostring(tonumber(CLOUDBASE1000)*1000+tonumber(CLOUDBASE0100)*100)
local cceil=tostring(tonumber(CLOUDCEIL1000)*1000+tonumber(CLOUDCEIL0100)*100)
if self.metric then
subtitle=string.format("Cloud base %s, ceiling %s meters",cbase,cceil)
else
subtitle=string.format("Cloud base %s, ceiling %s feet",cbase,cceil)
end
if not self.useSRS then
self:Transmission(ATIS.Sound.CloudBase,1.0,subtitle)
if tonumber(CLOUDBASE1000)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE1000)
self:Transmission(ATIS.Sound.Thousand,0.1)
end
if tonumber(CLOUDBASE0100)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE0100)
self:Transmission(ATIS.Sound.Hundred,0.1)
end
self:Transmission(ATIS.Sound.CloudCeiling,0.5)
if tonumber(CLOUDCEIL1000)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL1000)
self:Transmission(ATIS.Sound.Thousand,0.1)
end
if tonumber(CLOUDCEIL0100)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL0100)
self:Transmission(ATIS.Sound.Hundred,0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters,0.1)
else
self:Transmission(ATIS.Sound.Feet,0.1)
end
end
end
alltext=alltext..";\n"..subtitle
subtitle=""
if self.TDegF then
if temperature<0 then
subtitle=string.format("Temperature -%s °F",TEMPERATURE)
else
subtitle=string.format("Temperature %s °F",TEMPERATURE)
end
else
if temperature<0 then
subtitle=string.format("Temperature -%s °C",TEMPERATURE)
else
subtitle=string.format("Temperature %s °C",TEMPERATURE)
end
end
local _TEMPERATURE=subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.Temperature,1.0,subtitle)
if temperature<0 then
self:Transmission(ATIS.Sound.Minus,0.2)
end
self.radioqueue:Number2Transmission(TEMPERATURE)
if self.TDegF then
self:Transmission(ATIS.Sound.DegreesFahrenheit,0.2)
else
self:Transmission(ATIS.Sound.DegreesCelsius,0.2)
end
end
alltext=alltext..";\n"..subtitle
if self.TDegF then
if dewpoint<0 then
subtitle=string.format("Dew point -%s °F",DEWPOINT)
else
subtitle=string.format("Dew point %s °F",DEWPOINT)
end
else
if dewpoint<0 then
subtitle=string.format("Dew point -%s °C",DEWPOINT)
else
subtitle=string.format("Dew point %s °C",DEWPOINT)
end
end
local _DEWPOINT=subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.DewPoint,1.0,subtitle)
if dewpoint<0 then
self:Transmission(ATIS.Sound.Minus,0.2)
end
self.radioqueue:Number2Transmission(DEWPOINT)
if self.TDegF then
self:Transmission(ATIS.Sound.DegreesFahrenheit,0.2)
else
self:Transmission(ATIS.Sound.DegreesCelsius,0.2)
end
end
alltext=alltext..";\n"..subtitle
if self.PmmHg then
if self.qnhonly then
subtitle=string.format("Altimeter %s.%s mmHg",QNH[1],QNH[2])
else
subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s mmHg",QNH[1],QNH[2],QFE[1],QFE[2])
end
else
if self.metric then
if self.qnhonly then
subtitle=string.format("Altimeter %s.%s hPa",QNH[1],QNH[2])
else
subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s hPa",QNH[1],QNH[2],QFE[1],QFE[2])
end
else
if self.qnhonly then
subtitle=string.format("Altimeter %s.%s inHg",QNH[1],QNH[2])
else
subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s inHg",QNH[1],QNH[2],QFE[1],QFE[2])
end
end
end
if self.ReportmBar and not self.metric then
if self.qnhonly then
subtitle=string.format("%s;\nAltimeter %d hPa",subtitle,mBarqnh)
else
subtitle=string.format("%s;\nAltimeter: QNH %d, QFE %d hPa",subtitle,mBarqnh,mBarqfe)
end
end
local _ALTIMETER=subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.Altimeter,1.0,subtitle)
if not self.qnhonly then
self:Transmission(ATIS.Sound.QNH,0.5)
end
self.radioqueue:Number2Transmission(QNH[1])
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()]then
self:Transmission(ATIS.Sound.Decimal,0.2)
end
self.radioqueue:Number2Transmission(QNH[2])
if not self.qnhonly then
self:Transmission(ATIS.Sound.QFE,0.75)
self.radioqueue:Number2Transmission(QFE[1])
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()]then
self:Transmission(ATIS.Sound.Decimal,0.2)
end
self.radioqueue:Number2Transmission(QFE[2])
end
if self.PmmHg then
self:Transmission(ATIS.Sound.MillimetersOfMercury,0.1)
else
if self.metric then
self:Transmission(ATIS.Sound.HectoPascal,0.1)
else
self:Transmission(ATIS.Sound.InchesOfMercury,0.1)
end
end
end
alltext=alltext..";\n"..subtitle
if not self.ATISforFARPs then
local subtitle=string.format("Active runway %s",runwayLanding)
if rwyLandingLeft==true then
subtitle=subtitle.." Left"
elseif rwyLandingLeft==false then
subtitle=subtitle.." Right"
end
local _RUNACT=subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.ActiveRunway,1.0,subtitle)
self.radioqueue:Number2Transmission(runwayLanding)
if rwyLandingLeft==true then
self:Transmission(ATIS.Sound.Left,0.2)
elseif rwyLandingLeft==false then
self:Transmission(ATIS.Sound.Right,0.2)
end
end
alltext=alltext..";\n"..subtitle
if self.rwylength then
local runact=self.airbase:GetActiveRunway(self.runwaym2t)
local length=runact.length
if not self.metric then
length=UTILS.MetersToFeet(length)
end
local L1000,L0100=self:_GetThousandsAndHundreds(length)
local subtitle=string.format("Runway length %d",length)
if self.metric then
subtitle=subtitle.." meters"
else
subtitle=subtitle.." feet"
end
if not self.useSRS then
self:Transmission(ATIS.Sound.RunwayLength,1.0,subtitle)
if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000)
self:Transmission(ATIS.Sound.Thousand,0.1)
end
if tonumber(L0100)>0 then
self.radioqueue:Number2Transmission(L0100)
self:Transmission(ATIS.Sound.Hundred,0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters,0.1)
else
self:Transmission(ATIS.Sound.Feet,0.1)
end
end
alltext=alltext..";\n"..subtitle
end
end
if self.elevation then
local elevation=self.airbase:GetHeight()
if not self.metric then
elevation=UTILS.MetersToFeet(elevation)
end
local L1000,L0100=self:_GetThousandsAndHundreds(elevation)
local subtitle=string.format("Elevation %d",elevation)
if self.metric then
subtitle=subtitle.." meters"
else
subtitle=subtitle.." feet"
end
if not self.useSRS then
self:Transmission(ATIS.Sound.Elevation,1.0,subtitle)
if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000)
self:Transmission(ATIS.Sound.Thousand,0.1)
end
if tonumber(L0100)>0 then
self.radioqueue:Number2Transmission(L0100)
self:Transmission(ATIS.Sound.Hundred,0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters,0.1)
else
self:Transmission(ATIS.Sound.Feet,0.1)
end
end
alltext=alltext..";\n"..subtitle
end
if self.towerfrequency then
local freqs=""
for i,freq in pairs(self.towerfrequency)do
freqs=freqs..string.format("%.3f MHz",freq)
if i<#self.towerfrequency then
freqs=freqs..", "
end
end
subtitle=string.format("Tower frequency %s",freqs)
if not self.useSRS then
self:Transmission(ATIS.Sound.TowerFrequency,1.0,subtitle)
for _,freq in pairs(self.towerfrequency)do
local f=string.format("%.3f",freq)
f=UTILS.Split(f,".")
self.radioqueue:Number2Transmission(f[1],nil,0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal,0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz,0.2)
end
end
alltext=alltext..";\n"..subtitle
end
local ils=self:GetNavPoint(self.ils,runwayLanding,rwyLandingLeft)
if ils then
subtitle=string.format("ILS frequency %.2f MHz",ils.frequency)
if not self.useSRS then
self:Transmission(ATIS.Sound.ILSFrequency,1.0,subtitle)
local f=string.format("%.2f",ils.frequency)
f=UTILS.Split(f,".")
self.radioqueue:Number2Transmission(f[1],nil,0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal,0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz,0.2)
end
alltext=alltext..";\n"..subtitle
end
local ndb=self:GetNavPoint(self.ndbouter,runwayLanding,rwyLandingLeft)
if ndb then
subtitle=string.format("Outer NDB frequency %.2f MHz",ndb.frequency)
if not self.useSRS then
self:Transmission(ATIS.Sound.OuterNDBFrequency,1.0,subtitle)
local f=string.format("%.2f",ndb.frequency)
f=UTILS.Split(f,".")
self.radioqueue:Number2Transmission(f[1],nil,0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal,0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz,0.2)
end
alltext=alltext..";\n"..subtitle
end
local ndb=self:GetNavPoint(self.ndbinner,runwayLanding,rwyLandingLeft)
if ndb then
subtitle=string.format("Inner NDB frequency %.2f MHz",ndb.frequency)
if not self.useSRS then
self:Transmission(ATIS.Sound.InnerNDBFrequency,1.0,subtitle)
local f=string.format("%.2f",ndb.frequency)
f=UTILS.Split(f,".")
self.radioqueue:Number2Transmission(f[1],nil,0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal,0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz,0.2)
end
alltext=alltext..";\n"..subtitle
end
if self.vor then
subtitle=string.format("VOR frequency %.2f MHz",self.vor)
if self.useSRS then
subtitle=string.format("V O R frequency %.2f MHz",self.vor)
end
if not self.useSRS then
self:Transmission(ATIS.Sound.VORFrequency,1.0,subtitle)
local f=string.format("%.2f",self.vor)
f=UTILS.Split(f,".")
self.radioqueue:Number2Transmission(f[1],nil,0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal,0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz,0.2)
end
alltext=alltext..";\n"..subtitle
end
if self.tacan then
subtitle=string.format("TACAN channel %dX Ray",self.tacan)
if not self.useSRS then
self:Transmission(ATIS.Sound.TACANChannel,1.0,subtitle)
self.radioqueue:Number2Transmission(tostring(self.tacan),nil,0.2)
self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg",0.75,self.soundpath,nil,0.2)
end
alltext=alltext..";\n"..subtitle
end
if self.rsbn then
subtitle=string.format("RSBN channel %d",self.rsbn)
if not self.useSRS then
self:Transmission(ATIS.Sound.RSBNChannel,1.0,subtitle)
self.radioqueue:Number2Transmission(tostring(self.rsbn),nil,0.2)
end
alltext=alltext..";\n"..subtitle
end
local ndb=self:GetNavPoint(self.prmg,runwayLanding,rwyLandingLeft)
if ndb then
subtitle=string.format("PRMG channel %d",ndb.frequency)
if not self.useSRS then
self:Transmission(ATIS.Sound.PRMGChannel,1.0,subtitle)
self.radioqueue:Number2Transmission(tostring(ndb.frequency),nil,0.5)
end
alltext=alltext..";\n"..subtitle
end
if self.useSRS and self.AdditionalInformation then
alltext=alltext..";\n"..self.AdditionalInformation
end
subtitle=string.format("Advise on initial contact, you have information %s",NATO)
if not self.useSRS then
self:Transmission(ATIS.Sound.AdviceOnInitial,0.5,subtitle)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg",NATO),0.75,self.soundpath)
end
alltext=alltext..";\n"..subtitle
self:Report(alltext)
if self.usemarker then
self:UpdateMarker(_INFORMATION,_RUNACT,_WIND,_ALTIMETER,_TEMPERATURE)
end
end
function ATIS:onafterReport(From,Event,To,Text)
self:T(self.lid..string.format("Report:\n%s",Text))
if self.useSRS and self.msrs then
local text=string.gsub(Text,"[\r\n]","")
local text=string.gsub(text,"SM","statute miles")
local text=string.gsub(text,"°C","degrees Celsius")
local text=string.gsub(text,"°F","degrees Fahrenheit")
local text=string.gsub(text,"inHg","inches of Mercury")
local text=string.gsub(text,"mmHg","millimeters of Mercury")
local text=string.gsub(text,"hPa","hectopascals")
local text=string.gsub(text,"m/s","meters per second")
local text=string.gsub(text,"TACAN","tackan")
local text=string.gsub(text,"FARP","farp")
local text=string.gsub(text,";"," . ")
self:T("SRS TTS: "..text)
local duration=STTS.getSpeechTime(text,0.95)
self.msrsQ:NewTransmission(text,duration,self.msrs,nil,2)
self.SRSText=text
end
end
function ATIS:OnEventBaseCaptured(EventData)
if EventData and EventData.Place then
local airbase=EventData.Place
if EventData.PlaceName==self.airbasename then
local NewCoalitionAirbase=airbase:GetCoalition()
if self.useSRS and self.msrs and self.msrs.coalition~=NewCoalitionAirbase then
self.msrs:SetCoalition(NewCoalitionAirbase)
end
end
end
end
function ATIS:UpdateMarker(information,runact,wind,altimeter,temperature)
if self.markerid then
self.airbase:GetCoordinate():RemoveMark(self.markerid)
end
local text=string.format("ATIS on %.3f %s, %s:\n",self.frequency,UTILS.GetModulationName(self.modulation),tostring(information))
text=text..string.format("%s\n",tostring(runact))
text=text..string.format("%s\n",tostring(wind))
text=text..string.format("%s\n",tostring(altimeter))
text=text..string.format("%s",tostring(temperature))
self.markerid=self.airbase:GetCoordinate():MarkToAll(text,true)
return self.markerid
end
function ATIS:GetActiveRunway(Takeoff)
local runway=nil
if Takeoff then
runway=self.airbase:GetActiveRunwayTakeoff()
else
runway=self.airbase:GetActiveRunwayLanding()
end
return runway.name,runway.isLeft
end
function ATIS:GetMagneticRunway(windfrom)
local diffmin=nil
local runway=nil
for _,heading in pairs(self.runwaymag)do
local hdg=self:GetRunwayWithoutLR(heading)
local diff=UTILS.HdgDiff(windfrom,tonumber(hdg)*10)
if diffmin==nil or diff<diffmin then
diffmin=diff
runway=hdg
end
end
return runway
end
function ATIS:GetNavPoint(navpoints,runway,left)
for _,_nav in pairs(navpoints or{})do
local nav=_nav
if nav.runway==nil then
return nav
else
local navy=tonumber(self:GetRunwayWithoutLR(nav.runway))*10
local rwyy=tonumber(self:GetRunwayWithoutLR(runway))*10
local navL=self:GetRunwayLR(nav.runway)
local hdgD=UTILS.HdgDiff(navy,rwyy)
if hdgD<=15 then
if navL==nil or(navL==true and left==true)or(navL==false and left==false)then
return nav
end
end
end
end
return nil
end
function ATIS:GetRunwayWithoutLR(runway)
local rwywo=runway:gsub("%D+","")
return rwywo
end
function ATIS:GetRunwayLR(runway)
local rwyL=runway:lower():find("l")
local rwyR=runway:lower():find("r")
if rwyL then
return true
elseif rwyR then
return false
else
return nil
end
end
function ATIS:Transmission(sound,interval,subtitle,path)
self.radioqueue:NewTransmission(sound.filename,sound.duration,path or self.soundpath,nil,interval,subtitle,self.subduration)
end
function ATIS:SoundCheck()
for _,_sound in pairs(ATIS.Sound)do
local sound=_sound
local subtitle=string.format("Playing sound file %s, duration %.2f sec",sound.filename,sound.duration)
self:Transmission(sound,nil,subtitle)
MESSAGE:New(subtitle,5,"ATIS"):ToAll()
end
end
function ATIS:GetMissionWeather()
local weather=env.mission.weather
local clouds=weather.clouds
local static=weather.atmosphere_type==0
local visibility=weather.visibility.distance
local turbulence=weather.groundTurbulence
local dust=nil
if weather.enable_dust==true then
dust=weather.dust_density
end
local fog=nil
if weather.enable_fog==true then
fog=weather.fog
end
self:T("FF weather:")
self:T({clouds=clouds})
self:T({visibility=visibility})
self:T({turbulence=turbulence})
self:T({fog=fog})
self:T({dust=dust})
self:T({static=static})
return clouds,visibility,turbulence,fog,dust,static
end
function ATIS:_GetThousandsAndHundreds(n)
local N=UTILS.Round(n/1000,1)
local S=UTILS.Split(string.format("%.1f",N),".")
local t=S[1]
local h=S[2]
return t,h
end
do
CTLD_ENGINEERING={
ClassName="CTLD_ENGINEERING",
lid="",
Name="none",
Group=nil,
Unit=nil,
HeliGroup=nil,
HeliUnit=nil,
State="",
}
CTLD_ENGINEERING.Version="0.0.3"
function CTLD_ENGINEERING:New(Name,GroupName,HeliGroup,HeliUnit)
local self=BASE:Inherit(self,BASE:New())
self.Name=Name or"Engineer Squad"
self.Group=GROUP:FindByName(GroupName)
self.Unit=self.Group:GetUnit(1)
self.HeliGroup=HeliGroup
self.HeliUnit=HeliUnit
self.currwpt=nil
self.lid=string.format("%s (%s) | ",self.Name,self.Version)
self.State="Stopped"
self.marktimer=300
self:Start()
local parent=self:GetParent(self)
return self
end
function CTLD_ENGINEERING:SetStatus(State)
self.State=State
return self
end
function CTLD_ENGINEERING:GetStatus()
return self.State
end
function CTLD_ENGINEERING:IsStatus(State)
return self.State==State
end
function CTLD_ENGINEERING:IsNotStatus(State)
return self.State~=State
end
function CTLD_ENGINEERING:Start()
self:T(self.lid.."Start")
self:SetStatus("Running")
return self
end
function CTLD_ENGINEERING:Stop()
self:T(self.lid.."Stop")
self:SetStatus("Stopped")
return self
end
function CTLD_ENGINEERING:Build()
self:T(self.lid.."Build")
self:SetStatus("Building")
return self
end
function CTLD_ENGINEERING:Done()
self:T(self.lid.."Done")
local grp=self.Group
grp:RelocateGroundRandomInRadius(7,100,false,false,"Diamond")
self:SetStatus("Running")
return self
end
function CTLD_ENGINEERING:Search(crates,number)
self:T(self.lid.."Search")
self:SetStatus("Searching")
local dist=self.distance
local group=self.Group
local ctable={}
local ind=0
if number>0 then
for _,_cargo in pairs(crates)do
local cgotype=_cargo:GetType()
if _cargo:WasDropped()and cgotype~=CTLD_CARGO.Enum.STATIC then
local ok=false
local chalk=_cargo:GetMark()
if chalk==nil then
ok=true
else
local tag=chalk.tag or"none"
local timestamp=chalk.timestamp or 0
local gone=timer.getAbsTime()-timestamp
if gone>=self.marktimer then
ok=true
_cargo:WipeMark()
end
end
if ok then
local chalk={}
chalk.tag="Engineers"
chalk.timestamp=timer.getAbsTime()
_cargo:AddMark(chalk)
ind=ind+1
table.insert(ctable,ind,_cargo)
end
end
end
end
if ind>0 then
local crate=ctable[1]
local static=crate:GetPositionable()
local crate_pos=static:GetCoordinate()
local gpos=group:GetCoordinate()
local distance=self:_GetDistance(gpos,crate_pos)
self:T(string.format("%s Distance to crate: %d",self.lid,distance))
if distance>30 and distance~=-1 and self:IsStatus("Searching")then
group:RouteGroundTo(crate_pos,15,"Line abreast",1)
self.currwpt=crate_pos
self:Move()
elseif distance<=30 and distance~=-1 then
self:Arrive()
end
else
self:T(self.lid.."No crates in reach!")
end
return self
end
function CTLD_ENGINEERING:Move()
self:T(self.lid.."Move")
self:SetStatus("Moving")
local group=self.Group
local tgtpos=self.currwpt
local gpos=group:GetCoordinate()
local distance=self:_GetDistance(gpos,tgtpos)
self:T(string.format("%s Distance remaining: %d",self.lid,distance))
if distance<=30 and distance~=-1 then
self:Arrive()
end
return self
end
function CTLD_ENGINEERING:Arrive()
self:T(self.lid.."Arrive")
self:SetStatus("Arrived")
self.currwpt=nil
local Grp=self.Group
Grp:RouteStop()
return self
end
function CTLD_ENGINEERING:_GetDistance(_point1,_point2)
self:T(self.lid.." _GetDistance")
if _point1 and _point2 then
local distance1=_point1:Get2DDistance(_point2)
local distance2=_point1:DistanceFromPointVec2(_point2)
if distance1 and type(distance1)=="number"then
return distance1
elseif distance2 and type(distance2)=="number"then
return distance2
else
self:E("*****Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
else
self:E("******Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
end
end
do
CTLD_CARGO={
ClassName="CTLD_CARGO",
ID=0,
Name="none",
Templates={},
CargoType="none",
HasBeenMoved=false,
LoadDirectly=false,
CratesNeeded=0,
Positionable=nil,
HasBeenDropped=false,
PerCrateMass=0,
Stock=nil,
Mark=nil,
}
CTLD_CARGO.Enum={
VEHICLE="Vehicle",
TROOPS="Troops",
FOB="FOB",
CRATE="Crate",
REPAIR="Repair",
ENGINEERS="Engineers",
STATIC="Static",
}
function CTLD_CARGO:New(ID,Name,Templates,Sorte,HasBeenMoved,LoadDirectly,CratesNeeded,Positionable,Dropped,PerCrateMass,Stock,Subcategory)
local self=BASE:Inherit(self,BASE:New())
self:T({ID,Name,Templates,Sorte,HasBeenMoved,LoadDirectly,CratesNeeded,Positionable,Dropped})
self.ID=ID or math.random(100000,1000000)
self.Name=Name or"none"
self.Templates=Templates or{}
self.CargoType=Sorte or"type"
self.HasBeenMoved=HasBeenMoved or false
self.LoadDirectly=LoadDirectly or false
self.CratesNeeded=CratesNeeded or 0
self.Positionable=Positionable or nil
self.HasBeenDropped=Dropped or false
self.PerCrateMass=PerCrateMass or 0
self.Stock=Stock or nil
self.Mark=nil
self.Subcategory=Subcategory or"Other"
return self
end
function CTLD_CARGO:GetID()
return self.ID
end
function CTLD_CARGO:GetSubCat()
return self.Subcategory
end
function CTLD_CARGO:GetMass()
return self.PerCrateMass
end
function CTLD_CARGO:GetName()
return self.Name
end
function CTLD_CARGO:GetTemplates()
return self.Templates
end
function CTLD_CARGO:HasMoved()
return self.HasBeenMoved
end
function CTLD_CARGO:WasDropped()
return self.HasBeenDropped
end
function CTLD_CARGO:CanLoadDirectly()
return self.LoadDirectly
end
function CTLD_CARGO:GetCratesNeeded()
return self.CratesNeeded
end
function CTLD_CARGO:GetType()
return self.CargoType
end
function CTLD_CARGO:GetPositionable()
return self.Positionable
end
function CTLD_CARGO:SetHasMoved(moved)
self.HasBeenMoved=moved or false
end
function CTLD_CARGO:Isloaded()
if self.HasBeenMoved and not self.WasDropped()then
return true
else
return false
end
end
function CTLD_CARGO:SetWasDropped(dropped)
self.HasBeenDropped=dropped or false
end
function CTLD_CARGO:GetStock()
if self.Stock then
return self.Stock
else
return-1
end
end
function CTLD_CARGO:AddStock(Number)
if self.Stock then
local number=Number or 1
self.Stock=self.Stock+number
end
return self
end
function CTLD_CARGO:RemoveStock(Number)
if self.Stock then
local number=Number or 1
self.Stock=self.Stock-number
if self.Stock<0 then self.Stock=0 end
end
return self
end
function CTLD_CARGO:IsRepair()
if self.CargoType=="Repair"then
return true
else
return false
end
end
function CTLD_CARGO:IsStatic()
if self.CargoType=="Static"then
return true
else
return false
end
end
function CTLD_CARGO:AddMark(Mark)
self.Mark=Mark
return self
end
function CTLD_CARGO:GetMark(Mark)
return self.Mark
end
function CTLD_CARGO:WipeMark()
self.Mark=nil
return self
end
function CTLD_CARGO:GetNetMass()
return self.CratesNeeded*self.PerCrateMass
end
end
do
CTLD={
ClassName="CTLD",
verbose=0,
lid="",
coalition=1,
coalitiontxt="blue",
PilotGroups={},
CtldUnits={},
FreeVHFFrequencies={},
FreeUHFFrequencies={},
FreeFMFrequencies={},
CargoCounter=0,
Cargo_Troops={},
Cargo_Crates={},
Loaded_Cargo={},
Spawned_Crates={},
Spawned_Cargo={},
CrateDistance=35,
debug=false,
wpZones={},
dropOffZones={},
pickupZones={},
}
CTLD.RadioModulation={
AM=0,
FM=1,
}
CTLD.CargoZoneType={
LOAD="load",
DROP="drop",
MOVE="move",
SHIP="ship",
BEACON="beacon",
}
CTLD.UnitTypes={
["SA342Mistral"]={type="SA342Mistral",crates=false,troops=true,cratelimit=0,trooplimit=4,length=12,cargoweightlimit=400},
["SA342L"]={type="SA342L",crates=false,troops=true,cratelimit=0,trooplimit=2,length=12,cargoweightlimit=400},
["SA342M"]={type="SA342M",crates=false,troops=true,cratelimit=0,trooplimit=4,length=12,cargoweightlimit=400},
["SA342Minigun"]={type="SA342Minigun",crates=false,troops=true,cratelimit=0,trooplimit=2,length=12,cargoweightlimit=400},
["UH-1H"]={type="UH-1H",crates=true,troops=true,cratelimit=1,trooplimit=8,length=15,cargoweightlimit=700},
["Mi-8MTV2"]={type="Mi-8MTV2",crates=true,troops=true,cratelimit=2,trooplimit=12,length=15,cargoweightlimit=3000},
["Mi-8MT"]={type="Mi-8MT",crates=true,troops=true,cratelimit=2,trooplimit=12,length=15,cargoweightlimit=3000},
["Ka-50"]={type="Ka-50",crates=false,troops=false,cratelimit=0,trooplimit=0,length=15,cargoweightlimit=0},
["Ka-50_3"]={type="Ka-50_3",crates=false,troops=false,cratelimit=0,trooplimit=0,length=15,cargoweightlimit=0},
["Mi-24P"]={type="Mi-24P",crates=true,troops=true,cratelimit=2,trooplimit=8,length=18,cargoweightlimit=700},
["Mi-24V"]={type="Mi-24V",crates=true,troops=true,cratelimit=2,trooplimit=8,length=18,cargoweightlimit=700},
["Hercules"]={type="Hercules",crates=true,troops=true,cratelimit=7,trooplimit=64,length=25,cargoweightlimit=19000},
["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500},
["AH-64D_BLK_II"]={type="AH-64D_BLK_II",crates=false,troops=true,cratelimit=0,trooplimit=2,length=17,cargoweightlimit=200},
["Bronco-OV-10A"]={type="Bronco-OV-10A",crates=false,troops=true,cratelimit=0,trooplimit=5,length=13,cargoweightlimit=1450},
}
CTLD.version="1.0.29"
function CTLD:New(Coalition,Prefixes,Alias)
local self=BASE:Inherit(self,FSM:New())
BASE:T({Coalition,Prefixes,Alias})
if Coalition and type(Coalition)=="string"then
if Coalition=="blue"then
self.coalition=coalition.side.BLUE
self.coalitiontxt=Coalition
elseif Coalition=="red"then
self.coalition=coalition.side.RED
self.coalitiontxt=Coalition
elseif Coalition=="neutral"then
self.coalition=coalition.side.NEUTRAL
self.coalitiontxt=Coalition
else
self:E("ERROR: Unknown coalition in CTLD!")
end
else
self.coalition=Coalition
self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition))
end
if Alias then
self.alias=tostring(Alias)
else
self.alias="UNHCR"
if self.coalition then
if self.coalition==coalition.side.RED then
self.alias="Red CTLD"
elseif self.coalition==coalition.side.BLUE then
self.alias="Blue CTLD"
end
end
end
self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown")
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","TroopsPickedUp","*")
self:AddTransition("*","TroopsExtracted","*")
self:AddTransition("*","CratesPickedUp","*")
self:AddTransition("*","TroopsDeployed","*")
self:AddTransition("*","TroopsRTB","*")
self:AddTransition("*","CratesDropped","*")
self:AddTransition("*","CratesBuild","*")
self:AddTransition("*","CratesRepaired","*")
self:AddTransition("*","Load","*")
self:AddTransition("*","Save","*")
self:AddTransition("*","Stop","Stopped")
self.PilotGroups={}
self.CtldUnits={}
self.FreeVHFFrequencies={}
self.FreeUHFFrequencies={}
self.FreeFMFrequencies={}
self.UsedVHFFrequencies={}
self.UsedUHFFrequencies={}
self.UsedFMFrequencies={}
self.RadioSound="beacon.ogg"
self.RadioPath="l10n/DEFAULT/"
self.pickupZones={}
self.dropOffZones={}
self.wpZones={}
self.shipZones={}
self.droppedBeacons={}
self.droppedbeaconref={}
self.droppedbeacontimeout=600
self.Cargo_Crates={}
self.Cargo_Troops={}
self.Cargo_Statics={}
self.Loaded_Cargo={}
self.Spawned_Crates={}
self.Spawned_Cargo={}
self.MenusDone={}
self.DroppedTroops={}
self.DroppedCrates={}
self.CargoCounter=0
self.CrateCounter=0
self.TroopCounter=0
self.Engineers=0
self.EngineersInField={}
self.EngineerSearch=2000
self.nobuildmenu=false
self.CrateDistance=35
self.ExtractFactor=3.33
self.prefixes=Prefixes or{"Cargoheli"}
self.useprefix=true
self.maximumHoverHeight=15
self.minimumHoverHeight=4
self.forcehoverload=true
self.hoverautoloading=true
self.dropcratesanywhere=false
self.smokedistance=2000
self.movetroopstowpzone=true
self.movetroopsdistance=5000
self.troopdropzoneradius=100
self.enableHercules=false
self.HercMinAngels=165
self.HercMaxAngels=2000
self.HercMaxSpeed=77
self.suppressmessages=false
self.repairtime=300
self.buildtime=300
self.placeCratesAhead=false
self.cratecountry=country.id.GERMANY
self.pilotmustopendoors=false
if self.coalition==coalition.side.RED then
self.cratecountry=country.id.RUSSIA
end
self.enableLoadSave=false
self.filepath=nil
self.saveinterval=600
self.eventoninject=true
self.usesubcats=false
self.subcats={}
self.nobuildinloadzones=true
self.movecratesbeforebuild=true
self.surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER}
local AliaS=string.gsub(self.alias," ","_")
self.filename=string.format("CTLD_%s_Persist.csv",AliaS)
self.allowcratepickupagain=true
self.enableslingload=false
self.basetype="container_cargo"
self.SmokeColor=SMOKECOLOR.Red
self.FlareColor=FLARECOLOR.Red
for i=1,100 do
math.random()
end
self:_GenerateVHFrequencies()
self:_GenerateUHFrequencies()
self:_GenerateFMFrequencies()
return self
end
function CTLD:_GetUnitCapabilities(Unit)
self:T(self.lid.." _GetUnitCapabilities")
local _unit=Unit
local unittype=_unit:GetTypeName()
local capabilities=self.UnitTypes[unittype]
if not capabilities or capabilities=={}then
capabilities={}
capabilities.troops=false
capabilities.crates=false
capabilities.cratelimit=0
capabilities.trooplimit=0
capabilities.type="generic"
capabilities.length=20
capabilities.cargoweightlimit=0
end
return capabilities
end
function CTLD:_GenerateUHFrequencies()
self:T(self.lid.." _GenerateUHFrequencies")
self.FreeUHFFrequencies={}
self.FreeUHFFrequencies=UTILS.GenerateUHFrequencies()
return self
end
function CTLD:_GenerateFMFrequencies()
self:T(self.lid.." _GenerateFMrequencies")
self.FreeFMFrequencies={}
self.FreeFMFrequencies=UTILS.GenerateFMFrequencies()
return self
end
function CTLD:_GenerateVHFrequencies()
self:T(self.lid.." _GenerateVHFrequencies")
self.FreeVHFFrequencies={}
self.UsedVHFFrequencies={}
self.FreeVHFFrequencies=UTILS.GenerateVHFrequencies()
return self
end
function CTLD:SetTroopDropZoneRadius(Radius)
self:T(self.lid.." SetTroopDropZoneRadius")
local tradius=Radius or 100
if tradius<25 then tradius=25 end
self.troopdropzoneradius=tradius
return self
end
function CTLD:AddPlayerTask(PlayerTask)
self:T(self.lid.." AddPlayerTask")
if not self.PlayerTaskQueue then
self.PlayerTaskQueue=FIFO:New()
end
self.PlayerTaskQueue:Push(PlayerTask,PlayerTask.PlayerTaskNr)
return self
end
function CTLD:_EventHandler(EventData)
self:T(string.format("%s Event = %d",self.lid,EventData.id))
local event=EventData
if event.id==EVENTS.PlayerEnterAircraft or event.id==EVENTS.PlayerEnterUnit then
local _coalition=event.IniCoalition
if _coalition~=self.coalition then
return
end
local _unit=event.IniUnit
local _group=event.IniGroup
if _unit:IsHelicopter()or _group:IsHelicopter()then
local unitname=event.IniUnitName or"none"
self.Loaded_Cargo[unitname]=nil
self:_RefreshF10Menus()
end
if self:IsHercules(_unit)and self.enableHercules then
local unitname=event.IniUnitName or"none"
self.Loaded_Cargo[unitname]=nil
self:_RefreshF10Menus()
end
return
elseif event.id==EVENTS.PlayerLeaveUnit then
local unitname=event.IniUnitName or"none"
self.CtldUnits[unitname]=nil
self.Loaded_Cargo[unitname]=nil
end
return self
end
function CTLD:_SendMessage(Text,Time,Clearscreen,Group)
self:T(self.lid.." _SendMessage")
if not self.suppressmessages then
local m=MESSAGE:New(Text,Time,"CTLD",Clearscreen):ToGroup(Group)
end
return self
end
function CTLD:_FindTroopsCargoObject(Name)
self:T(self.lid.." _FindTroopsCargoObject")
local cargo=nil
for _,_cargo in pairs(self.Cargo_Troops)do
local cargo=_cargo
if cargo.Name==Name then
return cargo
end
end
return nil
end
function CTLD:PreloadTroops(Unit,Troopname)
self:T(self.lid.." PreloadTroops")
local name=Troopname or"Unknown"
if Unit and Unit:IsAlive()then
local cargo=self:_FindTroopsCargoObject(name)
local group=Unit:GetGroup()
if cargo then
self:_LoadTroops(group,Unit,cargo,true)
else
self:E(self.lid.." Troops preload - Cargo Object "..name.." not found!")
end
end
return self
end
function CTLD:_LoadTroops(Group,Unit,Cargotype,Inject)
self:T(self.lid.." _LoadTroops")
local instock=Cargotype:GetStock()
local cgoname=Cargotype:GetName()
local cgotype=Cargotype:GetType()
local cgonetmass=Cargotype:GetNetMass()
local maxloadable=self:_GetMaxLoadableMass(Unit)
if type(instock)=="number"and tonumber(instock)<=0 and tonumber(instock)~=-1 and not Inject then
self:_SendMessage(string.format("Sorry, all %s are gone!",cgoname),10,false,Group)
return self
end
local grounded=not self:IsUnitInAir(Unit)
local hoverload=self:CanHoverLoad(Unit)
local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if not inzone then
inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP)
end
if not Inject then
if not inzone then
self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group)
if not self.debug then return self end
elseif not grounded and not hoverload then
self:_SendMessage("You need to land or hover in position to load!",10,false,Group)
if not self.debug then return self end
elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
self:_SendMessage("You need to open the door(s) to load troops!",10,false,Group)
if not self.debug then return self end
end
end
local group=Group
local unit=Unit
local unitname=unit:GetName()
local cargotype=Cargotype
local cratename=cargotype:GetName()
local unittype=unit:GetTypeName()
local capabilities=self:_GetUnitCapabilities(Unit)
local cantroops=capabilities.troops
local trooplimit=capabilities.trooplimit
local troopsize=cargotype:GetCratesNeeded()
local numberonboard=0
local loaded={}
if self.Loaded_Cargo[unitname]then
loaded=self.Loaded_Cargo[unitname]
numberonboard=loaded.Troopsloaded or 0
else
loaded={}
loaded.Troopsloaded=0
loaded.Cratesloaded=0
loaded.Cargo={}
end
if troopsize+numberonboard>trooplimit then
self:_SendMessage("Sorry, we\'re crammed already!",10,false,Group)
return
elseif maxloadable<cgonetmass then
self:_SendMessage("Sorry, that\'s too heavy to load!",10,false,Group)
return
else
self.CargoCounter=self.CargoCounter+1
local loadcargotype=CTLD_CARGO:New(self.CargoCounter,Cargotype.Name,Cargotype.Templates,cgotype,true,true,Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass)
self:T({cargotype=loadcargotype})
loaded.Troopsloaded=loaded.Troopsloaded+troopsize
table.insert(loaded.Cargo,loadcargotype)
self.Loaded_Cargo[unitname]=loaded
self:_SendMessage("Troops boarded!",10,false,Group)
self:__TroopsPickedUp(1,Group,Unit,Cargotype)
self:_UpdateUnitCargoMass(Unit)
Cargotype:RemoveStock()
end
return self
end
function CTLD:_FindRepairNearby(Group,Unit,Repairtype)
self:T(self.lid.." _FindRepairNearby")
local unitcoord=Unit:GetCoordinate()
local nearestGroup=nil
local nearestGroupIndex=-1
local nearestDistance=10000
for k,v in pairs(self.DroppedTroops)do
local distance=self:_GetDistance(v:GetCoordinate(),unitcoord)
local unit=v:GetUnit(1)
local desc=unit:GetDesc()or nil
if distance<nearestDistance and distance~=-1 and not desc.attributes.Infantry then
nearestGroup=v
nearestGroupIndex=k
nearestDistance=distance
end
end
if nearestGroup==nil or nearestDistance>self.EngineerSearch then
self:_SendMessage("No unit close enough to repair!",10,false,Group)
return nil,nil
end
local groupname=nearestGroup:GetName()
local function matchstring(String,Table)
local match=false
if type(Table)=="table"then
for _,_name in pairs(Table)do
if string.find(String,_name)then
match=true
break
end
end
else
if type(String)=="string"then
if string.find(String,Table)then match=true end
end
end
return match
end
local Cargotype=nil
for k,v in pairs(self.Cargo_Crates)do
if matchstring(groupname,v.Templates)and matchstring(groupname,Repairtype)then
Cargotype=v
break
end
end
if Cargotype==nil then
return nil,nil
else
return nearestGroup,Cargotype
end
end
function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering)
self:T(self.lid.." _RepairObjectFromCrates")
local build=Build
local Repairtype=build.Template
local NearestGroup,CargoType=self:_FindRepairNearby(Group,Unit,Repairtype)
if NearestGroup~=nil then
if self.repairtime<2 then self.repairtime=30 end
if not Engineering then
self:_SendMessage(string.format("Repair started using %s taking %d secs",build.Name,self.repairtime),10,false,Group)
end
local name=CargoType:GetName()
local required=CargoType:GetCratesNeeded()
local template=CargoType:GetTemplates()
local ctype=CargoType:GetType()
local object={}
object.Name=CargoType:GetName()
object.Required=required
object.Found=required
object.Template=template
object.CanBuild=true
object.Type=ctype
self:_CleanUpCrates(Crates,Build,Number)
local desttimer=TIMER:New(function()NearestGroup:Destroy(false)end,self)
desttimer:Start(self.repairtime-1)
local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,object,true,NearestGroup:GetCoordinate())
buildtimer:Start(self.repairtime)
else
if not Engineering then
self:_SendMessage("Can't repair this unit with "..build.Name,10,false,Group)
else
self:T("Can't repair this unit with "..build.Name)
end
end
return self
end
function CTLD:_ExtractTroops(Group,Unit)
self:T(self.lid.." _ExtractTroops")
local grounded=not self:IsUnitInAir(Unit)
local hoverload=self:CanHoverLoad(Unit)
if not grounded and not hoverload then
self:_SendMessage("You need to land or hover in position to load!",10,false,Group)
if not self.debug then return self end
end
if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
self:_SendMessage("You need to open the door(s) to extract troops!",10,false,Group)
if not self.debug then return self end
end
local unit=Unit
local unitname=unit:GetName()
local unittype=unit:GetTypeName()
local capabilities=self:_GetUnitCapabilities(Unit)
local cantroops=capabilities.troops
local trooplimit=capabilities.trooplimit
local unitcoord=unit:GetCoordinate()
local nearestGroup=nil
local nearestGroupIndex=-1
local nearestDistance=10000000
local nearestList={}
local distancekeys={}
local extractdistance=self.CrateDistance*self.ExtractFactor
for k,v in pairs(self.DroppedTroops)do
local distance=self:_GetDistance(v:GetCoordinate(),unitcoord)
if distance<=extractdistance and distance~=-1 then
nearestGroup=v
nearestGroupIndex=k
nearestDistance=distance
table.insert(nearestList,math.floor(distance),v)
distancekeys[#distancekeys+1]=math.floor(distance)
end
end
if nearestGroup==nil or nearestDistance>extractdistance then
self:_SendMessage("No units close enough to extract!",10,false,Group)
return self
end
table.sort(distancekeys)
local secondarygroups={}
for i=1,#distancekeys do
local nearestGroup=nearestList[distancekeys[i]]
local groupType=string.match(nearestGroup:GetName(),"(.+)-(.+)$")
local Cargotype=nil
for k,v in pairs(self.Cargo_Troops)do
local comparison=""
if type(v.Templates)=="string"then comparison=v.Templates else comparison=v.Templates[1]end
if comparison==groupType then
Cargotype=v
break
end
end
if Cargotype==nil then
self:_SendMessage("Can't onboard "..groupType,10,false,Group)
else
local troopsize=Cargotype:GetCratesNeeded()
local numberonboard=0
local loaded={}
if self.Loaded_Cargo[unitname]then
loaded=self.Loaded_Cargo[unitname]
numberonboard=loaded.Troopsloaded or 0
else
loaded={}
loaded.Troopsloaded=0
loaded.Cratesloaded=0
loaded.Cargo={}
end
if troopsize+numberonboard>trooplimit then
self:_SendMessage("Sorry, we\'re crammed already!",10,false,Group)
else
self.CargoCounter=self.CargoCounter+1
local loadcargotype=CTLD_CARGO:New(self.CargoCounter,Cargotype.Name,Cargotype.Templates,Cargotype.CargoType,true,true,Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass)
self:T({cargotype=loadcargotype})
loaded.Troopsloaded=loaded.Troopsloaded+troopsize
table.insert(loaded.Cargo,loadcargotype)
self.Loaded_Cargo[unitname]=loaded
self:_SendMessage("Troops boarded!",10,false,Group)
self:_UpdateUnitCargoMass(Unit)
self:__TroopsExtracted(1,Group,Unit,nearestGroup)
if type(Cargotype.Templates)=="table"and Cargotype.Templates[2]then
for _,_key in pairs(Cargotype.Templates)do
table.insert(secondarygroups,_key)
end
end
nearestGroup:Destroy(false)
end
end
end
for _,_name in pairs(secondarygroups)do
for _,_group in pairs(nearestList)do
if _group and _group:IsAlive()then
local groupname=string.match(_group:GetName(),"(.+)-(.+)$")
if _name==groupname then
_group:Destroy(false)
end
end
end
end
self:CleanDroppedTroops()
return self
end
function CTLD:_GetCrates(Group,Unit,Cargo,number,drop)
self:T(self.lid.." _GetCrates")
if not drop then
local cgoname=Cargo:GetName()
local instock=Cargo:GetStock()
if type(instock)=="number"and tonumber(instock)<=0 and tonumber(instock)~=-1 then
self:_SendMessage(string.format("Sorry, we ran out of %s",cgoname),10,false,Group)
return self
end
end
local inzone=false
local drop=drop or false
local ship=nil
local width=20
local distance=nil
local zone=nil
if not drop then
inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if not inzone then
inzone,ship,zone,distance,width=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP)
end
else
if self.dropcratesanywhere then
inzone=true
else
inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP)
end
end
if not inzone then
self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group)
if not self.debug then return self end
end
local capabilities=self:_GetUnitCapabilities(Unit)
local canloadcratesno=capabilities.cratelimit
local loaddist=self.CrateDistance or 35
local nearcrates,numbernearby=self:_FindCratesNearby(Group,Unit,loaddist,true)
if numbernearby>=canloadcratesno and not drop then
self:_SendMessage("There are enough crates nearby already! Take care of those first!",10,false,Group)
return self
end
local IsHerc=self:IsHercules(Unit)
local cargotype=Cargo
local number=number or cargotype:GetCratesNeeded()
local cratesneeded=cargotype:GetCratesNeeded()
local cratename=cargotype:GetName()
local cratetemplate="Container"
local cgotype=cargotype:GetType()
local cgomass=cargotype:GetMass()
local isstatic=false
if cgotype==CTLD_CARGO.Enum.STATIC then
cratetemplate=cargotype:GetTemplates()
isstatic=true
end
local position=Unit:GetCoordinate()
local heading=Unit:GetHeading()+1
local height=Unit:GetHeight()
local droppedcargo={}
local cratedistance=0
local rheading=0
local angleOffNose=0
local addon=0
if IsHerc then
addon=180
end
for i=1,number do
local cratealias=string.format("%s-%d",cratetemplate,math.random(1,100000))
if not self.placeCratesAhead then
cratedistance=(i-1)*2.5+capabilities.length
if cratedistance>self.CrateDistance then cratedistance=self.CrateDistance end
rheading=UTILS.RandomGaussian(0,30,-90,90,100)
rheading=math.fmod((heading+rheading+addon),360)
else
local initialSpacing=IsHerc and 16 or 12
local crateSpacing=4
local lateralSpacing=4
local nrSideBySideCrates=3
if cratesneeded==1 then
cratedistance=initialSpacing
rheading=heading
else
if(i-1)%nrSideBySideCrates==0 then
cratedistance=i==1 and initialSpacing or cratedistance+crateSpacing
angleOffNose=math.ceil(math.deg(math.atan(lateralSpacing/cratedistance)))
rheading=heading-angleOffNose
else
rheading=rheading+angleOffNose
end
end
end
local cratecoord=position:Translate(cratedistance,rheading)
local cratevec2=cratecoord:GetVec2()
self.CrateCounter=self.CrateCounter+1
local basetype=self.basetype or"container_cargo"
if isstatic then
basetype=cratetemplate
end
if type(ship)=="string"then
self:T("Spawning on ship "..ship)
local Ship=UNIT:FindByName(ship)
local shipcoord=Ship:GetCoordinate()
local unitcoord=Unit:GetCoordinate()
local dist=shipcoord:Get2DDistance(unitcoord)
dist=dist-(20+math.random(1,10))
local width=width/2
local Offy=math.random(-width,width)
self.Spawned_Crates[self.CrateCounter]=SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry)
:InitCargoMass(cgomass)
:InitCargo(self.enableslingload)
:InitLinkToUnit(Ship,dist,Offy,0)
:Spawn(270,cratealias)
else
self.Spawned_Crates[self.CrateCounter]=SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry)
:InitCoordinate(cratecoord)
:InitCargoMass(cgomass)
:InitCargo(self.enableslingload)
:Spawn(270,cratealias)
end
local templ=cargotype:GetTemplates()
local sorte=cargotype:GetType()
local subcat=cargotype.Subcategory
self.CargoCounter=self.CargoCounter+1
local realcargo=nil
if drop then
realcargo=CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat)
table.insert(droppedcargo,realcargo)
else
realcargo=CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat)
Cargo:RemoveStock()
end
table.insert(self.Spawned_Cargo,realcargo)
end
local text=string.format("Crates for %s have been positioned near you!",cratename)
if drop then
text=string.format("Crates for %s have been dropped!",cratename)
self:__CratesDropped(1,Group,Unit,droppedcargo)
end
self:_SendMessage(text,10,false,Group)
return self
end
function CTLD:InjectStatics(Zone,Cargo,RandomCoord)
self:T(self.lid.." InjectStatics")
local cratecoord=Zone:GetCoordinate()
if RandomCoord then
cratecoord=Zone:GetRandomCoordinate(5,20)
end
local surface=cratecoord:GetSurfaceType()
if surface==land.SurfaceType.WATER then
return self
end
local cargotype=Cargo
local cratesneeded=cargotype:GetCratesNeeded()
local cratetemplate="Container"
local cratealias=string.format("%s-%d",cratetemplate,math.random(1,100000))
local cratename=cargotype:GetName()
local cgotype=cargotype:GetType()
local cgomass=cargotype:GetMass()
local isstatic=false
if cgotype==CTLD_CARGO.Enum.STATIC then
cratetemplate=cargotype:GetTemplates()
isstatic=true
end
local basetype=self.basetype or"container_cargo"
if isstatic then
basetype=cratetemplate
end
self.CrateCounter=self.CrateCounter+1
self.Spawned_Crates[self.CrateCounter]=SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry)
:InitCargoMass(cgomass)
:InitCargo(self.enableslingload)
:InitCoordinate(cratecoord)
:Spawn(270,cratealias)
local templ=cargotype:GetTemplates()
local sorte=cargotype:GetType()
self.CargoCounter=self.CargoCounter+1
cargotype.Positionable=self.Spawned_Crates[self.CrateCounter]
table.insert(self.Spawned_Cargo,cargotype)
return self
end
function CTLD:InjectStaticFromTemplate(Zone,Template,Mass)
self:T(self.lid.." InjectStaticFromTemplate")
local cargotype=self:GetStaticsCargoFromTemplate(Template,Mass)
self:InjectStatics(Zone,cargotype,true)
return self
end
function CTLD:_ListCratesNearby(_group,_unit)
self:T(self.lid.." _ListCratesNearby")
local finddist=self.CrateDistance or 35
local crates,number=self:_FindCratesNearby(_group,_unit,finddist,true)
if number>0 then
local text=REPORT:New("Crates Found Nearby:")
text:Add("------------------------------------------------------------")
for _,_entry in pairs(crates)do
local entry=_entry
local name=entry:GetName()
local dropped=entry:WasDropped()
if dropped then
text:Add(string.format("Dropped crate for %s, %dkg",name,entry.PerCrateMass))
else
text:Add(string.format("Crate for %s, %dkg",name,entry.PerCrateMass))
end
end
if text:GetCount()==1 then
text:Add("        N O N E")
end
text:Add("------------------------------------------------------------")
self:_SendMessage(text:Text(),30,true,_group)
else
self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group)
end
return self
end
function CTLD:_GetDistance(_point1,_point2)
self:T(self.lid.." _GetDistance")
if _point1 and _point2 then
local distance1=_point1:Get2DDistance(_point2)
local distance2=_point1:DistanceFromPointVec2(_point2)
if distance1 and type(distance1)=="number"then
return distance1
elseif distance2 and type(distance2)=="number"then
return distance2
else
self:E("*****Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
else
self:E("******Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
end
function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight)
self:T(self.lid.." _FindCratesNearby")
local finddist=_dist
local location=_group:GetCoordinate()
local existingcrates=self.Spawned_Cargo
local index=0
local found={}
local loadedmass=0
local unittype="none"
local capabilities={}
local maxmass=2000
local maxloadable=2000
if not _ignoreweight then
maxloadable=self:_GetMaxLoadableMass(_unit)
end
self:T(self.lid.." Max loadable mass: "..maxloadable)
for _,_cargoobject in pairs(existingcrates)do
local cargo=_cargoobject
local static=cargo:GetPositionable()
local staticid=cargo:GetID()
local weight=cargo:GetMass()
self:T(self.lid.." Found cargo mass: "..weight)
if static and static:IsAlive()then
local staticpos=static:GetCoordinate()
local distance=self:_GetDistance(location,staticpos)
if distance<=finddist and static and(weight<=maxloadable or _ignoreweight)then
index=index+1
table.insert(found,staticid,cargo)
maxloadable=maxloadable-weight
end
end
end
return found,index
end
function CTLD:_LoadCratesNearby(Group,Unit)
self:T(self.lid.." _LoadCratesNearby")
local group=Group
local unit=Unit
local unitname=unit:GetName()
local unittype=unit:GetTypeName()
local capabilities=self:_GetUnitCapabilities(Unit)
local cancrates=capabilities.crates
local cratelimit=capabilities.cratelimit
local grounded=not self:IsUnitInAir(Unit)
local canhoverload=self:CanHoverLoad(Unit)
if not cancrates then
self:_SendMessage("Sorry this chopper cannot carry crates!",10,false,Group)
elseif self.forcehoverload and not canhoverload then
self:_SendMessage("Hover over the crates to pick them up!",10,false,Group)
elseif not grounded and not canhoverload then
self:_SendMessage("Land or hover over the crates to pick them up!",10,false,Group)
else
local numberonboard=0
local massonboard=0
local loaded={}
if self.Loaded_Cargo[unitname]then
loaded=self.Loaded_Cargo[unitname]
numberonboard=loaded.Cratesloaded or 0
massonboard=self:_GetUnitCargoMass(Unit)
else
loaded={}
loaded.Troopsloaded=0
loaded.Cratesloaded=0
loaded.Cargo={}
end
local finddist=self.CrateDistance or 35
local nearcrates,number=self:_FindCratesNearby(Group,Unit,finddist,false)
self:T(self.lid.." Crates found: "..number)
if number==0 and self.hoverautoloading then
return self
elseif number==0 then
self:_SendMessage("Sorry no loadable crates nearby or max cargo weight reached!",10,false,Group)
return self
elseif numberonboard==cratelimit then
self:_SendMessage("Sorry no fully loaded!",10,false,Group)
return self
else
local capacity=cratelimit-numberonboard
local crateidsloaded={}
local loops=0
while loaded.Cratesloaded<cratelimit and loops<number do
loops=loops+1
local crateind=0
for _ind,_crate in pairs(nearcrates)do
if self.allowcratepickupagain then
if _crate:GetID()>crateind and _crate.Positionable~=nil then
crateind=_crate:GetID()
end
else
if not _crate:HasMoved()and _crate:WasDropped()and _crate:GetID()>crateind then
crateind=_crate:GetID()
end
end
end
if crateind>0 then
local crate=nearcrates[crateind]
loaded.Cratesloaded=loaded.Cratesloaded+1
crate:SetHasMoved(true)
crate:SetWasDropped(false)
table.insert(loaded.Cargo,crate)
table.insert(crateidsloaded,crate:GetID())
crate:GetPositionable():Destroy(false)
crate.Positionable=nil
self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,false,Group)
table.remove(nearcrates,crate:GetID())
self:__CratesPickedUp(1,Group,Unit,crate)
end
end
self.Loaded_Cargo[unitname]=loaded
self:_UpdateUnitCargoMass(Unit)
self:_CleanupTrackedCrates(crateidsloaded)
end
end
return self
end
function CTLD:_CleanupTrackedCrates(crateIdsToRemove)
local existingcrates=self.Spawned_Cargo
local newexcrates={}
for _,_crate in pairs(existingcrates)do
local excrate=_crate
local ID=excrate:GetID()
local keep=true
for _,_ID in pairs(crateIdsToRemove)do
if ID==_ID then
keep=false
end
end
local static=_crate:GetPositionable()
if not static or not static:IsAlive()then
keep=false
end
if keep then
table.insert(newexcrates,_crate)
end
end
self.Spawned_Cargo=nil
self.Spawned_Cargo=newexcrates
return self
end
function CTLD:_GetUnitCargoMass(Unit)
self:T(self.lid.." _GetUnitCargoMass")
if not Unit then return 0 end
local unitname=Unit:GetName()
local loadedcargo=self.Loaded_Cargo[unitname]or{}
local loadedmass=0
if self.Loaded_Cargo[unitname]then
local cargotable=loadedcargo.Cargo or{}
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then
loadedmass=loadedmass+(cargo.PerCrateMass*cargo:GetCratesNeeded())
end
if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped()then
loadedmass=loadedmass+cargo.PerCrateMass
end
end
end
return loadedmass
end
function CTLD:_GetMaxLoadableMass(Unit)
self:T(self.lid.." _GetMaxLoadableMass")
if not Unit then return 0 end
local loadable=0
local loadedmass=self:_GetUnitCargoMass(Unit)
local capabilities=self:_GetUnitCapabilities(Unit)
local maxmass=capabilities.cargoweightlimit or 2000
loadable=maxmass-loadedmass
return loadable
end
function CTLD:_UpdateUnitCargoMass(Unit)
self:T(self.lid.." _UpdateUnitCargoMass")
local calculatedMass=self:_GetUnitCargoMass(Unit)
Unit:SetUnitInternalCargo(calculatedMass)
return self
end
function CTLD:_ListCargo(Group,Unit)
self:T(self.lid.." _ListCargo")
local unitname=Unit:GetName()
local unittype=Unit:GetTypeName()
local capabilities=self:_GetUnitCapabilities(Unit)
local trooplimit=capabilities.trooplimit
local cratelimit=capabilities.cratelimit
local loadedcargo=self.Loaded_Cargo[unitname]or{}
local loadedmass=self:_GetUnitCargoMass(Unit)
local maxloadable=self:_GetMaxLoadableMass(Unit)
if self.Loaded_Cargo[unitname]then
local no_troops=loadedcargo.Troopsloaded or 0
local no_crates=loadedcargo.Cratesloaded or 0
local cargotable=loadedcargo.Cargo or{}
local report=REPORT:New("Transport Checkout Sheet")
report:Add("------------------------------------------------------------")
report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit))
report:Add("------------------------------------------------------------")
report:Add("        -- TROOPS --")
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and(not cargo:WasDropped()or self.allowcratepickupagain)then
report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded()))
end
end
if report:GetCount()==4 then
report:Add("        N O N E")
end
report:Add("------------------------------------------------------------")
report:Add("       -- CRATES --")
local cratecount=0
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
if(type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS)and(not cargo:WasDropped()or self.allowcratepickupagain)then
report:Add(string.format("Crate: %s size 1",cargo:GetName()))
cratecount=cratecount+1
end
end
if cratecount==0 then
report:Add("        N O N E")
end
report:Add("------------------------------------------------------------")
report:Add("Total Mass: "..loadedmass.." kg. Loadable: "..maxloadable.." kg.")
local text=report:Text()
self:_SendMessage(text,30,true,Group)
else
self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs",trooplimit,cratelimit,maxloadable),10,false,Group)
end
return self
end
function CTLD:_ListInventory(Group,Unit)
self:T(self.lid.." _ListInventory")
local unitname=Unit:GetName()
local unittype=Unit:GetTypeName()
local cgotypes=self.Cargo_Crates
local trptypes=self.Cargo_Troops
local stctypes=self.Cargo_Statics
local function countcargo(cgotable)
local counter=0
for _,_cgo in pairs(cgotable)do
counter=counter+1
end
return counter
end
local crateno=countcargo(cgotypes)
local troopno=countcargo(trptypes)
local staticno=countcargo(stctypes)
if(crateno>0 or troopno>0 or staticno>0)then
local report=REPORT:New("Inventory Sheet")
report:Add("------------------------------------------------------------")
report:Add(string.format("Troops: %d, Cratetypes: %d",troopno,crateno+staticno))
report:Add("------------------------------------------------------------")
report:Add("        -- TROOPS --")
for _,_cargo in pairs(trptypes)do
local cargo=_cargo
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then
local stockn=cargo:GetStock()
local stock="none"
if stockn==-1 then
stock="unlimited"
elseif stockn>0 then
stock=tostring(stockn)
end
report:Add(string.format("Unit: %s | Soldiers: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock))
end
end
if report:GetCount()==4 then
report:Add("        N O N E")
end
report:Add("------------------------------------------------------------")
report:Add("       -- CRATES --")
local cratecount=0
for _,_cargo in pairs(cgotypes)do
local cargo=_cargo
local type=cargo:GetType()
if(type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then
local stockn=cargo:GetStock()
local stock="none"
if stockn==-1 then
stock="unlimited"
elseif stockn>0 then
stock=tostring(stockn)
end
report:Add(string.format("Type: %s | Crates per Set: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock))
cratecount=cratecount+1
end
end
for _,_cargo in pairs(stctypes)do
local cargo=_cargo
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.STATIC)and not cargo:WasDropped()then
local stockn=cargo:GetStock()
local stock="none"
if stockn==-1 then
stock="unlimited"
elseif stockn>0 then
stock=tostring(stockn)
end
report:Add(string.format("Type: %s | Stock: %s",cargo:GetName(),stock))
cratecount=cratecount+1
end
end
if cratecount==0 then
report:Add("        N O N E")
end
local text=report:Text()
self:_SendMessage(text,30,true,Group)
else
self:_SendMessage(string.format("Nothing in stock!"),10,false,Group)
end
return self
end
function CTLD:IsHercules(Unit)
if Unit:GetTypeName()=="Hercules"or string.find(Unit:GetTypeName(),"Bronco")then
return true
else
return false
end
end
function CTLD:_UnloadTroops(Group,Unit)
self:T(self.lid.." _UnloadTroops")
local droppingatbase=false
local canunload=true
if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
self:_SendMessage("You need to open the door(s) to unload troops!",10,false,Group)
if not self.debug then return self end
end
local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if not inzone then
inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP)
end
if inzone then
droppingatbase=true
end
local hoverunload=self:IsCorrectHover(Unit)
local IsHerc=self:IsHercules(Unit)
if IsHerc then
hoverunload=self:IsCorrectFlightParameters(Unit)
end
local grounded=not self:IsUnitInAir(Unit)
local unitname=Unit:GetName()
if self.Loaded_Cargo[unitname]and(grounded or hoverunload)then
if not droppingatbase or self.debug then
local loadedcargo=self.Loaded_Cargo[unitname]or{}
local cargotable=loadedcargo.Cargo
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then
local name=cargo:GetName()or"none"
local temptable=cargo:GetTemplates()or{}
local position=Group:GetCoordinate()
local zoneradius=self.troopdropzoneradius or 100
local factor=1
if IsHerc then
factor=cargo:GetCratesNeeded()or 1
zoneradius=Unit:GetVelocityMPS()or 100
end
local zone=ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,zoneradius*factor)
local randomcoord=zone:GetRandomCoordinate(10,30*factor):GetVec2()
for _,_template in pairs(temptable)do
self.TroopCounter=self.TroopCounter+1
local alias=string.format("%s-%d",_template,math.random(1,100000))
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitRandomizeUnits(true,20,2)
:InitDelayOff()
:SpawnFromVec2(randomcoord)
self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],type)
end
cargo:SetWasDropped(true)
if type==CTLD_CARGO.Enum.ENGINEERS then
self.Engineers=self.Engineers+1
local grpname=self.DroppedTroops[self.TroopCounter]:GetName()
self.EngineersInField[self.Engineers]=CTLD_ENGINEERING:New(name,grpname)
self:_SendMessage(string.format("Dropped Engineers %s into action!",name),10,false,Group)
else
self:_SendMessage(string.format("Dropped Troops %s into action!",name),10,false,Group)
end
end
end
else
self:_SendMessage("Troops have returned to base!",10,false,Group)
self:__TroopsRTB(1,Group,Unit)
end
local loaded={}
loaded.Troopsloaded=0
loaded.Cratesloaded=0
loaded.Cargo={}
local loadedcargo=self.Loaded_Cargo[unitname]or{}
local cargotable=loadedcargo.Cargo or{}
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
local dropped=cargo:WasDropped()
if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and not dropped then
table.insert(loaded.Cargo,_cargo)
loaded.Cratesloaded=loaded.Cratesloaded+1
else
if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and droppingatbase then
local name=cargo:GetName()
local gentroops=self.Cargo_Troops
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
local stock=_troop:GetStock()
if stock and tonumber(stock)>=0 then _troop:AddStock()end
end
end
end
end
end
self.Loaded_Cargo[unitname]=nil
self.Loaded_Cargo[unitname]=loaded
self:_UpdateUnitCargoMass(Unit)
else
if IsHerc then
self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group)
else
self:_SendMessage("Nothing loaded or not hovering within parameters!",10,false,Group)
end
end
return self
end
function CTLD:_UnloadCrates(Group,Unit)
self:T(self.lid.." _UnloadCrates")
if not self.dropcratesanywhere then
local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP)
if not inzone then
self:_SendMessage("You are not close enough to a drop zone!",10,false,Group)
if not self.debug then
return self
end
end
end
local hoverunload=self:IsCorrectHover(Unit)
local IsHerc=self:IsHercules(Unit)
if IsHerc then
hoverunload=self:IsCorrectFlightParameters(Unit)
end
local grounded=not self:IsUnitInAir(Unit)
local unitname=Unit:GetName()
if self.Loaded_Cargo[unitname]and(grounded or hoverunload)then
local loadedcargo=self.Loaded_Cargo[unitname]or{}
local cargotable=loadedcargo.Cargo
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and(not cargo:WasDropped()or self.allowcratepickupagain)then
self:_GetCrates(Group,Unit,cargo,1,true)
cargo:SetWasDropped(true)
cargo:SetHasMoved(true)
end
end
local loaded={}
loaded.Troopsloaded=0
loaded.Cratesloaded=0
loaded.Cargo={}
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
local size=cargo:GetCratesNeeded()
if type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS then
table.insert(loaded.Cargo,_cargo)
loaded.Troopsloaded=loaded.Troopsloaded+size
end
end
self.Loaded_Cargo[unitname]=nil
self.Loaded_Cargo[unitname]=loaded
self:_UpdateUnitCargoMass(Unit)
else
if IsHerc then
self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group)
else
self:_SendMessage("Nothing loaded or not hovering within parameters!",10,false,Group)
end
end
return self
end
function CTLD:_BuildCrates(Group,Unit,Engineering)
self:T(self.lid.." _BuildCrates")
if self:IsHercules(Unit)and self.enableHercules and not Engineering then
local speed=Unit:GetVelocityKMH()
if speed>1 then
self:_SendMessage("You need to land / stop to build something, Pilot!",10,false,Group)
return self
end
end
if not Engineering and self.nobuildinloadzones then
local inloadzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if inloadzone then
self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group)
return self
end
end
local finddist=self.CrateDistance or 35
local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true)
local buildables={}
local foundbuilds=false
local canbuild=false
if number>0 then
for _,_crate in pairs(crates)do
local Crate=_crate
if(Crate:WasDropped()or not self.movecratesbeforebuild)and not Crate:IsRepair()and not Crate:IsStatic()then
local name=Crate:GetName()
local required=Crate:GetCratesNeeded()
local template=Crate:GetTemplates()
local ctype=Crate:GetType()
local ccoord=Crate:GetPositionable():GetCoordinate()
if not buildables[name]then
local object={}
object.Name=name
object.Required=required
object.Found=1
object.Template=template
object.CanBuild=false
object.Type=ctype
object.Coord=ccoord:GetVec2()
buildables[name]=object
foundbuilds=true
else
buildables[name].Found=buildables[name].Found+1
foundbuilds=true
end
if buildables[name].Found>=buildables[name].Required then
buildables[name].CanBuild=true
canbuild=true
end
self:T({buildables=buildables})
end
end
local report=REPORT:New("Checklist Buildable Crates")
report:Add("------------------------------------------------------------")
for _,_build in pairs(buildables)do
local build=_build
local name=build.Name
local needed=build.Required
local found=build.Found
local txtok="NO"
if build.CanBuild then
txtok="YES"
end
local text=string.format("Type: %s | Required %d | Found %d | Can Build %s",name,needed,found,txtok)
report:Add(text)
end
if not foundbuilds then
report:Add("     --- None found! ---")
if self.movecratesbeforebuild then
report:Add("*** Crates need to be moved before building!")
end
end
report:Add("------------------------------------------------------------")
local text=report:Text()
if not Engineering then
self:_SendMessage(text,30,true,Group)
else
self:T(text)
end
if canbuild then
for _,_build in pairs(buildables)do
local build=_build
if build.CanBuild then
self:_CleanUpCrates(crates,build,number)
if self.buildtime and self.buildtime>0 then
local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate())
buildtimer:Start(self.buildtime)
self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group)
else
self:_BuildObjectFromCrates(Group,Unit,build)
end
end
end
end
else
if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist),10,false,Group)end
end
return self
end
function CTLD:_RepairCrates(Group,Unit,Engineering)
self:T(self.lid.." _RepairCrates")
local finddist=self.CrateDistance or 35
local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true)
local buildables={}
local foundbuilds=false
local canbuild=false
if number>0 then
for _,_crate in pairs(crates)do
local Crate=_crate
if Crate:WasDropped()and Crate:IsRepair()and not Crate:IsStatic()then
local name=Crate:GetName()
local required=Crate:GetCratesNeeded()
local template=Crate:GetTemplates()
local ctype=Crate:GetType()
if not buildables[name]then
local object={}
object.Name=name
object.Required=required
object.Found=1
object.Template=template
object.CanBuild=false
object.Type=ctype
buildables[name]=object
foundbuilds=true
else
buildables[name].Found=buildables[name].Found+1
foundbuilds=true
end
if buildables[name].Found>=buildables[name].Required then
buildables[name].CanBuild=true
canbuild=true
end
self:T({repair=buildables})
end
end
local report=REPORT:New("Checklist Repairs")
report:Add("------------------------------------------------------------")
for _,_build in pairs(buildables)do
local build=_build
local name=build.Name
local needed=build.Required
local found=build.Found
local txtok="NO"
if build.CanBuild then
txtok="YES"
end
local text=string.format("Type: %s | Required %d | Found %d | Can Repair %s",name,needed,found,txtok)
report:Add(text)
end
if not foundbuilds then report:Add("     --- None Found ---")end
report:Add("------------------------------------------------------------")
local text=report:Text()
if not Engineering then
self:_SendMessage(text,30,true,Group)
else
self:T(text)
end
if canbuild then
for _,_build in pairs(buildables)do
local build=_build
if build.CanBuild then
self:_RepairObjectFromCrates(Group,Unit,crates,build,number,Engineering)
end
end
end
else
if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist),10,false,Group)end
end
return self
end
function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation)
self:T(self.lid.." _BuildObjectFromCrates")
if Group and Group:IsAlive()or(RepairLocation and not Repair)then
local name=Build.Name
local ctype=Build.Type
local canmove=false
if ctype==CTLD_CARGO.Enum.VEHICLE then canmove=true end
if ctype==CTLD_CARGO.Enum.STATIC then
return self
end
local temptable=Build.Template or{}
if type(temptable)=="string"then
temptable={temptable}
end
local zone=nil
if RepairLocation and not Repair then
zone=ZONE_RADIUS:New(string.format("Build zone-%d",math.random(1,10000)),RepairLocation:GetVec2(),100)
else
zone=ZONE_GROUP:New(string.format("Unload zone-%d",math.random(1,10000)),Group,100)
end
local randomcoord=Build.Coord or zone:GetRandomCoordinate(35):GetVec2()
if Repair then
randomcoord=RepairLocation:GetVec2()
end
for _,_template in pairs(temptable)do
self.TroopCounter=self.TroopCounter+1
local alias=string.format("%s-%d",_template,math.random(1,100000))
if canmove then
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitDelayOff()
:SpawnFromVec2(randomcoord)
else
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitDelayOff()
:SpawnFromVec2(randomcoord)
end
if Repair then
self:__CratesRepaired(1,Group,Unit,self.DroppedTroops[self.TroopCounter])
else
self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter])
end
end
else
self:T(self.lid.."Group KIA while building!")
end
return self
end
function CTLD:_MoveGroupToZone(Group)
self:T(self.lid.." _MoveGroupToZone")
local groupname=Group:GetName()or"none"
local groupcoord=Group:GetCoordinate()
local outcome,name,zone,distance=self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE)
if(distance<=self.movetroopsdistance)and zone then
local groupname=Group:GetName()
local zonecoord=zone:GetRandomCoordinate(20,125)
local coordinate=zonecoord:GetVec2()
Group:SetAIOn()
Group:OptionAlarmStateAuto()
Group:OptionDisperseOnAttack(30)
Group:OptionROEOpenFirePossible()
Group:RouteToVec2(coordinate,5)
end
return self
end
function CTLD:_CleanUpCrates(Crates,Build,Number)
self:T(self.lid.." _CleanUpCrates")
local build=Build
local existingcrates=self.Spawned_Cargo
local newexcrates={}
local numberdest=Build.Required
local nametype=Build.Name
local found=0
local rounds=Number
local destIDs={}
for _,_crate in pairs(Crates)do
local nowcrate=_crate
local name=nowcrate:GetName()
local thisID=nowcrate:GetID()
if name==nametype then
table.insert(destIDs,thisID)
found=found+1
nowcrate:GetPositionable():Destroy(false)
nowcrate.Positionable=nil
nowcrate.HasBeenDropped=false
end
if found==numberdest then break end
end
self:_CleanupTrackedCrates(destIDs)
return self
end
function CTLD:_RefreshF10Menus()
self:T(self.lid.." _RefreshF10Menus")
local PlayerSet=self.PilotGroups
local PlayerTable=PlayerSet:GetSetObjects()
local _UnitList={}
for _key,_group in pairs(PlayerTable)do
local _unit=_group:GetUnit(1)
if _unit then
if _unit:IsAlive()and _unit:IsPlayer()then
if _unit:IsHelicopter()or(self:IsHercules(_unit)and self.enableHercules)then
local unitName=_unit:GetName()
_UnitList[unitName]=unitName
end
end
end
end
self.CtldUnits=_UnitList
if self.usesubcats then
for _id,_cargo in pairs(self.Cargo_Crates)do
local entry=_cargo
if not self.subcats[entry.Subcategory]then
self.subcats[entry.Subcategory]=entry.Subcategory
end
end
for _id,_cargo in pairs(self.Cargo_Statics)do
local entry=_cargo
if not self.subcats[entry.Subcategory]then
self.subcats[entry.Subcategory]=entry.Subcategory
end
end
end
local menucount=0
local menus={}
for _,_unitName in pairs(self.CtldUnits)do
if not self.MenusDone[_unitName]then
local _unit=UNIT:FindByName(_unitName)
if _unit then
local _group=_unit:GetGroup()
if _group then
local unittype=_unit:GetTypeName()
local capabilities=self:_GetUnitCapabilities(_unit)
local cantroops=capabilities.troops
local cancrates=capabilities.crates
local topmenu=MENU_GROUP:New(_group,"CTLD",nil)
local toptroops=MENU_GROUP:New(_group,"Manage Troops",topmenu)
local topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu)
local listmenu=MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit)
local invtry=MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit)
local rbcns=MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu,self._ListRadioBeacons,self,_group,_unit)
local smoketopmenu=MENU_GROUP:New(_group,"Smokes, Flares, Beacons",topmenu)
local smokemenu=MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu,self.SmokeZoneNearBy,self,_unit,false)
local smokeself=MENU_GROUP:New(_group,"Drop smoke now",smoketopmenu)
local smokeselfred=MENU_GROUP_COMMAND:New(_group,"Red smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Red)
local smokeselfblue=MENU_GROUP_COMMAND:New(_group,"Blue smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Blue)
local smokeselfgreen=MENU_GROUP_COMMAND:New(_group,"Green smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Green)
local smokeselforange=MENU_GROUP_COMMAND:New(_group,"Orange smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Orange)
local smokeselfwhite=MENU_GROUP_COMMAND:New(_group,"White smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.White)
local flaremenu=MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu,self.SmokeZoneNearBy,self,_unit,true)
local flareself=MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu,self.SmokePositionNow,self,_unit,true)
local beaconself=MENU_GROUP_COMMAND:New(_group,"Drop beacon now",smoketopmenu,self.DropBeaconNow,self,_unit):Refresh()
if cantroops then
local troopsmenu=MENU_GROUP:New(_group,"Load troops",toptroops)
for _,_entry in pairs(self.Cargo_Troops)do
local entry=_entry
menucount=menucount+1
menus[menucount]=MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops,self,_group,_unit,entry)
end
local unloadmenu1=MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops,self._UnloadTroops,self,_group,_unit):Refresh()
local extractMenu1=MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh()
end
if cancrates then
local loadmenu=MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates,self._LoadCratesNearby,self,_group,_unit)
local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates)
if self.usesubcats then
local subcatmenus={}
for _name,_entry in pairs(self.subcats)do
subcatmenus[_name]=MENU_GROUP:New(_group,_name,cratesmenu)
end
for _,_entry in pairs(self.Cargo_Crates)do
local entry=_entry
local subcat=entry.Subcategory
menucount=menucount+1
local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates,self,_group,_unit,entry)
end
for _,_entry in pairs(self.Cargo_Statics)do
local entry=_entry
local subcat=entry.Subcategory
menucount=menucount+1
local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates,self,_group,_unit,entry)
end
else
for _,_entry in pairs(self.Cargo_Crates)do
local entry=_entry
menucount=menucount+1
local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates,self,_group,_unit,entry)
end
for _,_entry in pairs(self.Cargo_Statics)do
local entry=_entry
menucount=menucount+1
local menutext=string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates,self,_group,_unit,entry)
end
end
listmenu=MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit)
local unloadmenu=MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates,self._UnloadCrates,self,_group,_unit)
if not self.nobuildmenu then
local buildmenu=MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates,self._BuildCrates,self,_group,_unit)
local repairmenu=MENU_GROUP_COMMAND:New(_group,"Repair",topcrates,self._RepairCrates,self,_group,_unit):Refresh()
else
unloadmenu:Refresh()
end
end
if self:IsHercules(_unit)then
local hoverpars=MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu,self._ShowFlightParams,self,_group,_unit):Refresh()
else
local hoverpars=MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu,self._ShowHoverParams,self,_group,_unit):Refresh()
end
self.MenusDone[_unitName]=true
end
end
else
self:T(self.lid.." Menus already done for this group!")
end
end
return self
end
function CTLD:_CheckTemplates(temptable)
self:T(self.lid.." _CheckTemplates")
local outcome=true
if type(temptable)~="table"then
temptable={temptable}
end
for _,_name in pairs(temptable)do
if not _DATABASE.Templates.Groups[_name]then
outcome=false
self:E(self.lid.."ERROR: Template name ".._name.." is missing!")
end
end
return outcome
end
function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock)
self:T(self.lid.." AddTroopsCargo")
self:T({Name,Templates,Type,NoTroops,PerTroopMass,Stock})
if not self:_CheckTemplates(Templates)then
self:E(self.lid.."Troops Cargo for "..Name.." has missing template(s)!")
return self
end
self.CargoCounter=self.CargoCounter+1
local cargo=CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock)
table.insert(self.Cargo_Troops,cargo)
return self
end
function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory)
self:T(self.lid.." AddCratesCargo")
if not self:_CheckTemplates(Templates)then
self:E(self.lid.."Crates Cargo for "..Name.." has missing template(s)!")
return self
end
self.CargoCounter=self.CargoCounter+1
local cargo=CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory)
table.insert(self.Cargo_Crates,cargo)
return self
end
function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory)
self:T(self.lid.." AddStaticsCargo")
self.CargoCounter=self.CargoCounter+1
local type=CTLD_CARGO.Enum.STATIC
local template=STATIC:FindByName(Name,true):GetTypeName()
local cargo=CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory)
table.insert(self.Cargo_Statics,cargo)
return self
end
function CTLD:GetStaticsCargoFromTemplate(Name,Mass)
self:T(self.lid.." GetStaticsCargoFromTemplate")
self.CargoCounter=self.CargoCounter+1
local type=CTLD_CARGO.Enum.STATIC
local template=STATIC:FindByName(Name,true):GetTypeName()
local cargo=CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,1)
return cargo
end
function CTLD:AddCratesRepair(Name,Template,Type,NoCrates,PerCrateMass,Stock,SubCategory)
self:T(self.lid.." AddCratesRepair")
if not self:_CheckTemplates(Template)then
self:E(self.lid.."Repair Cargo for "..Name.." has a missing template!")
return self
end
self.CargoCounter=self.CargoCounter+1
local cargo=CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory)
table.insert(self.Cargo_Crates,cargo)
return self
end
function CTLD:AddZone(Zone)
self:T(self.lid.." AddZone")
local zone=Zone
if zone.type==CTLD.CargoZoneType.LOAD then
table.insert(self.pickupZones,zone)
elseif zone.type==CTLD.CargoZoneType.DROP then
table.insert(self.dropOffZones,zone)
elseif zone.type==CTLD.CargoZoneType.SHIP then
table.insert(self.shipZones,zone)
elseif zone.type==CTLD.CargoZoneType.BEACON then
table.insert(self.droppedBeacons,zone)
else
table.insert(self.wpZones,zone)
end
return self
end
function CTLD:ActivateZone(Name,ZoneType,NewState)
self:T(self.lid.." ActivateZone")
local newstate=true
if NewState~=nil then
newstate=NewState
end
local table={}
if ZoneType==CTLD.CargoZoneType.LOAD then
table=self.pickupZones
elseif ZoneType==CTLD.CargoZoneType.DROP then
table=self.dropOffZones
elseif ZoneType==CTLD.CargoZoneType.SHIP then
table=self.shipZones
else
table=self.wpZones
end
for _,_zone in pairs(table)do
local thiszone=_zone
if thiszone.name==Name then
thiszone.active=newstate
break
end
end
return self
end
function CTLD:DeactivateZone(Name,ZoneType)
self:T(self.lid.." DeactivateZone")
self:ActivateZone(Name,ZoneType,false)
return self
end
function CTLD:_GetFMBeacon(Name)
self:T(self.lid.." _GetFMBeacon")
local beacon={}
if#self.FreeFMFrequencies<=1 then
self.FreeFMFrequencies=self.UsedFMFrequencies
self.UsedFMFrequencies={}
end
local FM=table.remove(self.FreeFMFrequencies,math.random(#self.FreeFMFrequencies))
table.insert(self.UsedFMFrequencies,FM)
beacon.name=Name
beacon.frequency=FM/1000000
beacon.modulation=CTLD.RadioModulation.FM
return beacon
end
function CTLD:_GetUHFBeacon(Name)
self:T(self.lid.." _GetUHFBeacon")
local beacon={}
if#self.FreeUHFFrequencies<=1 then
self.FreeUHFFrequencies=self.UsedUHFFrequencies
self.UsedUHFFrequencies={}
end
local UHF=table.remove(self.FreeUHFFrequencies,math.random(#self.FreeUHFFrequencies))
table.insert(self.UsedUHFFrequencies,UHF)
beacon.name=Name
beacon.frequency=UHF/1000000
beacon.modulation=CTLD.RadioModulation.AM
return beacon
end
function CTLD:_GetVHFBeacon(Name)
self:T(self.lid.." _GetVHFBeacon")
local beacon={}
if#self.FreeVHFFrequencies<=3 then
self.FreeVHFFrequencies=self.UsedVHFFrequencies
self.UsedVHFFrequencies={}
end
local VHF=table.remove(self.FreeVHFFrequencies,math.random(#self.FreeVHFFrequencies))
table.insert(self.UsedVHFFrequencies,VHF)
beacon.name=Name
beacon.frequency=VHF/1000000
beacon.modulation=CTLD.RadioModulation.FM
return beacon
end
function CTLD:AddCTLDZone(Name,Type,Color,Active,HasBeacon,Shiplength,Shipwidth)
self:T(self.lid.." AddCTLDZone")
local zone=ZONE:FindByName(Name)
if not zone and Type~=CTLD.CargoZoneType.SHIP then
self:E(self.lid.."**** Zone does not exist: "..Name)
return self
end
if Type==CTLD.CargoZoneType.SHIP then
local Ship=UNIT:FindByName(Name)
if not Ship then
self:E(self.lid.."**** Ship does not exist: "..Name)
return self
end
end
local ctldzone={}
ctldzone.active=Active or false
ctldzone.color=Color or SMOKECOLOR.Red
ctldzone.name=Name or"NONE"
ctldzone.type=Type or CTLD.CargoZoneType.MOVE
ctldzone.hasbeacon=HasBeacon or false
if Type==CTLD.CargoZoneType.BEACON then
self.droppedbeaconref[ctldzone.name]=zone:GetCoordinate()
ctldzone.timestamp=timer.getTime()
end
if HasBeacon then
ctldzone.fmbeacon=self:_GetFMBeacon(Name)
ctldzone.uhfbeacon=self:_GetUHFBeacon(Name)
ctldzone.vhfbeacon=self:_GetVHFBeacon(Name)
else
ctldzone.fmbeacon=nil
ctldzone.uhfbeacon=nil
ctldzone.vhfbeacon=nil
end
if Type==CTLD.CargoZoneType.SHIP then
ctldzone.shiplength=Shiplength or 100
ctldzone.shipwidth=Shipwidth or 10
end
self:AddZone(ctldzone)
return self
end
function CTLD:AddCTLDZoneFromAirbase(AirbaseName,Type,Color,Active,HasBeacon)
self:T(self.lid.." AddCTLDZoneFromAirbase")
local AFB=AIRBASE:FindByName(AirbaseName)
local name=AFB:GetZone():GetName()
self:T(self.lid.."AFB "..AirbaseName.." ZoneName "..name)
self:AddCTLDZone(name,Type,Color,Active,HasBeacon)
return self
end
function CTLD:DropBeaconNow(Unit)
self:T(self.lid.." DropBeaconNow")
local ctldzone={}
ctldzone.active=true
ctldzone.color=math.random(0,4)
ctldzone.name="Beacon "..math.random(1,10000)
ctldzone.type=CTLD.CargoZoneType.BEACON
ctldzone.hasbeacon=true
ctldzone.fmbeacon=self:_GetFMBeacon(ctldzone.name)
ctldzone.uhfbeacon=self:_GetUHFBeacon(ctldzone.name)
ctldzone.vhfbeacon=self:_GetVHFBeacon(ctldzone.name)
ctldzone.timestamp=timer.getTime()
self.droppedbeaconref[ctldzone.name]=Unit:GetCoordinate()
self:AddZone(ctldzone)
local FMbeacon=ctldzone.fmbeacon
local VHFbeacon=ctldzone.vhfbeacon
local UHFbeacon=ctldzone.uhfbeacon
local Name=ctldzone.name
local FM=FMbeacon.frequency
local VHF=VHFbeacon.frequency*1000
local UHF=UHFbeacon.frequency
local text=string.format("Dropped %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",Name,FM,VHF,UHF)
self:_SendMessage(text,15,false,Unit:GetGroup())
return self
end
function CTLD:CheckDroppedBeacons()
self:T(self.lid.." CheckDroppedBeacons")
local timeout=self.droppedbeacontimeout or 600
local livebeacontable={}
for _,_beacon in pairs(self.droppedBeacons)do
local beacon=_beacon
if not beacon.timestamp then beacon.timestamp=timer.getTime()+timeout end
local T0=beacon.timestamp
if timer.getTime()-T0>timeout then
local name=beacon.name
self.droppedbeaconref[name]=nil
_beacon=nil
else
table.insert(livebeacontable,beacon)
end
end
self.droppedBeacons=nil
self.droppedBeacons=livebeacontable
return self
end
function CTLD:_ListRadioBeacons(Group,Unit)
self:T(self.lid.." _ListRadioBeacons")
local report=REPORT:New("Active Zone Beacons")
report:Add("------------------------------------------------------------")
local zones={[1]=self.pickupZones,[2]=self.wpZones,[3]=self.dropOffZones,[4]=self.shipZones,[5]=self.droppedBeacons}
for i=1,5 do
for index,cargozone in pairs(zones[i])do
local czone=cargozone
if czone.active and czone.hasbeacon then
local FMbeacon=czone.fmbeacon
local VHFbeacon=czone.vhfbeacon
local UHFbeacon=czone.uhfbeacon
local Name=czone.name
local FM=FMbeacon.frequency
local VHF=VHFbeacon.frequency*1000
local UHF=UHFbeacon.frequency
report:AddIndent(string.format(" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",Name,FM,VHF,UHF),"|")
end
end
end
if report:GetCount()==1 then
report:Add("        N O N E")
end
report:Add("------------------------------------------------------------")
self:_SendMessage(report:Text(),30,true,Group)
return self
end
function CTLD:_AddRadioBeacon(Name,Sound,Mhz,Modulation,IsShip,IsDropped)
self:T(self.lid.." _AddRadioBeacon")
local Zone=nil
if IsShip then
Zone=UNIT:FindByName(Name)
elseif IsDropped then
Zone=self.droppedbeaconref[Name]
else
Zone=ZONE:FindByName(Name)
if not Zone then
Zone=AIRBASE:FindByName(Name):GetZone()
end
end
local Sound=Sound or"beacon.ogg"
if Zone then
if IsDropped then
local ZoneCoord=Zone
local ZoneVec3=ZoneCoord:GetVec3()or{x=0,y=0,z=0}
local Frequency=Mhz*1000000
local Sound=self.RadioPath..Sound
trigger.action.radioTransmission(Sound,ZoneVec3,Modulation,false,Frequency,1000,Name..math.random(1,10000))
self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = %d %d %d | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation))
else
local ZoneCoord=Zone:GetCoordinate()
local ZoneVec3=ZoneCoord:GetVec3()or{x=0,y=0,z=0}
local Frequency=Mhz*1000000
local Sound=self.RadioPath..Sound
trigger.action.radioTransmission(Sound,ZoneVec3,Modulation,false,Frequency,1000,Name..math.random(1,10000))
self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = {x=%d, y=%d, z=%d} | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation))
end
else
self:E(self.lid.."***** _AddRadioBeacon: Zone does not exist: "..Name)
end
return self
end
function CTLD:SetSoundfilesFolder(FolderPath)
self:T(self.lid.." SetSoundfilesFolder")
if FolderPath then
local lastchar=string.sub(FolderPath,-1)
if lastchar~="/"then
FolderPath=FolderPath.."/"
end
end
self.RadioPath=FolderPath
self:I(self.lid..string.format("Setting sound files folder to: %s",self.RadioPath))
return self
end
function CTLD:_RefreshRadioBeacons()
self:T(self.lid.." _RefreshRadioBeacons")
local zones={[1]=self.pickupZones,[2]=self.wpZones,[3]=self.dropOffZones,[4]=self.shipZones,[5]=self.droppedBeacons}
for i=1,5 do
local IsShip=false
if i==4 then IsShip=true end
local IsDropped=false
if i==5 then IsDropped=true end
for index,cargozone in pairs(zones[i])do
local czone=cargozone
local Sound=self.RadioSound
if czone.active and czone.hasbeacon then
local FMbeacon=czone.fmbeacon
local VHFbeacon=czone.vhfbeacon
local UHFbeacon=czone.uhfbeacon
local Name=czone.name
local FM=FMbeacon.frequency
local VHF=VHFbeacon.frequency
local UHF=UHFbeacon.frequency
self:_AddRadioBeacon(Name,Sound,FM,CTLD.RadioModulation.FM,IsShip,IsDropped)
self:_AddRadioBeacon(Name,Sound,VHF,CTLD.RadioModulation.FM,IsShip,IsDropped)
self:_AddRadioBeacon(Name,Sound,UHF,CTLD.RadioModulation.AM,IsShip,IsDropped)
end
end
end
return self
end
function CTLD:IsUnitInZone(Unit,Zonetype)
self:T(self.lid.." IsUnitInZone")
self:T(Zonetype)
local unitname=Unit:GetName()
local zonetable={}
local outcome=false
if Zonetype==CTLD.CargoZoneType.LOAD then
zonetable=self.pickupZones
elseif Zonetype==CTLD.CargoZoneType.DROP then
zonetable=self.dropOffZones
elseif Zonetype==CTLD.CargoZoneType.SHIP then
zonetable=self.shipZones
else
zonetable=self.wpZones
end
local zonecoord=nil
local colorret=nil
local maxdist=1000000
local zoneret=nil
local zonewret=nil
local zonenameret=nil
local unitcoord=Unit:GetCoordinate()
local unitVec2=unitcoord:GetVec2()
for _,_cargozone in pairs(zonetable)do
local czone=_cargozone
local zonename=czone.name
local active=czone.active
local color=czone.color
local zone=nil
local zoneradius=100
local zonewidth=20
if Zonetype==CTLD.CargoZoneType.SHIP then
self:T("Checking Type Ship: "..zonename)
local ZoneUNIT=UNIT:FindByName(zonename)
zonecoord=ZoneUNIT:GetCoordinate()
zoneradius=czone.shiplength
zonewidth=czone.shipwidth
zone=ZONE_UNIT:New(ZoneUNIT:GetName(),ZoneUNIT,zoneradius/2)
elseif ZONE:FindByName(zonename)then
zone=ZONE:FindByName(zonename)
self:T("Checking Zone: "..zonename)
zonecoord=zone:GetCoordinate()
zonewidth=zoneradius
elseif AIRBASE:FindByName(zonename)then
zone=AIRBASE:FindByName(zonename):GetZone()
self:T("Checking Zone: "..zonename)
zonecoord=zone:GetCoordinate()
zoneradius=2000
zonewidth=zoneradius
end
local distance=self:_GetDistance(zonecoord,unitcoord)
if zone:IsVec2InZone(unitVec2)and active then
outcome=true
end
if maxdist>distance then
maxdist=distance
zoneret=zone
zonenameret=zonename
zonewret=zonewidth
colorret=color
end
end
if Zonetype==CTLD.CargoZoneType.SHIP then
return outcome,zonenameret,zoneret,maxdist,zonewret
else
return outcome,zonenameret,zoneret,maxdist
end
end
function CTLD:SmokePositionNow(Unit,Flare,SmokeColor)
self:T(self.lid.." SmokePositionNow")
local Smokecolor=self.SmokeColor or SMOKECOLOR.Red
if SmokeColor then
Smokecolor=SmokeColor
end
local FlareColor=self.FlareColor or FLARECOLOR.Red
local unitcoord=Unit:GetCoordinate()
local Group=Unit:GetGroup()
if Flare then
unitcoord:Flare(FlareColor,90)
else
local height=unitcoord:GetLandHeight()+2
unitcoord.y=height
unitcoord:Smoke(Smokecolor)
end
return self
end
function CTLD:SmokeZoneNearBy(Unit,Flare)
self:T(self.lid.." SmokeZoneNearBy")
local unitcoord=Unit:GetCoordinate()
local Group=Unit:GetGroup()
local smokedistance=self.smokedistance
local smoked=false
local zones={[1]=self.pickupZones,[2]=self.wpZones,[3]=self.dropOffZones,[4]=self.shipZones}
for i=1,4 do
for index,cargozone in pairs(zones[i])do
local CZone=cargozone
local zonename=CZone.name
local zone=nil
if i==4 then
zone=UNIT:FindByName(zonename)
else
zone=ZONE:FindByName(zonename)
if not zone then
zone=AIRBASE:FindByName(zonename):GetZone()
end
end
local zonecoord=zone:GetCoordinate()
local active=CZone.active
local color=CZone.color
local distance=self:_GetDistance(zonecoord,unitcoord)
if distance<smokedistance and active then
if not Flare then
zonecoord:Smoke(color or SMOKECOLOR.White)
else
if color==SMOKECOLOR.Blue then color=FLARECOLOR.White end
zonecoord:Flare(color or FLARECOLOR.White)
end
local txt="smoking"
if Flare then txt="flaring"end
self:_SendMessage(string.format("Roger, %s zone %s!",txt,zonename),10,false,Group)
smoked=true
end
end
end
if not smoked then
local distance=UTILS.MetersToNM(self.smokedistance)
self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,false,Group)
end
return self
end
function CTLD:UnitCapabilities(Unittype,Cancrates,Cantroops,Cratelimit,Trooplimit,Length,Maxcargoweight)
self:T(self.lid.." UnitCapabilities")
local unittype=nil
local unit=nil
if type(Unittype)=="string"then
unittype=Unittype
elseif type(Unittype)=="table"then
unit=UNIT:FindByName(Unittype)
unittype=unit:GetTypeName()
else
return self
end
local length=20
local maxcargo=500
local existingcaps=self.UnitTypes[unittype]
if existingcaps then
length=existingcaps.length or 20
maxcargo=existingcaps.cargoweightlimit or 500
end
local capabilities={}
capabilities.type=unittype
capabilities.crates=Cancrates or false
capabilities.troops=Cantroops or false
capabilities.cratelimit=Cratelimit or 0
capabilities.trooplimit=Trooplimit or 0
capabilities.length=Length or length
capabilities.cargoweightlimit=Maxcargoweight or maxcargo
self.UnitTypes[unittype]=capabilities
return self
end
function CTLD:IsCorrectHover(Unit)
self:T(self.lid.." IsCorrectHover")
local outcome=false
if self:IsUnitInAir(Unit)then
local uspeed=Unit:GetVelocityMPS()
local uheight=Unit:GetHeight()
local ucoord=Unit:GetCoordinate()
local gheight=ucoord:GetLandHeight()
local aheight=uheight-gheight
local maxh=self.maximumHoverHeight
local minh=self.minimumHoverHeight
local mspeed=2
if(uspeed<=mspeed)and(aheight<=maxh)and(aheight>=minh)then
outcome=true
end
end
return outcome
end
function CTLD:IsCorrectFlightParameters(Unit)
self:T(self.lid.." IsCorrectFlightParameters")
local outcome=false
if self:IsUnitInAir(Unit)then
local uspeed=Unit:GetVelocityMPS()
local uheight=Unit:GetHeight()
local ucoord=Unit:GetCoordinate()
local gheight=ucoord:GetLandHeight()
local aheight=uheight-gheight
local minh=self.HercMinAngels
local maxh=self.HercMaxAngels
local maxspeed=self.HercMaxSpeed
local kmspeed=uspeed*3.6
local knspeed=kmspeed/1.86
self:T(string.format("%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn",self.lid,aheight,uspeed,kmspeed,knspeed))
if(aheight<=maxh)and(aheight>=minh)and(uspeed<=maxspeed)then
outcome=true
end
end
return outcome
end
function CTLD:_ShowHoverParams(Group,Unit)
local inhover=self:IsCorrectHover(Unit)
local htxt="true"
if not inhover then htxt="false"end
local text=""
if _SETTINGS:IsMetric()then
text=string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s",self.minimumHoverHeight,self.maximumHoverHeight,htxt)
else
local minheight=UTILS.MetersToFeet(self.minimumHoverHeight)
local maxheight=UTILS.MetersToFeet(self.maximumHoverHeight)
text=string.format("Hover parameters (autoload/drop):\n - Min height %dft \n - Max height %dft \n - Max speed 6ftps \n - In parameter: %s",minheight,maxheight,htxt)
end
self:_SendMessage(text,10,false,Group)
return self
end
function CTLD:_ShowFlightParams(Group,Unit)
local inhover=self:IsCorrectFlightParameters(Unit)
local htxt="true"
if not inhover then htxt="false"end
local text=""
if _SETTINGS:IsImperial()then
local minheight=UTILS.MetersToFeet(self.HercMinAngels)
local maxheight=UTILS.MetersToFeet(self.HercMaxAngels)
text=string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s",minheight,maxheight,htxt)
else
local minheight=self.HercMinAngels
local maxheight=self.HercMaxAngels
text=string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s",minheight,maxheight,htxt)
end
self:_SendMessage(text,10,false,Group)
return self
end
function CTLD:CanHoverLoad(Unit)
self:T(self.lid.." CanHoverLoad")
if self:IsHercules(Unit)then return false end
local outcome=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)and self:IsCorrectHover(Unit)
if not outcome then
outcome=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP)
end
return outcome
end
function CTLD:IsUnitInAir(Unit)
local minheight=self.minimumHoverHeight
if self.enableHercules and self:IsHercules(Unit)then
minheight=5.1
end
local uheight=Unit:GetHeight()
local ucoord=Unit:GetCoordinate()
local gheight=ucoord:GetLandHeight()
local aheight=uheight-gheight
if aheight>=minheight then
return true
else
return false
end
end
function CTLD:AutoHoverLoad(Unit)
self:T(self.lid.." AutoHoverLoad")
local unittype=Unit:GetTypeName()
local unitname=Unit:GetName()
local Group=Unit:GetGroup()
local capabilities=self:_GetUnitCapabilities(Unit)
local cancrates=capabilities.crates
local cratelimit=capabilities.cratelimit
if cancrates then
local numberonboard=0
local loaded={}
if self.Loaded_Cargo[unitname]then
loaded=self.Loaded_Cargo[unitname]
numberonboard=loaded.Cratesloaded or 0
end
local load=cratelimit-numberonboard
local canload=self:CanHoverLoad(Unit)
if canload and load>0 then
self:_LoadCratesNearby(Group,Unit)
end
end
return self
end
function CTLD:CheckAutoHoverload()
if self.hoverautoloading then
for _,_pilot in pairs(self.CtldUnits)do
local Unit=UNIT:FindByName(_pilot)
if self:CanHoverLoad(Unit)then self:AutoHoverLoad(Unit)end
end
end
return self
end
function CTLD:CleanDroppedTroops()
local troops=self.DroppedTroops
local newtable={}
for _index,_group in pairs(troops)do
self:T({_group.ClassName})
if _group and _group.ClassName=="GROUP"then
if _group:IsAlive()then
newtable[_index]=_group
end
end
end
self.DroppedTroops=newtable
local engineers=self.EngineersInField
local engtable={}
for _index,_group in pairs(engineers)do
self:T({_group.ClassName})
if _group and _group:IsNotStatus("Stopped")then
engtable[_index]=_group
end
end
self.EngineersInField=engtable
return self
end
function CTLD:AddStockTroops(Name,Number)
local name=Name or"none"
local number=Number or 1
local gentroops=self.Cargo_Troops
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:AddStock(number)
end
end
end
function CTLD:AddStockCrates(Name,Number)
local name=Name or"none"
local number=Number or 1
local gentroops=self.Cargo_Crates
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:AddStock(number)
end
end
end
function CTLD:RemoveStockTroops(Name,Number)
local name=Name or"none"
local number=Number or 1
local gentroops=self.Cargo_Troops
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:RemoveStock(number)
end
end
end
function CTLD:RemoveStockCrates(Name,Number)
local name=Name or"none"
local number=Number or 1
local gentroops=self.Cargo_Crates
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:RemoveStock(number)
end
end
return self
end
function CTLD:_CheckEngineers()
self:T(self.lid.." CheckEngineers")
local engtable=self.EngineersInField
for _ind,_engineers in pairs(engtable)do
local engineers=_engineers
local wrenches=engineers.Group
self:T(_engineers.lid.._engineers:GetStatus())
if wrenches and wrenches:IsAlive()then
if engineers:IsStatus("Running")or engineers:IsStatus("Searching")then
local crates,number=self:_FindCratesNearby(wrenches,nil,self.EngineerSearch,true)
engineers:Search(crates,number)
elseif engineers:IsStatus("Moving")then
engineers:Move()
elseif engineers:IsStatus("Arrived")then
engineers:Build()
local unit=wrenches:GetUnit(1)
self:_BuildCrates(wrenches,unit,true)
self:_RepairCrates(wrenches,unit,true)
engineers:Done()
end
else
engineers:Stop()
end
end
return self
end
function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation)
self:T(self.lid.." InjectTroops")
local cargo=Cargo
local function IsTroopsMatch(cargo)
local match=false
local cgotbl=self.Cargo_Troops
local name=cargo:GetName()
for _,_cgo in pairs(cgotbl)do
local cname=_cgo:GetName()
if name==cname then
match=true
break
end
end
return match
end
if not IsTroopsMatch(cargo)then
self.CargoCounter=self.CargoCounter+1
cargo.ID=self.CargoCounter
cargo.Stock=1
table.insert(self.Cargo_Troops,cargo)
end
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then
local name=cargo:GetName()or"none"
local temptable=cargo:GetTemplates()or{}
local factor=1.5
local zone=Zone
local randomcoord=zone:GetRandomCoordinate(10,30*factor,Surfacetypes):GetVec2()
if PreciseLocation then
randomcoord=zone:GetCoordinate():GetVec2()
end
for _,_template in pairs(temptable)do
self.TroopCounter=self.TroopCounter+1
local alias=string.format("%s-%d",_template,math.random(1,100000))
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitRandomizeUnits(true,20,2)
:InitDelayOff()
:SpawnFromVec2(randomcoord)
if self.movetroopstowpzone and type~=CTLD_CARGO.Enum.ENGINEERS then
self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter])
end
end
cargo:SetWasDropped(true)
if type==CTLD_CARGO.Enum.ENGINEERS then
self.Engineers=self.Engineers+1
local grpname=self.DroppedTroops[self.TroopCounter]:GetName()
self.EngineersInField[self.Engineers]=CTLD_ENGINEERING:New(name,grpname)
end
if self.eventoninject then
self:__TroopsDeployed(1,nil,nil,self.DroppedTroops[self.TroopCounter],type)
end
end
return self
end
function CTLD:InjectVehicles(Zone,Cargo)
self:T(self.lid.." InjectVehicles")
local cargo=Cargo
local function IsVehicMatch(cargo)
local match=false
local cgotbl=self.Cargo_Crates
local name=cargo:GetName()
for _,_cgo in pairs(cgotbl)do
local cname=_cgo:GetName()
if name==cname then
match=true
break
end
end
return match
end
if not IsVehicMatch(cargo)then
self.CargoCounter=self.CargoCounter+1
cargo.ID=self.CargoCounter
cargo.Stock=1
table.insert(self.Cargo_Crates,cargo)
end
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then
local name=cargo:GetName()or"none"
local temptable=cargo:GetTemplates()or{}
local factor=1.5
local zone=Zone
local randomcoord=zone:GetRandomCoordinate(10,30*factor):GetVec2()
cargo:SetWasDropped(true)
local canmove=false
if type==CTLD_CARGO.Enum.VEHICLE then canmove=true end
for _,_template in pairs(temptable)do
self.TroopCounter=self.TroopCounter+1
local alias=string.format("%s-%d",_template,math.random(1,100000))
if canmove then
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitRandomizeUnits(true,20,2)
:InitDelayOff()
:SpawnFromVec2(randomcoord)
else
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitDelayOff()
:SpawnFromVec2(randomcoord)
end
if self.eventoninject then
self:__CratesBuild(1,nil,nil,self.DroppedTroops[self.TroopCounter])
end
end
end
return self
end
function CTLD:onafterStart(From,Event,To)
self:T({From,Event,To})
self:I(self.lid.."Started ("..self.version..")")
if self.useprefix or self.enableHercules then
local prefix=self.prefixes
if self.enableHercules then
self.PilotGroups=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart()
else
self.PilotGroups=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart()
end
else
self.PilotGroups=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategories("helicopter"):FilterStart()
end
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler)
self:__Status(-5)
if self.enableLoadSave then
local interval=self.saveinterval
local filename=self.filename
local filepath=self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
function CTLD:onbeforeStatus(From,Event,To)
self:T({From,Event,To})
self:CleanDroppedTroops()
self:_RefreshF10Menus()
self:CheckDroppedBeacons()
self:_RefreshRadioBeacons()
self:CheckAutoHoverload()
self:_CheckEngineers()
return self
end
function CTLD:onafterStatus(From,Event,To)
self:T({From,Event,To})
local pilots=0
for _,_pilot in pairs(self.CtldUnits)do
pilots=pilots+1
end
local boxes=0
for _,_pilot in pairs(self.Spawned_Cargo)do
boxes=boxes+1
end
local cc=self.CargoCounter
local tc=self.TroopCounter
if self.debug or self.verbose>0 then
local text=string.format("%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d",self.lid,pilots,boxes,cc,tc)
local m=MESSAGE:New(text,10,"CTLD"):ToAll()
if self.verbose>0 then
self:I(self.lid.."Cargo and Troops in Stock:")
for _,_troop in pairs(self.Cargo_Crates)do
local name=_troop:GetName()
local stock=_troop:GetStock()
self:I(string.format("-- %s \t\t\t %d",name,stock))
end
for _,_troop in pairs(self.Cargo_Statics)do
local name=_troop:GetName()
local stock=_troop:GetStock()
self:I(string.format("-- %s \t\t\t %d",name,stock))
end
for _,_troop in pairs(self.Cargo_Troops)do
local name=_troop:GetName()
local stock=_troop:GetStock()
self:I(string.format("-- %s \t\t %d",name,stock))
end
end
end
self:__Status(-30)
return self
end
function CTLD:onafterStop(From,Event,To)
self:T({From,Event,To})
self:UnhandleEvent(EVENTS.PlayerEnterAircraft)
self:UnhandleEvent(EVENTS.PlayerEnterUnit)
self:UnhandleEvent(EVENTS.PlayerLeaveUnit)
return self
end
function CTLD:onbeforeTroopsPickedUp(From,Event,To,Group,Unit,Cargo)
self:T({From,Event,To})
return self
end
function CTLD:onbeforeCratesPickedUp(From,Event,To,Group,Unit,Cargo)
self:T({From,Event,To})
return self
end
function CTLD:onbeforeTroopsExtracted(From,Event,To,Group,Unit,Troops)
self:T({From,Event,To})
return self
end
function CTLD:onbeforeTroopsDeployed(From,Event,To,Group,Unit,Troops)
self:T({From,Event,To})
if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then
local playername=Unit:GetPlayerName()
local dropcoord=Troops:GetCoordinate()or COORDINATE:New(0,0,0)
local dropvec2=dropcoord:GetVec2()
self.PlayerTaskQueue:ForEach(
function(Task)
local task=Task
local subtype=task:GetSubType()
if Event==subtype and not task:IsDone()then
local targetzone=task.Target:GetObject()
if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and targetzone:IsVec2InZone(dropvec2)then
if task.Clients:HasUniqueID(playername)then
task:__Success(-1)
end
end
end
end
)
end
return self
end
function CTLD:onafterTroopsDeployed(From,Event,To,Group,Unit,Troops,Type)
self:T({From,Event,To})
if self.movetroopstowpzone and Type~=CTLD_CARGO.Enum.ENGINEERS then
self:_MoveGroupToZone(Troops)
end
return self
end
function CTLD:onbeforeCratesDropped(From,Event,To,Group,Unit,Cargotable)
self:T({From,Event,To})
return self
end
function CTLD:onbeforeCratesBuild(From,Event,To,Group,Unit,Vehicle)
self:T({From,Event,To})
if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then
local playername=Unit:GetPlayerName()
local dropcoord=Vehicle:GetCoordinate()or COORDINATE:New(0,0,0)
local dropvec2=dropcoord:GetVec2()
self.PlayerTaskQueue:ForEach(
function(Task)
local task=Task
local subtype=task:GetSubType()
if Event==subtype and not task:IsDone()then
local targetzone=task.Target:GetObject()
if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and targetzone:IsVec2InZone(dropvec2)then
if task.Clients:HasUniqueID(playername)then
task:__Success(-1)
end
end
end
end
)
end
return self
end
function CTLD:onafterCratesBuild(From,Event,To,Group,Unit,Vehicle)
self:T({From,Event,To})
if self.movetroopstowpzone then
self:_MoveGroupToZone(Vehicle)
end
return self
end
function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit)
self:T({From,Event,To})
return self
end
function CTLD:onbeforeSave(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
if not io then
self:E(self.lid.."ERROR: io not desanitized. Can't save current state.")
return false
end
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
return true
end
function CTLD:onafterSave(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
local function _savefile(filename,data)
local f=assert(io.open(filename,"wb"))
f:write(data)
f:close()
end
if lfs then
path=self.filepath or lfs.writedir()
end
filename=filename or self.filename
if path~=nil then
filename=path.."\\"..filename
end
local grouptable=self.DroppedTroops
local cgovehic=self.Cargo_Crates
local cgotable=self.Cargo_Troops
local stcstable=self.Spawned_Cargo
local statics=nil
local statics={}
self:T(self.lid.."Bulding Statics Table for Saving")
for _,_cargo in pairs(stcstable)do
local cargo=_cargo
local object=cargo:GetPositionable()
if object and object:IsAlive()and(cargo:WasDropped()or not cargo:HasMoved())then
statics[#statics+1]=cargo
end
end
local function FindCargoType(name,table)
local match=false
local cargo=nil
for _ind,_cargo in pairs(table)do
local thiscargo=_cargo
local template=thiscargo:GetTemplates()
if type(template)=="string"then
template={template}
end
for _,_name in pairs(template)do
if string.find(name,_name)and _cargo:GetType()~=CTLD_CARGO.Enum.REPAIR then
match=true
cargo=thiscargo
end
end
if match then break end
end
return match,cargo
end
local data="Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass\n"
local n=0
for _,_grp in pairs(grouptable)do
local group=_grp
if group and group:IsAlive()then
local name=group:GetName()
local template=string.gsub(name,"-(.+)$","")
if string.find(template,"#")then
template=string.gsub(name,"#(%d+)$","")
end
local match,cargo=FindCargoType(template,cgotable)
if not match then
match,cargo=FindCargoType(template,cgovehic)
end
if match then
n=n+1
local cargo=cargo
local cgoname=cargo.Name
local cgotemp=cargo.Templates
local cgotype=cargo.CargoType
local cgoneed=cargo.CratesNeeded
local cgomass=cargo.PerCrateMass
if type(cgotemp)=="table"then
local templates="{"
for _,_tmpl in pairs(cgotemp)do
templates=templates.._tmpl..";"
end
templates=templates.."}"
cgotemp=templates
end
local location=group:GetVec3()
local txt=string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n"
,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass)
data=data..txt
end
end
end
for _,_cgo in pairs(statics)do
local object=_cgo
local cgoname=object.Name
local cgotemp=object.Templates
if type(cgotemp)=="table"then
local templates="{"
for _,_tmpl in pairs(cgotemp)do
templates=templates.._tmpl..";"
end
templates=templates.."}"
cgotemp=templates
end
local cgotype=object.CargoType
local cgoneed=object.CratesNeeded
local cgomass=object.PerCrateMass
local crateobj=object.Positionable
local location=crateobj:GetVec3()
local txt=string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n"
,"STATIC",location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass)
data=data..txt
end
_savefile(filename,data)
if self.enableLoadSave then
local interval=self.saveinterval
local filename=self.filename
local filepath=self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
function CTLD:onbeforeLoad(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
filename=filename or self.filename
path=path or self.filepath
if not io then
self:E(self.lid.."WARNING: io not desanitized. Cannot load file.")
return false
end
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
if lfs then
path=path or lfs.writedir()
end
if path~=nil then
filename=path.."\\"..filename
end
local exists=_fileexists(filename)
if exists then
return true
else
self:E(self.lid..string.format("WARNING: State file %s might not exist.",filename))
return false
end
end
function CTLD:onafterLoad(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
local function _loadfile(filename)
local f=assert(io.open(filename,"rb"))
local data=f:read("*all")
f:close()
return data
end
filename=filename or self.filename
path=path or self.filepath
if lfs then
path=path or lfs.writedir()
end
if path~=nil then
filename=path.."\\"..filename
end
local text=string.format("Loading CTLD state from file %s",filename)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:I(self.lid..text)
local file=assert(io.open(filename,"rb"))
local loadeddata={}
for line in file:lines()do
loadeddata[#loadeddata+1]=line
end
file:close()
table.remove(loadeddata,1)
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local groupname=dataset[1]
local vec2={}
vec2.x=tonumber(dataset[2])
vec2.y=tonumber(dataset[4])
local cargoname=dataset[5]
local cargotype=dataset[7]
if type(groupname)=="string"and groupname~="STATIC"then
local cargotemplates=dataset[6]
cargotemplates=string.gsub(cargotemplates,"{","")
cargotemplates=string.gsub(cargotemplates,"}","")
cargotemplates=UTILS.Split(cargotemplates,";")
local size=tonumber(dataset[8])
local mass=tonumber(dataset[9])
local dropzone=ZONE_RADIUS:New("DropZone",vec2,20)
if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then
local injectvehicle=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass)
self:InjectVehicles(dropzone,injectvehicle)
elseif cargotype==CTLD_CARGO.Enum.TROOPS or cargotype==CTLD_CARGO.Enum.ENGINEERS then
local injecttroops=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass)
self:InjectTroops(dropzone,injecttroops,self.surfacetypes)
end
elseif(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then
local cargotemplates=dataset[6]
local size=tonumber(dataset[8])
local mass=tonumber(dataset[9])
local dropzone=ZONE_RADIUS:New("DropZone",vec2,20)
local injectstatic=nil
if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then
cargotemplates=string.gsub(cargotemplates,"{","")
cargotemplates=string.gsub(cargotemplates,"}","")
cargotemplates=UTILS.Split(cargotemplates,";")
injectstatic=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass)
elseif cargotype==CTLD_CARGO.Enum.STATIC or cargotype==CTLD_CARGO.Enum.REPAIR then
injectstatic=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass)
end
if injectstatic then
self:InjectStatics(dropzone,injectstatic)
end
end
end
return self
end
end
do
CTLD_HERCULES={
ClassName="CTLD_HERCULES",
lid="",
Name="",
Version="0.0.2",
}
CTLD_HERCULES.Types={
["ATGM M1045 HMMWV TOW Air [7183lb]"]={['name']="M1045 HMMWV TOW",['container']=true},
["ATGM M1045 HMMWV TOW Skid [7073lb]"]={['name']="M1045 HMMWV TOW",['container']=false},
["APC M1043 HMMWV Armament Air [7023lb]"]={['name']="M1043 HMMWV Armament",['container']=true},
["APC M1043 HMMWV Armament Skid [6912lb]"]={['name']="M1043 HMMWV Armament",['container']=false},
["SAM Avenger M1097 Air [7200lb]"]={['name']="M1097 Avenger",['container']=true},
["SAM Avenger M1097 Skid [7090lb]"]={['name']="M1097 Avenger",['container']=false},
["APC Cobra Air [10912lb]"]={['name']="Cobra",['container']=true},
["APC Cobra Skid [10802lb]"]={['name']="Cobra",['container']=false},
["APC M113 Air [21624lb]"]={['name']="M-113",['container']=true},
["APC M113 Skid [21494lb]"]={['name']="M-113",['container']=false},
["Tanker M978 HEMTT [34000lb]"]={['name']="M978 HEMTT Tanker",['container']=false},
["HEMTT TFFT [34400lb]"]={['name']="HEMTT TFFT",['container']=false},
["SPG M1128 Stryker MGS [33036lb]"]={['name']="M1128 Stryker MGS",['container']=false},
["AAA Vulcan M163 Air [21666lb]"]={['name']="Vulcan",['container']=true},
["AAA Vulcan M163 Skid [21577lb]"]={['name']="Vulcan",['container']=false},
["APC M1126 Stryker ICV [29542lb]"]={['name']="M1126 Stryker ICV",['container']=false},
["ATGM M1134 Stryker [30337lb]"]={['name']="M1134 Stryker ATGM",['container']=false},
["APC LAV-25 Air [22520lb]"]={['name']="LAV-25",['container']=true},
["APC LAV-25 Skid [22514lb]"]={['name']="LAV-25",['container']=false},
["M1025 HMMWV Air [6160lb]"]={['name']="Hummer",['container']=true},
["M1025 HMMWV Skid [6050lb]"]={['name']="Hummer",['container']=false},
["IFV M2A2 Bradley [34720lb]"]={['name']="M-2 Bradley",['container']=false},
["IFV MCV-80 [34720lb]"]={['name']="MCV-80",['container']=false},
["IFV BMP-1 [23232lb]"]={['name']="BMP-1",['container']=false},
["IFV BMP-2 [25168lb]"]={['name']="BMP-2",['container']=false},
["IFV BMP-3 [32912lb]"]={['name']="BMP-3",['container']=false},
["ARV BRDM-2 Air [12320lb]"]={['name']="BRDM-2",['container']=true},
["ARV BRDM-2 Skid [12210lb]"]={['name']="BRDM-2",['container']=false},
["APC BTR-80 Air [23936lb]"]={['name']="BTR-80",['container']=true},
["APC BTR-80 Skid [23826lb]"]={['name']="BTR-80",['container']=false},
["APC BTR-82A Air [24998lb]"]={['name']="BTR-82A",['container']=true},
["APC BTR-82A Skid [24888lb]"]={['name']="BTR-82A",['container']=false},
["SAM ROLAND ADS [34720lb]"]={['name']="Roland Radar",['container']=false},
["SAM ROLAND LN [34720b]"]={['name']="Roland ADS",['container']=false},
["SAM SA-13 STRELA [21624lb]"]={['name']="Strela-10M3",['container']=false},
["AAA ZSU-23-4 Shilka [32912lb]"]={['name']="ZSU-23-4 Shilka",['container']=false},
["SAM SA-19 Tunguska 2S6 [34720lb]"]={['name']="2S6 Tunguska",['container']=false},
["Transport UAZ-469 Air [3747lb]"]={['name']="UAZ-469",['container']=true},
["Transport UAZ-469 Skid [3630lb]"]={['name']="UAZ-469",['container']=false},
["AAA GEPARD [34720lb]"]={['name']="Gepard",['container']=false},
["SAM CHAPARRAL Air [21624lb]"]={['name']="M48 Chaparral",['container']=true},
["SAM CHAPARRAL Skid [21516lb]"]={['name']="M48 Chaparral",['container']=false},
["SAM LINEBACKER [34720lb]"]={['name']="M6 Linebacker",['container']=false},
["Transport URAL-375 [14815lb]"]={['name']="Ural-375",['container']=false},
["Transport M818 [16000lb]"]={['name']="M 818",['container']=false},
["IFV MARDER [34720lb]"]={['name']="Marder",['container']=false},
["Transport Tigr Air [15900lb]"]={['name']="Tigr_233036",['container']=true},
["Transport Tigr Skid [15730lb]"]={['name']="Tigr_233036",['container']=false},
["IFV TPZ FUCH [33440lb]"]={['name']="TPZ",['container']=false},
["IFV BMD-1 Air [18040lb]"]={['name']="BMD-1",['container']=true},
["IFV BMD-1 Skid [17930lb]"]={['name']="BMD-1",['container']=false},
["IFV BTR-D Air [18040lb]"]={['name']="BTR_D",['container']=true},
["IFV BTR-D Skid [17930lb]"]={['name']="BTR_D",['container']=false},
["EWR SBORKA Air [21624lb]"]={['name']="Dog Ear radar",['container']=true},
["EWR SBORKA Skid [21624lb]"]={['name']="Dog Ear radar",['container']=false},
["ART 2S9 NONA Air [19140lb]"]={['name']="SAU 2-C9",['container']=true},
["ART 2S9 NONA Skid [19030lb]"]={['name']="SAU 2-C9",['container']=false},
["ART GVOZDIKA [34720lb]"]={['name']="SAU Gvozdika",['container']=false},
["APC MTLB Air [26400lb]"]={['name']="MTLB",['container']=true},
["APC MTLB Skid [26290lb]"]={['name']="MTLB",['container']=false},
}
function CTLD_HERCULES:New(Coalition,Alias,CtldObject)
local self=BASE:Inherit(self,FSM:New())
if Coalition and type(Coalition)=="string"then
if Coalition=="blue"then
self.coalition=coalition.side.BLUE
self.coalitiontxt=Coalition
elseif Coalition=="red"then
self.coalition=coalition.side.RED
self.coalitiontxt=Coalition
elseif Coalition=="neutral"then
self.coalition=coalition.side.NEUTRAL
self.coalitiontxt=Coalition
else
self:E("ERROR: Unknown coalition in CTLD!")
end
else
self.coalition=Coalition
self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition))
end
if Alias then
self.alias=tostring(Alias)
else
self.alias="UNHCR"
if self.coalition then
if self.coalition==coalition.side.RED then
self.alias="Red CTLD Hercules"
elseif self.coalition==coalition.side.BLUE then
self.alias="Blue CTLD Hercules"
end
end
end
self.lid=string.format("%s (%s) | ",self.alias,self.coalitiontxt)
self.infantrytemplate="Infantry"
self.CTLD=CtldObject
self.verbose=true
self.j=0
self.carrierGroups={}
self.Cargo={}
self.ParatrooperCount={}
self.ObjectTracker={}
self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown")
self:HandleEvent(EVENTS.Shot,self._HandleShot)
self:I(self.lid.."Started")
self:CheckTemplates()
return self
end
function CTLD_HERCULES:CheckTemplates()
self:T(self.lid..'CheckTemplates')
self.Types["Paratroopers 10"]={
name=self.infantrytemplate,
container=false,
available=false,
}
local missing={}
local nomissing=0
local found={}
local nofound=0
for _index,_tab in pairs(self.Types)do
local outcometxt="MISSING"
if _DATABASE.Templates.Groups[_tab.name]then
outcometxt="OK"
self.Types[_index].available=true
found[_tab.name]=true
else
self.Types[_index].available=false
missing[_tab.name]=true
end
if self.verbose then
self:I(string.format(self.lid.."Checking template for %s (%s) ... %s",_index,_tab.name,outcometxt))
end
end
for _,_name in pairs(found)do
nofound=nofound+1
end
for _,_name in pairs(missing)do
nomissing=nomissing+1
end
self:I(string.format(self.lid.."Template Check Summary: Found %d, Missing %d, Total %d",nofound,nomissing,nofound+nomissing))
return self
end
function CTLD_HERCULES:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position,Cargo_Type_name,CargoHeading,Cargo_Country,GroupSpacing)
self:T(self.lid..'Soldier_SpawnGroup')
self:T(Cargo_Drop_Position)
local InjectTroopsType=CTLD_CARGO:New(nil,self.infantrytemplate,{self.infantrytemplate},CTLD_CARGO.Enum.TROOPS,true,true,10,nil,false,80)
local position=Cargo_Drop_Position:GetVec2()
local dropzone=ZONE_RADIUS:New("Infantry "..math.random(1,10000),position,100)
self.CTLD:InjectTroops(dropzone,InjectTroopsType)
return self
end
function CTLD_HERCULES:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position,Cargo_Type_name,CargoHeading,Cargo_Country)
self:T(self.lid.."Cargo_SpawnGroup")
self:T(Cargo_Type_name)
if Cargo_Type_name~='Container red 1'then
local InjectVehicleType=CTLD_CARGO:New(nil,Cargo_Type_name,{Cargo_Type_name},CTLD_CARGO.Enum.VEHICLE,true,true,1,nil,false,1000)
local position=Cargo_Drop_Position:GetVec2()
local dropzone=ZONE_RADIUS:New("Vehicle "..math.random(1,10000),position,100)
self.CTLD:InjectVehicles(dropzone,InjectVehicleType)
end
return self
end
function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Position,Cargo_Type_name,CargoHeading,dead,Cargo_Country)
self:T(self.lid.."Cargo_SpawnStatic")
self:T("Static "..Cargo_Type_name.." Dead "..tostring(dead))
local position=Cargo_Drop_Position:GetVec2()
local Zone=ZONE_RADIUS:New("Cargo Static "..math.random(1,10000),position,100)
if not dead then
local injectstatic=CTLD_CARGO:New(nil,"Cargo Static Group "..math.random(1,10000),"iso_container",CTLD_CARGO.Enum.STATIC,true,false,1,nil,true,4500,1)
self.CTLD:InjectStatics(Zone,injectstatic,true)
end
return self
end
function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direction,Cargo_Content_position,Cargo_Type_name,Cargo_over_water,Container_Enclosed,ParatrooperGroupSpawn,offload_cargo,all_cargo_survive_to_the_ground,all_cargo_gets_destroyed,destroy_cargo_dropped_without_parachute,Cargo_Country)
self:T(self.lid..'Cargo_SpawnObjects')
local CargoHeading=self.CargoHeading
if offload_cargo==true or ParatrooperGroupSpawn==true then
if ParatrooperGroupSpawn==true then
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country,0)
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country,5)
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country,10)
else
self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country)
end
else
if all_cargo_gets_destroyed==true or Cargo_over_water==true then
else
if all_cargo_survive_to_the_ground==true then
if ParatrooperGroupSpawn==true then
self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,true,Cargo_Country)
else
self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country)
end
if Container_Enclosed==true then
if ParatrooperGroupSpawn==false then
self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,"Hercules_Container_Parachute_Static",CargoHeading,false,Cargo_Country)
end
end
end
if destroy_cargo_dropped_without_parachute==true then
if Container_Enclosed==true then
if ParatrooperGroupSpawn==true then
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country,0)
else
self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country)
self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,"Hercules_Container_Parachute_Static",CargoHeading,false,Cargo_Country)
end
else
self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,true,Cargo_Country)
end
end
end
end
return self
end
function CTLD_HERCULES:Calculate_Object_Height_AGL(group)
self:T(self.lid.."Calculate_Object_Height_AGL")
if group.ClassName and group.ClassName=="GROUP"then
local gcoord=group:GetCoordinate()
local height=group:GetHeight()
local lheight=gcoord:GetLandHeight()
self:T(self.lid.."Height "..height-lheight)
return height-lheight
else
if group:isExist()then
local dcsposition=group:getPosition().p
local dcsvec2={x=dcsposition.x,y=dcsposition.z}
local height=math.floor(group:getPosition().p.y-land.getHeight(dcsvec2))
self.ObjectTracker[group.id_]=dcsposition
self:T(self.lid.."Height "..height)
return height
else
return 0
end
end
end
function CTLD_HERCULES:Check_SurfaceType(object)
self:T(self.lid.."Check_SurfaceType")
if object:isExist()then
return land.getSurfaceType({x=object:getPosition().p.x,y=object:getPosition().p.z})
else
return 1
end
end
function CTLD_HERCULES:Cargo_Track(cargo,initiator)
self:T(self.lid.."Cargo_Track")
local Cargo_Drop_initiator=initiator
if cargo.Cargo_Contents~=nil then
if self:Calculate_Object_Height_AGL(cargo.Cargo_Contents)<10 then
if self:Check_SurfaceType(cargo.Cargo_Contents)==2 or self:Check_SurfaceType(cargo.Cargo_Contents)==3 then
cargo.Cargo_over_water=true
end
local dcsvec3=self.ObjectTracker[cargo.Cargo_Contents.id_]or initiator:GetVec3()
self:T("SPAWNPOSITION: ")
self:T({dcsvec3})
local Vec2={
x=dcsvec3.x,
y=dcsvec3.z,
}
local vec3=COORDINATE:NewFromVec2(Vec2)
self.ObjectTracker[cargo.Cargo_Contents.id_]=nil
self:Cargo_SpawnObjects(Cargo_Drop_initiator,cargo.Cargo_Drop_Direction,vec3,cargo.Cargo_Type_name,cargo.Cargo_over_water,cargo.Container_Enclosed,cargo.ParatrooperGroupSpawn,cargo.offload_cargo,cargo.all_cargo_survive_to_the_ground,cargo.all_cargo_gets_destroyed,cargo.destroy_cargo_dropped_without_parachute,cargo.Cargo_Country)
if cargo.Cargo_Contents:isExist()then
cargo.Cargo_Contents:destroy()
end
cargo.scheduleFunctionID:Stop()
cargo={}
end
end
return self
end
function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_NorthCorrection(point)
self:T(self.lid.."Calculate_Cargo_Drop_initiator_NorthCorrection")
if not point.z then
point.z=point.y
point.y=0
end
local lat,lon=coord.LOtoLL(point)
local north_posit=coord.LLtoLO(lat+1,lon)
return math.atan2(north_posit.z-point.z,north_posit.x-point.x)
end
function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_Heading(Cargo_Drop_initiator)
self:T(self.lid.."Calculate_Cargo_Drop_initiator_Heading")
local Heading=Cargo_Drop_initiator:GetHeading()
Heading=Heading+self:Calculate_Cargo_Drop_initiator_NorthCorrection(Cargo_Drop_initiator:GetVec3())
if Heading<0 then
Heading=Heading+(2*math.pi)
end
return Heading+0.06
end
function CTLD_HERCULES:Cargo_Initialize(Initiator,Cargo_Contents,Cargo_Type_name,Container_Enclosed,SoldierGroup,ParatrooperGroupSpawnInit)
self:T(self.lid.."Cargo_Initialize")
local Cargo_Drop_initiator=Initiator:GetName()
if Cargo_Drop_initiator~=nil then
if ParatrooperGroupSpawnInit==true then
self:T("Paratrooper Drop")
if not self.ParatrooperCount[Cargo_Drop_initiator]then
self.ParatrooperCount[Cargo_Drop_initiator]=1
else
self.ParatrooperCount[Cargo_Drop_initiator]=self.ParatrooperCount[Cargo_Drop_initiator]+1
end
local Paratroopers=self.ParatrooperCount[Cargo_Drop_initiator]
self:T("Paratrooper Drop Number "..self.ParatrooperCount[Cargo_Drop_initiator])
local SpawnParas=false
if math.fmod(Paratroopers,10)==0 then
SpawnParas=true
end
self.j=self.j+1
self.Cargo[self.j]={}
self.Cargo[self.j].Cargo_Drop_Direction=self:Calculate_Cargo_Drop_initiator_Heading(Initiator)
self.Cargo[self.j].Cargo_Contents=Cargo_Contents
self.Cargo[self.j].Cargo_Type_name=Cargo_Type_name
self.Cargo[self.j].Container_Enclosed=Container_Enclosed
self.Cargo[self.j].ParatrooperGroupSpawn=SpawnParas
self.Cargo[self.j].Cargo_Country=Initiator:GetCountry()
if self:Calculate_Object_Height_AGL(Initiator)<5.0 then
self.Cargo[self.j].offload_cargo=true
elseif self:Calculate_Object_Height_AGL(Initiator)<10.0 then
self.Cargo[self.j].all_cargo_survive_to_the_ground=true
elseif self:Calculate_Object_Height_AGL(Initiator)<100.0 then
self.Cargo[self.j].all_cargo_gets_destroyed=true
else
self.Cargo[self.j].all_cargo_gets_destroyed=false
end
local timer=TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator)
self.Cargo[self.j].scheduleFunctionID=timer
timer:Start(1,1,600)
else
self.j=self.j+1
self.Cargo[self.j]={}
self.Cargo[self.j].Cargo_Drop_Direction=self:Calculate_Cargo_Drop_initiator_Heading(Initiator)
self.Cargo[self.j].Cargo_Contents=Cargo_Contents
self.Cargo[self.j].Cargo_Type_name=Cargo_Type_name
self.Cargo[self.j].Container_Enclosed=Container_Enclosed
self.Cargo[self.j].ParatrooperGroupSpawn=false
self.Cargo[self.j].Cargo_Country=Initiator:GetCountry()
if self:Calculate_Object_Height_AGL(Initiator)<5.0 then
self.Cargo[self.j].offload_cargo=true
elseif self:Calculate_Object_Height_AGL(Initiator)<10.0 then
self.Cargo[self.j].all_cargo_survive_to_the_ground=true
elseif self:Calculate_Object_Height_AGL(Initiator)<100.0 then
self.Cargo[self.j].all_cargo_gets_destroyed=true
else
self.Cargo[self.j].destroy_cargo_dropped_without_parachute=true
end
local timer=TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator)
self.Cargo[self.j].scheduleFunctionID=timer
timer:Start(1,1,600)
end
end
return self
end
function CTLD_HERCULES:SetType(key,cargoType,cargoNum)
self:T(self.lid.."SetType")
self.carrierGroups[key]['cargoType']=cargoType
self.carrierGroups[key]['cargoNum']=cargoNum
return self
end
function CTLD_HERCULES:_HandleShot(Cargo_Drop_Event)
self:T(self.lid.."Shot Event ID:"..Cargo_Drop_Event.id)
if Cargo_Drop_Event.id==EVENTS.Shot then
local GT_Name=""
local SoldierGroup=false
local ParatrooperGroupSpawnInit=false
local GT_DisplayName=Weapon.getDesc(Cargo_Drop_Event.weapon).typeName:sub(15,-1)
self:T(string.format("%sCargo_Drop_Event: %s",self.lid,Weapon.getDesc(Cargo_Drop_Event.weapon).typeName))
if(GT_DisplayName=="Squad 30 x Soldier [7950lb]")then
self:Cargo_Initialize(Cargo_Drop_Event.IniGroup,Cargo_Drop_Event.weapon,"Soldier M4 GRG",false,true,true)
end
if self.Types[GT_DisplayName]then
local GT_Name=self.Types[GT_DisplayName]['name']
local Cargo_Container_Enclosed=self.Types[GT_DisplayName]['container']
self:Cargo_Initialize(Cargo_Drop_Event.IniGroup,Cargo_Drop_Event.weapon,GT_Name,Cargo_Container_Enclosed)
end
end
return self
end
function CTLD_HERCULES:_HandleBirth(event)
self:T(self.lid.."Birth Event ID:"..event.id)
return self
end
end
CSAR={
ClassName="CSAR",
verbose=0,
lid="",
coalition=1,
coalitiontxt="blue",
FreeVHFFrequencies={},
UsedVHFFrequencies={},
takenOff={},
csarUnits={},
downedPilots={},
woundedGroups={},
landedStatus={},
addedTo={},
woundedGroups={},
inTransitGroups={},
smokeMarkers={},
heliVisibleMessage={},
heliCloseMessage={},
max_units=6,
hoverStatus={},
pilotDisabled={},
pilotLives={},
useprefix=true,
csarPrefix={},
template=nil,
mash={},
smokecolor=4,
rescues=0,
rescuedpilots=0,
limitmaxdownedpilots=true,
maxdownedpilots=10,
allheligroupset=nil,
topmenuname="CSAR",
ADFRadioPwr=1000,
PilotWeight=80,
}
CSAR.AircraftType={}
CSAR.AircraftType["SA342Mistral"]=2
CSAR.AircraftType["SA342Minigun"]=2
CSAR.AircraftType["SA342L"]=4
CSAR.AircraftType["SA342M"]=4
CSAR.AircraftType["UH-1H"]=8
CSAR.AircraftType["Mi-8MTV2"]=12
CSAR.AircraftType["Mi-8MT"]=12
CSAR.AircraftType["Mi-24P"]=8
CSAR.AircraftType["Mi-24V"]=8
CSAR.AircraftType["Bell-47"]=2
CSAR.AircraftType["UH-60L"]=10
CSAR.AircraftType["AH-64D_BLK_II"]=2
CSAR.AircraftType["Bronco-OV-10A"]=2
CSAR.version="1.0.17"
function CSAR:New(Coalition,Template,Alias)
local self=BASE:Inherit(self,FSM:New())
BASE:T({Coalition,Prefixes,Alias})
if Coalition and type(Coalition)=="string"then
if Coalition=="blue"then
self.coalition=coalition.side.BLUE
self.coalitiontxt=Coalition
elseif Coalition=="red"then
self.coalition=coalition.side.RED
self.coalitiontxt=Coalition
elseif Coalition=="neutral"then
self.coalition=coalition.side.NEUTRAL
self.coalitiontxt=Coalition
else
self:E("ERROR: Unknown coalition in CSAR!")
end
else
self.coalition=Coalition
self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition))
end
if Alias then
self.alias=tostring(Alias)
else
self.alias="Red Cross"
if self.coalition then
if self.coalition==coalition.side.RED then
self.alias="IFRC"
elseif self.coalition==coalition.side.BLUE then
self.alias="CSAR"
end
end
end
self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown")
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","PilotDown","*")
self:AddTransition("*","Approach","*")
self:AddTransition("*","Landed","*")
self:AddTransition("*","Boarded","*")
self:AddTransition("*","Returning","*")
self:AddTransition("*","Rescued","*")
self:AddTransition("*","KIA","*")
self:AddTransition("*","Load","*")
self:AddTransition("*","Save","*")
self:AddTransition("*","Stop","Stopped")
self.addedTo={}
self.allheligroupset={}
self.csarUnits={}
self.FreeVHFFrequencies={}
self.heliVisibleMessage={}
self.heliCloseMessage={}
self.hoverStatus={}
self.inTransitGroups={}
self.landedStatus={}
self.lastCrash={}
self.takenOff={}
self.smokeMarkers={}
self.UsedVHFFrequencies={}
self.woundedGroups={}
self.downedPilots={}
self.downedpilotcounter=1
self.rescues=0
self.rescuedpilots=0
self.csarOncrash=false
self.allowDownedPilotCAcontrol=false
self.enableForAI=false
self.smokecolor=4
self.coordtype=2
self.immortalcrew=true
self.invisiblecrew=false
self.messageTime=15
self.pilotRuntoExtractPoint=true
self.loadDistance=75
self.extractDistance=500
self.loadtimemax=135
self.radioSound="beacon.ogg"
self.beaconRefresher=29
self.allowFARPRescue=true
self.FARPRescueDistance=1000
self.max_units=6
self.useprefix=true
self.csarPrefix={"helicargo","MEDEVAC"}
self.template=Template or"generic"
self.mashprefix={"MASH"}
self.autosmoke=false
self.autosmokedistance=2000
self.limitmaxdownedpilots=true
self.maxdownedpilots=25
self:_GenerateVHFrequencies()
self.approachdist_far=5000
self.approachdist_near=3000
self.pilotmustopendoors=false
self.suppressmessages=false
self.rescuehoverheight=20
self.rescuehoverdistance=10
self.countryblue=country.id.USA
self.countryred=country.id.RUSSIA
self.countryneutral=country.id.UN_PEACEKEEPERS
self.csarUsePara=false
self.wetfeettemplate=nil
self.usewetfeet=false
self.allowbronco=false
self.ADFRadioPwr=1000
self.PilotWeight=80
self.useSRS=false
self.SRSPath="E:\\Program Files\\DCS-SimpleRadio-Standalone"
self.SRSchannel=300
self.SRSModulation=radio.modulation.AM
self.SRSport=5002
self.SRSCulture="en-GB"
self.SRSVoice=nil
self.SRSGPathToCredentials=nil
self.SRSVolume=1.0
self.SRSGender="male"
local AliaS=string.gsub(self.alias," ","_")
self.filename=string.format("CSAR_%s_Persist.csv",AliaS)
self.enableLoadSave=false
self.filepath=nil
self.saveinterval=600
return self
end
function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet)
self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername})
local DownedPilot={}
DownedPilot.desc=Description or""
DownedPilot.frequency=Frequency or 0
DownedPilot.index=self.downedpilotcounter
DownedPilot.name=Groupname or""
DownedPilot.originalUnit=OriginalUnit or""
DownedPilot.player=Playername or""
DownedPilot.side=Side or 0
DownedPilot.typename=Typename or""
DownedPilot.group=Group
DownedPilot.timestamp=0
DownedPilot.alive=true
DownedPilot.wetfeet=Wetfeet or false
local PilotTable=self.downedPilots
local counter=self.downedpilotcounter
PilotTable[counter]={}
PilotTable[counter]=DownedPilot
self:T({Table=PilotTable})
self.downedPilots=PilotTable
self.downedpilotcounter=self.downedpilotcounter+1
return self
end
function CSAR:_PilotsOnboard(_heliName)
self:T(self.lid.." _PilotsOnboard")
local count=0
if self.inTransitGroups[_heliName]then
for _,_group in pairs(self.inTransitGroups[_heliName])do
count=count+1
end
end
return count
end
function CSAR:_DoubleEjection(_unitname)
if self.lastCrash[_unitname]then
local _time=self.lastCrash[_unitname]
if timer.getTime()-_time<10 then
self:E(self.lid.."Caught double ejection!")
return true
end
end
self.lastCrash[_unitname]=timer.getTime()
return false
end
function CSAR:AddPlayerTask(PlayerTask)
self:T(self.lid.." AddPlayerTask")
if not self.PlayerTaskQueue then
self.PlayerTaskQueue=FIFO:New()
end
self.PlayerTaskQueue:Push(PlayerTask,PlayerTask.PlayerTaskNr)
return self
end
function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet)
self:T({country,point,frequency,tostring(wetfeet)})
local freq=frequency or 1000
local freq=freq/1000
for i=1,10 do
math.random(i,10000)
end
if point:IsSurfaceTypeWater()or wetfeet then
point.y=0
end
local template=self.template
if self.usewetfeet and wetfeet then
template=self.wetfeettemplate
end
local alias=string.format("Pilot %.2fkHz-%d",freq,math.random(1,99))
local coalition=self.coalition
local pilotcacontrol=self.allowDownedPilotCAcontrol
local _spawnedGroup=SPAWN
:NewWithAlias(template,alias)
:InitCoalition(coalition)
:InitCountry(country)
:InitAIOnOff(pilotcacontrol)
:InitDelayOff()
:SpawnFromCoordinate(point)
return _spawnedGroup,alias
end
function CSAR:_AddSpecialOptions(group)
self:T(self.lid.." _AddSpecialOptions")
self:T({group})
local immortalcrew=self.immortalcrew
local invisiblecrew=self.invisiblecrew
if immortalcrew then
local _setImmortal={
id='SetImmortal',
params={
value=true
}
}
group:SetCommand(_setImmortal)
end
if invisiblecrew then
local _setInvisible={
id='SetInvisible',
params={
value=true
}
}
group:SetCommand(_setInvisible)
end
group:OptionAlarmStateGreen()
group:OptionROEHoldFire()
return self
end
function CSAR:_AddCsar(_coalition,_country,_point,_typeName,_unitName,_playerName,_freq,noMessage,_description,forcedesc)
self:T(self.lid.." _AddCsar")
self:T({_coalition,_country,_point,_typeName,_unitName,_playerName,_freq,noMessage,_description})
local template=self.template
local wetfeet=false
local surface=_point:GetSurfaceType()
if surface==land.SurfaceType.WATER then
wetfeet=true
end
if not _freq then
_freq=self:_GenerateADFFrequency()
if not _freq then _freq=333000 end
end
local _spawnedGroup,_alias=self:_SpawnPilotInField(_country,_point,_freq,wetfeet)
local _typeName=_typeName or"Pilot"
if not noMessage then
if _freq~=0 then
self:_DisplayToAllSAR("MAYDAY MAYDAY! ".._typeName.." is down. ",self.coalition,self.messageTime)
else
self:_DisplayToAllSAR("Troops In Contact. ".._typeName.." requests CASEVAC. ",self.coalition,self.messageTime)
end
end
if(_freq and _freq~=0)then
self:_AddBeaconToGroup(_spawnedGroup,_freq)
end
self:_AddSpecialOptions(_spawnedGroup)
local _text=_description
if not forcedesc then
if _playerName~=nil then
if _freq~=0 then
_text="Pilot ".._playerName
else
_text="TIC - ".._playerName
end
elseif _unitName~=nil then
if _freq~=0 then
_text="AI Pilot of ".._unitName
else
_text="TIC - ".._unitName
end
end
end
self:T({_spawnedGroup,_alias})
local _GroupName=_spawnedGroup:GetName()or _alias
self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet)
self:_InitSARForPilot(_spawnedGroup,_unitName,_freq,noMessage)
return self
end
function CSAR:_SpawnCsarAtZone(_zone,_coalition,_description,_randomPoint,_nomessage,unitname,typename,forcedesc)
self:T(self.lid.." _SpawnCsarAtZone")
local freq=self:_GenerateADFFrequency()
local _triggerZone=nil
if type(_zone)=="string"then
_triggerZone=ZONE:New(_zone)
elseif type(_zone)=="table"and _zone.ClassName then
if string.find(_zone.ClassName,"ZONE",1)then
_triggerZone=_zone
end
end
if _triggerZone==nil then
self:E(self.lid.."ERROR: Can\'t find zone called ".._zone,10)
return
end
local _description=_description or"PoW"
local unitname=unitname or"Old Rusty"
local typename=typename or"Phantom II"
local pos={}
if _randomPoint then
local _pos=_triggerZone:GetRandomPointVec3()
pos=COORDINATE:NewFromVec3(_pos)
else
pos=_triggerZone:GetCoordinate()
end
local _country=0
if _coalition==coalition.side.BLUE then
_country=self.countryblue
elseif _coalition==coalition.side.RED then
_country=self.countryred
else
_country=self.countryneutral
end
self:_AddCsar(_coalition,_country,pos,typename,unitname,_description,freq,_nomessage,_description,forcedesc)
return self
end
function CSAR:SpawnCSARAtZone(Zone,Coalition,Description,RandomPoint,Nomessage,Unitname,Typename,Forcedesc)
self:_SpawnCsarAtZone(Zone,Coalition,Description,RandomPoint,Nomessage,Unitname,Typename,Forcedesc)
return self
end
function CSAR:_SpawnCASEVAC(_Point,_coalition,_description,_nomessage,unitname,typename,forcedesc)
self:T(self.lid.." _SpawnCASEVAC")
local _description=_description or"CASEVAC"
local unitname=unitname or"CASEVAC"
local typename=typename or"Ground Commander"
local pos={}
pos=_Point
local _country=0
if _coalition==coalition.side.BLUE then
_country=self.countryblue
elseif _coalition==coalition.side.RED then
_country=self.countryred
else
_country=self.countryneutral
end
self:_AddCsar(_coalition,_country,pos,typename,unitname,_description,0,_nomessage,_description,forcedesc)
return self
end
function CSAR:SpawnCASEVAC(Point,Coalition,Description,Nomessage,Unitname,Typename,Forcedesc)
self:_SpawnCASEVAC(Point,Coalition,Description,Nomessage,Unitname,Typename,Forcedesc)
return self
end
function CSAR:_EventHandler(EventData)
self:T(self.lid.." _EventHandler")
self:T({Event=EventData.id})
local _event=EventData
if self.enableForAI==false and _event.IniPlayerName==nil then
return self
end
if _event==nil or _event.initiator==nil then
return self
elseif _event.id==EVENTS.Takeoff then
self:T(self.lid.." Event unit - Takeoff")
local _coalition=_event.IniCoalition
if _coalition~=self.coalition then
return self
end
if _event.IniGroupName then
self.takenOff[_event.IniUnitName]=true
end
return self
elseif _event.id==EVENTS.PlayerEnterAircraft or _event.id==EVENTS.PlayerEnterUnit then
self:T(self.lid.." Event unit - Player Enter")
local _coalition=_event.IniCoalition
self:T("Coalition = "..UTILS.GetCoalitionName(_coalition))
if _coalition~=self.coalition then
return self
end
if _event.IniPlayerName then
self.takenOff[_event.IniPlayerName]=nil
end
self:T("Taken Off: "..tostring(_event.IniUnit:InAir(true)))
if _event.IniUnit:InAir(true)then
self.takenOff[_event.IniPlayerName]=true
end
local _unit=_event.IniUnit
local _group=_event.IniGroup
local function IsBronco(Group)
local grp=Group
local typename=grp:GetTypeName()
self:T(typename)
if typename=="Bronco-OV-10A"then return true end
return false
end
if _unit:IsHelicopter()or _group:IsHelicopter()or IsBronco(_group)then
self:_AddMedevacMenuItem()
end
return self
elseif(_event.id==EVENTS.PilotDead and self.csarOncrash==false)then
self:T(self.lid.." Event unit - Pilot Dead")
local _unit=_event.IniUnit
local _unitname=_event.IniUnitName
local _group=_event.IniGroup
if _unit==nil then
return self
end
local _coalition=_event.IniCoalition
if _coalition~=self.coalition then
return self
end
if self.takenOff[_event.IniUnitName]==true or _group:IsAirborne()then
if self:_DoubleEjection(_unitname)then
return self
end
else
self:T(self.lid.." Pilot has not taken off, ignore")
end
return self
elseif _event.id==EVENTS.PilotDead or _event.id==EVENTS.Ejection then
if _event.id==EVENTS.PilotDead and self.csarOncrash==false then
return self
end
self:T(self.lid.." Event unit - Pilot Ejected")
local _unit=_event.IniUnit
local _unitname=_event.IniUnitName
local _group=_event.IniGroup
self:T({_unit.UnitName,_unitname,_group.GroupName})
if _unit==nil then
self:T("Unit NIL!")
return self
end
local _coalition=_group:GetCoalition()
if _coalition~=self.coalition then
self:T("Wrong coalition! Coalition = "..UTILS.GetCoalitionName(_coalition))
return self
end
self:T("Airborne: "..tostring(_group:IsAirborne()))
self:T("Taken Off: "..tostring(self.takenOff[_event.IniUnitName]))
if not self.takenOff[_event.IniUnitName]and not _group:IsAirborne()then
self:T(self.lid.." Pilot has not taken off, ignore")
end
if self:_DoubleEjection(_unitname)then
self:T("Double Ejection!")
return self
end
if self.limitmaxdownedpilots and self:_ReachedPilotLimit()then
self:T("Maxed Downed Pilot!")
return self
end
local wetfeet=false
local initdcscoord=nil
local initcoord=nil
if _event.id==EVENTS.Ejection then
initdcscoord=_event.TgtDCSUnit:getPoint()
initcoord=COORDINATE:NewFromVec3(initdcscoord)
self:T({initdcscoord})
else
initdcscoord=_event.IniDCSUnit:getPoint()
initcoord=COORDINATE:NewFromVec3(initdcscoord)
self:T({initdcscoord})
end
local surface=initcoord:GetSurfaceType()
if surface==land.SurfaceType.WATER then
self:T("Wet feet!")
wetfeet=true
end
if self.csarUsePara==false or(self.csarUsePara and wetfeet)then
local _freq=self:_GenerateADFFrequency()
self:_AddCsar(_coalition,_unit:GetCountry(),initcoord,_unit:GetTypeName(),_unit:GetName(),_event.IniPlayerName,_freq,false,"none")
return self
end
elseif _event.id==EVENTS.Land then
self:T(self.lid.." Landing")
if _event.IniUnitName then
self.takenOff[_event.IniUnitName]=nil
end
if self.allowFARPRescue then
local _unit=_event.IniUnit
if _unit==nil then
self:T(self.lid.." Unit nil on landing")
return self
end
local _coalition=_event.IniGroup:GetCoalition()
if _coalition~=self.coalition then
self:T(self.lid.." Wrong coalition")
return self
end
self.takenOff[_event.IniUnitName]=nil
local _place=_event.Place
if _place==nil then
self:T(self.lid.." Landing Place Nil")
return self
end
if self.inTransitGroups[_event.IniUnitName]==nil then
return self
end
if _place:GetCoalition()==self.coalition or _place:GetCoalition()==coalition.side.NEUTRAL then
self:__Landed(2,_event.IniUnitName,_place)
self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true)
else
self:T(string.format("Airfield %d, Unit %d",_place:GetCoalition(),_unit:GetCoalition()))
end
end
return self
end
if(_event.id==EVENTS.LandingAfterEjection and self.csarUsePara==true)then
self:T("LANDING_AFTER_EJECTION")
local _LandingPos=COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
local _unitname="Aircraft"
local _typename="Ejected Pilot"
local _country=_event.initiator:getCountry()
local _coalition=coalition.getCountryCoalition(_country)
self:T("Country = ".._country.." Coalition = ".._coalition)
if _coalition==self.coalition then
local _freq=self:_GenerateADFFrequency()
self:I({coalition=_coalition,country=_country,coord=_LandingPos,name=_unitname,player=_event.IniPlayerName,freq=_freq})
self:_AddCsar(_coalition,_country,_LandingPos,nil,_unitname,_event.IniPlayerName,_freq,false,"none")
Unit.destroy(_event.initiator)
end
end
return self
end
function CSAR:_InitSARForPilot(_downedGroup,_GroupName,_freq,_nomessage)
self:T(self.lid.." _InitSARForPilot")
local _leader=_downedGroup:GetUnit(1)
local _groupName=_GroupName
local _freqk=_freq/1000
local _coordinatesText=self:_GetPositionOfWounded(_downedGroup)
local _leadername=_leader:GetName()
if not _nomessage then
if _freq~=0 then
local _text=string.format("%s requests SAR at %s, beacon at %.2f KHz",_groupName,_coordinatesText,_freqk)
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
else
local _text=string.format("Pickup Zone at %s.",_coordinatesText)
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
end
end
for _,_heliName in pairs(self.csarUnits)do
self:_CheckWoundedGroupStatus(_heliName,_groupName)
end
self:__PilotDown(2,_downedGroup,_freqk,_groupName,_coordinatesText)
return self
end
function CSAR:_CheckNameInDownedPilots(name)
local PilotTable=self.downedPilots
local found=false
local table=nil
for _,_pilot in pairs(PilotTable)do
if _pilot.name==name and _pilot.alive==true then
found=true
table=_pilot
break
end
end
return found,table
end
function CSAR:_RemoveNameFromDownedPilots(name,force)
local PilotTable=self.downedPilots
local found=false
for _index,_pilot in pairs(PilotTable)do
if _pilot.name==name then
self.downedPilots[_index].alive=false
end
end
return found
end
function CSAR:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
if not ShortCallsign or ShortCallsign==false then
self.ShortCallsign=false
else
self.ShortCallsign=true
end
self.Keepnumber=Keepnumber or false
self.CallsignTranslations=CallsignTranslations
return self
end
function CSAR:_GetCustomCallSign(UnitName)
local callsign=Unitname
local unit=UNIT:FindByName(UnitName)
if unit and unit:IsAlive()then
local group=unit:GetGroup()
callsign=group:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
end
return callsign
end
function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname)
self:T(self.lid.." _CheckWoundedGroupStatus")
local _heliName=heliname
local _woundedGroupName=woundedgroupname
self:T({Heli=_heliName,Downed=_woundedGroupName})
local _found,_downedpilot=self:_CheckNameInDownedPilots(_woundedGroupName)
if not _found then
self:T("...not found in list!")
return
end
local _woundedGroup=_downedpilot.group
if _woundedGroup~=nil and _woundedGroup:IsAlive()then
local _heliUnit=self:_GetSARHeli(_heliName)
local _lookupKeyHeli=_heliName.."_".._woundedGroupName
if _heliUnit==nil then
self.heliVisibleMessage[_lookupKeyHeli]=nil
self.heliCloseMessage[_lookupKeyHeli]=nil
self.landedStatus[_lookupKeyHeli]=nil
self:T("...heliunit nil!")
return
end
local _heliCoord=_heliUnit:GetCoordinate()
local _leaderCoord=_woundedGroup:GetCoordinate()
local _distance=self:_GetDistance(_heliCoord,_leaderCoord)
if(self.autosmoke==true)and(_distance<self.autosmokedistance)and(_distance~=-1)then
self:_PopSmokeForGroup(_woundedGroupName,_woundedGroup)
end
if _distance<self.approachdist_near and _distance>0 then
if self:_CheckCloseWoundedGroup(_distance,_heliUnit,_heliName,_woundedGroup,_woundedGroupName)==true then
_downedpilot.timestamp=timer.getAbsTime()
self:__Approach(-5,heliname,woundedgroupname)
end
elseif _distance>=self.approachdist_near and _distance<self.approachdist_far then
if self.heliVisibleMessage[_lookupKeyHeli]==nil then
local _pilotName=_downedpilot.desc
if self.autosmoke==true then
local dist=self.autosmokedistance/1000
local disttext=string.format("%.0fkm",dist)
if _SETTINGS:IsImperial()then
local dist=UTILS.MetersToNM(self.autosmokedistance)
disttext=string.format("%.0fnm",dist)
end
self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.",self:_GetCustomCallSign(_heliName),_pilotName,disttext),self.messageTime,false,true)
else
self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,false,true)
end
self.heliVisibleMessage[_lookupKeyHeli]=true
end
self.heliCloseMessage[_lookupKeyHeli]=nil
self.landedStatus[_lookupKeyHeli]=nil
_downedpilot.timestamp=timer.getAbsTime()
self:__Approach(-10,heliname,woundedgroupname)
end
else
self:T("...Downed Pilot KIA?!")
if not _downedpilot.alive then
self:_RemoveNameFromDownedPilots(_downedpilot.name,true)
end
end
return self
end
function CSAR:_PopSmokeForGroup(_woundedGroupName,_woundedLeader)
self:T(self.lid.." _PopSmokeForGroup")
local _lastSmoke=self.smokeMarkers[_woundedGroupName]
if _lastSmoke==nil or timer.getTime()>_lastSmoke then
local _smokecolor=self.smokecolor
local _smokecoord=_woundedLeader:GetCoordinate():Translate(6,math.random(1,360))
_smokecoord:Smoke(_smokecolor)
self.smokeMarkers[_woundedGroupName]=timer.getTime()+300
end
return self
end
function CSAR:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName)
self:T(self.lid.." _PickupUnit")
local _heliName=_heliUnit:GetName()
local _groups=self.inTransitGroups[_heliName]
local _unitsInHelicopter=self:_PilotsOnboard(_heliName)
if not _groups then
self.inTransitGroups[_heliName]={}
_groups=self.inTransitGroups[_heliName]
end
local _maxUnits=self.AircraftType[_heliUnit:GetTypeName()]
if _maxUnits==nil then
_maxUnits=self.max_units
end
if _unitsInHelicopter+1>_maxUnits then
self:_DisplayMessageToSAR(_heliUnit,string.format("%s, %s. We\'re already crammed with %d guys! Sorry!",_pilotName,self:_GetCustomCallSign(_heliName),_unitsInHelicopter,_unitsInHelicopter),self.messageTime,false,false,true)
return self
end
local found,downedgrouptable=self:_CheckNameInDownedPilots(_woundedGroupName)
local grouptable=downedgrouptable
self.inTransitGroups[_heliName][_woundedGroupName]=
{
originalUnit=grouptable.originalUnit,
woundedGroup=_woundedGroupName,
side=self.coalition,
desc=grouptable.desc,
player=grouptable.player,
}
_woundedGroup:Destroy(false)
self:_RemoveNameFromDownedPilots(_woundedGroupName,true)
self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s I\'m in! Get to the MASH ASAP! ",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,true,true)
self:_UpdateUnitCargoMass(_heliName)
self:__Boarded(5,_heliName,_woundedGroupName,grouptable.desc)
return self
end
function CSAR:_UpdateUnitCargoMass(_heliName)
self:T(self.lid.." _UpdateUnitCargoMass")
local calculatedMass=self:_PilotsOnboard(_heliName)*(self.PilotWeight or 80)
local Unit=UNIT:FindByName(_heliName)
if Unit then
Unit:SetUnitInternalCargo(calculatedMass)
end
return self
end
function CSAR:_OrderGroupToMoveToPoint(_leader,_destination)
self:T(self.lid.." _OrderGroupToMoveToPoint")
local group=_leader
local coordinate=_destination:GetVec2()
group:SetAIOn()
group:RouteToVec2(coordinate,5)
return self
end
function CSAR:_IsLoadingDoorOpen(unit_name)
self:T(self.lid.." _IsLoadingDoorOpen")
return UTILS.IsLoadingDoorOpen(unit_name)
end
function CSAR:_CheckCloseWoundedGroup(_distance,_heliUnit,_heliName,_woundedGroup,_woundedGroupName)
self:T(self.lid.." _CheckCloseWoundedGroup")
local _woundedLeader=_woundedGroup
local _lookupKeyHeli=_heliUnit:GetName().."_".._woundedGroupName
local _found,_pilotable=self:_CheckNameInDownedPilots(_woundedGroupName)
local _pilotName=_pilotable.desc
local _reset=true
if(_distance<500)then
if self.heliCloseMessage[_lookupKeyHeli]==nil then
if self.autosmoke==true then
self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s. You\'re close now! Land or hover at the smoke.",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,false,true)
else
self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,false,true)
end
self.heliCloseMessage[_lookupKeyHeli]=true
end
if not _heliUnit:InAir()then
if self.pilotRuntoExtractPoint==true then
if(_distance<self.extractDistance)then
local _time=self.landedStatus[_lookupKeyHeli]
if _time==nil then
self.landedStatus[_lookupKeyHeli]=math.floor((_distance-self.loadDistance)/3.6)
_time=self.landedStatus[_lookupKeyHeli]
_woundedGroup:OptionAlarmStateGreen()
self:_OrderGroupToMoveToPoint(_woundedGroup,_heliUnit:GetCoordinate())
self:_DisplayMessageToSAR(_heliUnit,"Wait till ".._pilotName.." gets in. \nETA ".._time.." more seconds.",self.messageTime,false)
else
_time=self.landedStatus[_lookupKeyHeli]-10
self.landedStatus[_lookupKeyHeli]=_time
end
if _distance<self.loadDistance+5 or _distance<=13 then
if self.pilotmustopendoors and(self:_IsLoadingDoorOpen(_heliName)==false)then
self:_DisplayMessageToSAR(_heliUnit,"Open the door to let me in!",self.messageTime,true,true)
return false
else
self.landedStatus[_lookupKeyHeli]=nil
self:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName)
return true
end
end
end
else
if(_distance<self.loadDistance)then
if self.pilotmustopendoors and(self:_IsLoadingDoorOpen(_heliName)==false)then
self:_DisplayMessageToSAR(_heliUnit,"Open the door to let me in!",self.messageTime,true,true)
return false
else
self:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName)
return true
end
end
end
else
local _unitsInHelicopter=self:_PilotsOnboard(_heliName)
local _maxUnits=self.AircraftType[_heliUnit:GetTypeName()]
if _maxUnits==nil then
_maxUnits=self.max_units
end
if _heliUnit:InAir()and _unitsInHelicopter+1<=_maxUnits then
if _distance<self.rescuehoverdistance then
local leaderheight=_woundedLeader:GetHeight()
if leaderheight<0 then leaderheight=0 end
local _height=_heliUnit:GetHeight()-leaderheight
if _height<=self.rescuehoverheight then
local _time=self.hoverStatus[_lookupKeyHeli]
if _time==nil then
self.hoverStatus[_lookupKeyHeli]=10
_time=10
else
_time=self.hoverStatus[_lookupKeyHeli]-10
self.hoverStatus[_lookupKeyHeli]=_time
end
if _time>0 then
self:_DisplayMessageToSAR(_heliUnit,"Hovering above ".._pilotName..". \n\nHold hover for ".._time.." seconds to winch them up. \n\nIf the countdown stops you\'re too far away!",self.messageTime,true)
else
if self.pilotmustopendoors and(self:_IsLoadingDoorOpen(_heliName)==false)then
self:_DisplayMessageToSAR(_heliUnit,"Open the door to let me in!",self.messageTime,true,true)
return false
else
self.hoverStatus[_lookupKeyHeli]=nil
self:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName)
return true
end
end
_reset=false
else
self:_DisplayMessageToSAR(_heliUnit,"Too high to winch ".._pilotName.." \nReduce height and hover for 10 seconds!",self.messageTime,true,true)
return false
end
end
end
end
end
if _reset then
self.hoverStatus[_lookupKeyHeli]=nil
end
if _distance<500 then
return true
else
return false
end
end
function CSAR:_ScheduledSARFlight(heliname,groupname,isairport)
self:T(self.lid.." _ScheduledSARFlight")
self:T({heliname,groupname})
local _heliUnit=self:_GetSARHeli(heliname)
local _woundedGroupName=groupname
if(_heliUnit==nil)then
self.inTransitGroups[heliname]=nil
return
end
if self.inTransitGroups[heliname]==nil or self.inTransitGroups[heliname][_woundedGroupName]==nil then
return
end
local _dist=self:_GetClosestMASH(_heliUnit)
if _dist==-1 then
return
end
if(_dist<self.FARPRescueDistance or isairport)and _heliUnit:InAir()==false then
if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname)==false then
self:_DisplayMessageToSAR(_heliUnit,"Open the door to let me out!",self.messageTime,true,true)
else
self:_RescuePilots(_heliUnit)
return
end
end
self:__Returning(-5,heliname,_woundedGroupName,isairport)
return self
end
function CSAR:_RescuePilots(_heliUnit)
self:T(self.lid.." _RescuePilots")
local _heliName=_heliUnit:GetName()
local _rescuedGroups=self.inTransitGroups[_heliName]
if _rescuedGroups==nil then
return
end
local PilotsSaved=self:_PilotsOnboard(_heliName)
self.inTransitGroups[_heliName]=nil
local _txt=string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!",self:_GetCustomCallSign(_heliName),PilotsSaved)
self:_DisplayMessageToSAR(_heliUnit,_txt,self.messageTime)
self:_UpdateUnitCargoMass(_heliName)
self:__Rescued(-1,_heliUnit,_heliName,PilotsSaved)
return self
end
function CSAR:_GetSARHeli(_unitName)
self:T(self.lid.." _GetSARHeli")
local unit=UNIT:FindByName(_unitName)
if unit and unit:IsAlive()then
return unit
else
return nil
end
end
function CSAR:_DisplayMessageToSAR(_unit,_text,_time,_clear,_speak,_override)
self:T(self.lid.." _DisplayMessageToSAR")
local group=_unit:GetGroup()
local _clear=_clear or nil
local _time=_time or self.messageTime
if _override or not self.suppressmessages then
local m=MESSAGE:New(_text,_time,"CSAR",_clear):ToGroup(group)
end
if _speak and self.useSRS then
self.SRSQueue:NewTransmission(_text,nil,self.msrs,nil,2)
end
return self
end
function CSAR:_GetPositionOfWounded(_woundedGroup)
self:T(self.lid.." _GetPositionOfWounded")
local _coordinate=_woundedGroup:GetCoordinate()
local _coordinatesText="None"
if _coordinate then
if self.coordtype==0 then
_coordinatesText=_coordinate:ToStringLLDDM()
elseif self.coordtype==1 then
_coordinatesText=_coordinate:ToStringLLDMS()
elseif self.coordtype==2 then
_coordinatesText=_coordinate:ToStringMGRS()
else
_coordinatesText=_coordinate:ToStringBULLS(self.coalition)
end
end
return _coordinatesText
end
function CSAR:_DisplayActiveSAR(_unitName)
self:T(self.lid.." _DisplayActiveSAR")
local _msg="Active MEDEVAC/SAR:"
local _heli=self:_GetSARHeli(_unitName)
if _heli==nil then
return
end
local _heliSide=self.coalition
local _csarList={}
local _DownedPilotTable=self.downedPilots
self:T({Table=_DownedPilotTable})
for _,_value in pairs(_DownedPilotTable)do
local _groupName=_value.name
self:T(string.format("Display Active Pilot: %s",tostring(_groupName)))
self:T({Table=_value})
local _woundedGroup=_value.group
if _woundedGroup and _value.alive then
local _coordinatesText=self:_GetPositionOfWounded(_woundedGroup)
local _helicoord=_heli:GetCoordinate()
local _woundcoord=_woundedGroup:GetCoordinate()
local _distance=self:_GetDistance(_helicoord,_woundcoord)
self:T({_distance=_distance})
local distancetext=""
if _SETTINGS:IsImperial()then
distancetext=string.format("%.1fnm",UTILS.MetersToNM(_distance))
else
distancetext=string.format("%.1fkm",_distance/1000.0)
end
if _value.frequency==0 then
table.insert(_csarList,{dist=_distance,msg=string.format("%s at %s - %s ",_value.desc,_coordinatesText,distancetext)})
else
table.insert(_csarList,{dist=_distance,msg=string.format("%s at %s - %.2f KHz ADF - %s ",_value.desc,_coordinatesText,_value.frequency/1000,distancetext)})
end
end
end
local function sortDistance(a,b)
return a.dist<b.dist
end
table.sort(_csarList,sortDistance)
for _,_line in pairs(_csarList)do
_msg=_msg.."\n".._line.msg
end
self:_DisplayMessageToSAR(_heli,_msg,self.messageTime*2,false,false,true)
return self
end
function CSAR:_GetClosestDownedPilot(_heli)
self:T(self.lid.." _GetClosestDownedPilot")
local _side=self.coalition
local _closestGroup=nil
local _shortestDistance=-1
local _distance=0
local _closestGroupInfo=nil
local _heliCoord=_heli:GetCoordinate()or _heli:GetCoordinate()
if _heliCoord==nil then
self:E("****Error obtaining coordinate!")
return nil
end
local DownedPilotsTable=self.downedPilots
for _,_groupInfo in UTILS.spairs(DownedPilotsTable)do
local _woundedName=_groupInfo.name
local _tempWounded=_groupInfo.group
if _tempWounded then
local _tempCoord=_tempWounded:GetCoordinate()
_distance=self:_GetDistance(_heliCoord,_tempCoord)
if _distance~=nil and(_shortestDistance==-1 or _distance<_shortestDistance)then
_shortestDistance=_distance
_closestGroup=_tempWounded
_closestGroupInfo=_groupInfo
end
end
end
return{pilot=_closestGroup,distance=_shortestDistance,groupInfo=_closestGroupInfo}
end
function CSAR:_SignalFlare(_unitName)
self:T(self.lid.." _SignalFlare")
local _heli=self:_GetSARHeli(_unitName)
if _heli==nil then
return
end
local _closest=self:_GetClosestDownedPilot(_heli)
local smokedist=8000
if self.approachdist_far>smokedist then smokedist=self.approachdist_far end
if _closest~=nil and _closest.pilot~=nil and _closest.distance>0 and _closest.distance<smokedist then
local _clockDir=self:_GetClockDirection(_heli,_closest.pilot)
local _distance=0
if _SETTINGS:IsImperial()then
_distance=string.format("%.1fnm",UTILS.MetersToNM(_closest.distance))
else
_distance=string.format("%.1fkm",_closest.distance)
end
local _msg=string.format("%s - Popping signal flare at your %s o\'clock. Distance %s",self:_GetCustomCallSign(_unitName),_clockDir,_distance)
self:_DisplayMessageToSAR(_heli,_msg,self.messageTime,false,true,true)
local _coord=_closest.pilot:GetCoordinate()
_coord:FlareRed(_clockDir)
else
local _distance=smokedist
if _SETTINGS:IsImperial()then
_distance=string.format("%.1fnm",UTILS.MetersToNM(smokedist))
else
_distance=string.format("%.1fkm",smokedist/1000)
end
self:_DisplayMessageToSAR(_heli,string.format("No Pilots within %s",_distance),self.messageTime,false,false,true)
end
return self
end
function CSAR:_DisplayToAllSAR(_message,_side,_messagetime)
self:T(self.lid.." _DisplayToAllSAR")
local messagetime=_messagetime or self.messageTime
for _,_unitName in pairs(self.csarUnits)do
local _unit=self:_GetSARHeli(_unitName)
if _unit and not self.suppressmessages then
self:_DisplayMessageToSAR(_unit,_message,_messagetime)
end
end
return self
end
function CSAR:_Reqsmoke(_unitName)
self:T(self.lid.." _Reqsmoke")
local _heli=self:_GetSARHeli(_unitName)
if _heli==nil then
return
end
local smokedist=8000
if smokedist<self.approachdist_far then smokedist=self.approachdist_far end
local _closest=self:_GetClosestDownedPilot(_heli)
if _closest~=nil and _closest.pilot~=nil and _closest.distance>0 and _closest.distance<smokedist then
local _clockDir=self:_GetClockDirection(_heli,_closest.pilot)
local _distance=0
if _SETTINGS:IsImperial()then
_distance=string.format("%.1fnm",UTILS.MetersToNM(_closest.distance))
else
_distance=string.format("%.1fkm",_closest.distance/1000)
end
local _msg=string.format("%s - Popping smoke at your %s o\'clock. Distance %s",self:_GetCustomCallSign(_unitName),_clockDir,_distance)
self:_DisplayMessageToSAR(_heli,_msg,self.messageTime,false,true,true)
local _coord=_closest.pilot:GetCoordinate()
local color=self.smokecolor
_coord:Smoke(color)
else
local _distance=0
if _SETTINGS:IsImperial()then
_distance=string.format("%.1fnm",UTILS.MetersToNM(smokedist))
else
_distance=string.format("%.1fkm",smokedist/1000)
end
self:_DisplayMessageToSAR(_heli,string.format("No Pilots within %s",_distance),self.messageTime,false,false,true)
end
return self
end
function CSAR:_GetClosestMASH(_heli)
self:T(self.lid.." _GetClosestMASH")
local _mashset=self.mash
local _mashes=_mashset:GetSetObjects()
local _shortestDistance=-1
local _distance=0
local _helicoord=_heli:GetCoordinate()
local function GetCloseAirbase(coordinate,Coalition,Category)
local a=coordinate:GetVec3()
local distmin=math.huge
local airbase=nil
for DCSairbaseID,DCSairbase in pairs(world.getAirbases(Coalition))do
local b=DCSairbase:getPoint()
local c=UTILS.VecSubstract(a,b)
local dist=UTILS.VecNorm(c)
if dist<distmin and(Category==nil or Category==DCSairbase:getDesc().category)then
distmin=dist
airbase=DCSairbase
end
end
return distmin
end
if self.allowFARPRescue then
local position=_heli:GetCoordinate()
local afb,distance=position:GetClosestAirbase(nil,self.coalition)
_shortestDistance=distance
end
for _,_mashUnit in pairs(_mashes)do
if _mashUnit and _mashUnit:IsAlive()then
local _mashcoord=_mashUnit:GetCoordinate()
_distance=self:_GetDistance(_helicoord,_mashcoord)
if _distance~=nil and(_shortestDistance==-1 or _distance<_shortestDistance)then
_shortestDistance=_distance
end
end
end
if _shortestDistance~=-1 then
return _shortestDistance
else
return-1
end
end
function CSAR:_CheckOnboard(_unitName)
self:T(self.lid.." _CheckOnboard")
local _unit=self:_GetSARHeli(_unitName)
if _unit==nil then
return
end
local _inTransit=self.inTransitGroups[_unitName]
if _inTransit==nil then
self:_DisplayMessageToSAR(_unit,"No Rescued Pilots onboard",self.messageTime,false,false,true)
else
local _text="Onboard - RTB to FARP/Airfield or MASH: "
for _,_onboard in pairs(self.inTransitGroups[_unitName])do
_text=_text.."\n".._onboard.desc
end
self:_DisplayMessageToSAR(_unit,_text,self.messageTime*2,false,false,true)
end
return self
end
function CSAR:_AddMedevacMenuItem()
self:T(self.lid.." _AddMedevacMenuItem")
local coalition=self.coalition
local allheligroupset=self.allheligroupset
local _allHeliGroups=allheligroupset:GetSetObjects()
local _UnitList={}
for _key,_group in pairs(_allHeliGroups)do
local _unit=_group:GetUnit(1)
if _unit then
if _unit:IsAlive()and _unit:IsPlayer()then
local unitName=_unit:GetName()
_UnitList[unitName]=unitName
end
end
end
self.csarUnits=_UnitList
for _,_unitName in pairs(self.csarUnits)do
local _unit=self:_GetSARHeli(_unitName)
if _unit then
local _group=_unit:GetGroup()
if _group then
local groupname=_group:GetName()
if self.addedTo[groupname]==nil then
self.addedTo[groupname]=true
local menuname=self.topmenuname or"CSAR"
local _rootPath=MENU_GROUP:New(_group,menuname)
local _rootMenu1=MENU_GROUP_COMMAND:New(_group,"List Active CSAR",_rootPath,self._DisplayActiveSAR,self,_unitName)
local _rootMenu2=MENU_GROUP_COMMAND:New(_group,"Check Onboard",_rootPath,self._CheckOnboard,self,_unitName)
local _rootMenu3=MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath,self._SignalFlare,self,_unitName)
local _rootMenu4=MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath,self._Reqsmoke,self,_unitName):Refresh()
end
end
end
end
return self
end
function CSAR:_GetDistance(_point1,_point2)
self:T(self.lid.." _GetDistance")
if _point1 and _point2 then
local distance1=_point1:Get2DDistance(_point2)
local distance2=_point1:DistanceFromPointVec2(_point2)
if distance1 and type(distance1)=="number"then
return distance1
elseif distance2 and type(distance2)=="number"then
return distance2
else
self:E("*****Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
else
self:E("******Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
end
function CSAR:_GenerateVHFrequencies()
self:T(self.lid.." _GenerateVHFrequencies")
local FreeVHFFrequencies={}
FreeVHFFrequencies=UTILS.GenerateVHFrequencies()
self.FreeVHFFrequencies=FreeVHFFrequencies
return self
end
function CSAR:_GenerateADFFrequency()
self:T(self.lid.." _GenerateADFFrequency")
if#self.FreeVHFFrequencies<=3 then
self.FreeVHFFrequencies=self.UsedVHFFrequencies
self.UsedVHFFrequencies={}
end
local _vhf=table.remove(self.FreeVHFFrequencies,math.random(#self.FreeVHFFrequencies))
return _vhf
end
function CSAR:_GetClockDirection(_heli,_group)
self:T(self.lid.." _GetClockDirection")
local _playerPosition=_heli:GetCoordinate()
local _targetpostions=_group:GetCoordinate()
local _heading=_heli:GetHeading()
local DirectionVec3=_playerPosition:GetDirectionVec3(_targetpostions)
local Angle=_playerPosition:GetAngleDegrees(DirectionVec3)
self:T(self.lid.." _GetClockDirection"..tostring(Angle).." "..tostring(_heading))
local hours=0
local clock=12
if _heading and Angle then
clock=12
clock=_heading-Angle
hours=(clock/30)*-1
clock=12+hours
clock=UTILS.Round(clock,0)
if clock>12 then clock=clock-12 end
end
return clock
end
function CSAR:_AddBeaconToGroup(_group,_freq)
self:T(self.lid.." _AddBeaconToGroup")
local _group=_group
if _group==nil then
for _i,_current in ipairs(self.UsedVHFFrequencies)do
if _current==_freq then
table.insert(self.FreeVHFFrequencies,_freq)
table.remove(self.UsedVHFFrequencies,_i)
end
end
return
end
if _group:IsAlive()then
local _radioUnit=_group:GetUnit(1)
if _radioUnit then
local Frequency=_freq
local name=_radioUnit:GetName()
local Sound="l10n/DEFAULT/"..self.radioSound
local vec3=_radioUnit:GetVec3()or _radioUnit:GetPositionVec3()or{x=0,y=0,z=0}
trigger.action.radioTransmission(Sound,vec3,0,false,Frequency,self.ADFRadioPwr or 1000,name..math.random(1,10000))
end
end
return self
end
function CSAR:_RefreshRadioBeacons()
self:T(self.lid.." _RefreshRadioBeacons")
if self:_CountActiveDownedPilots()>0 then
local PilotTable=self.downedPilots
for _,_pilot in pairs(PilotTable)do
self:T({_pilot})
local pilot=_pilot
local group=pilot.group
local frequency=pilot.frequency or 0
if group and group:IsAlive()and frequency>0 then
self:_AddBeaconToGroup(group,frequency)
end
end
end
return self
end
function CSAR:_CountActiveDownedPilots()
self:T(self.lid.." _CountActiveDownedPilots")
local PilotsInFieldN=0
for _,_unitName in pairs(self.downedPilots)do
self:T({_unitName.desc})
if _unitName.alive==true then
PilotsInFieldN=PilotsInFieldN+1
end
end
return PilotsInFieldN
end
function CSAR:_ReachedPilotLimit()
self:T(self.lid.." _ReachedPilotLimit")
local limit=self.maxdownedpilots
local islimited=self.limitmaxdownedpilots
local count=self:_CountActiveDownedPilots()
if islimited and(count>=limit)then
return true
else
return false
end
end
function CSAR:onafterStart(From,Event,To)
self:T({From,Event,To})
self:I(self.lid.."Started ("..self.version..")")
self:HandleEvent(EVENTS.Takeoff,self._EventHandler)
self:HandleEvent(EVENTS.Land,self._EventHandler)
self:HandleEvent(EVENTS.Ejection,self._EventHandler)
self:HandleEvent(EVENTS.LandingAfterEjection,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler)
self:HandleEvent(EVENTS.PilotDead,self._EventHandler)
if self.allowbronco then
local prefixes=self.csarPrefix or{}
self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterStart()
elseif self.useprefix then
local prefixes=self.csarPrefix or{}
self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart()
else
self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart()
end
self.mash=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart()
if self.wetfeettemplate then
self.usewetfeet=true
end
if self.useSRS then
local path=self.SRSPath
local modulation=self.SRSModulation
local channel=self.SRSchannel
self.msrs=MSRS:New(path,channel,modulation)
self.msrs:SetPort(self.SRSport)
self.msrs:SetLabel("CSAR")
self.msrs:SetCulture(self.SRSCulture)
self.msrs:SetCoalition(self.coalition)
self.msrs:SetVoice(self.SRSVoice)
self.msrs:SetGender(self.SRSGender)
if self.SRSGPathToCredentials then
self.msrs:SetGoogle(self.SRSGPathToCredentials)
end
self.msrs:SetVolume(self.SRSVolume)
self.msrs:SetLabel("CSAR")
self.SRSQueue=MSRSQUEUE:New("CSAR")
end
self:__Status(-10)
if self.enableLoadSave then
local interval=self.saveinterval
local filename=self.filename
local filepath=self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
function CSAR:_CheckDownedPilotTable()
local pilots=self.downedPilots
local npilots={}
for _ind,_entry in pairs(pilots)do
local _group=_entry.group
if _group:IsAlive()then
npilots[_ind]=_entry
else
if _entry.alive then
self:__KIA(1,_entry.desc)
end
end
end
self.downedPilots=npilots
return self
end
function CSAR:onbeforeStatus(From,Event,To)
self:T({From,Event,To})
self:_AddMedevacMenuItem()
if not self.BeaconTimer or(self.BeaconTimer and not self.BeaconTimer:IsRunning())then
self.BeaconTimer=TIMER:New(self._RefreshRadioBeacons,self)
self.BeaconTimer:Start(2,self.beaconRefresher)
end
self:_CheckDownedPilotTable()
for _,_sar in pairs(self.csarUnits)do
local PilotTable=self.downedPilots
for _,_entry in pairs(PilotTable)do
if _entry.alive then
local entry=_entry
local name=entry.name
local timestamp=entry.timestamp or 0
local now=timer.getAbsTime()
if now-timestamp>17 then
self:_CheckWoundedGroupStatus(_sar,name)
end
end
end
end
return self
end
function CSAR:onafterStatus(From,Event,To)
self:T({From,Event,To})
local NumberOfSARPilots=0
for _,_unitName in pairs(self.csarUnits)do
NumberOfSARPilots=NumberOfSARPilots+1
end
local PilotsInFieldN=self:_CountActiveDownedPilots()
local PilotsBoarded=0
for _,_unitName in pairs(self.inTransitGroups)do
for _,_units in pairs(_unitName)do
PilotsBoarded=PilotsBoarded+1
end
end
if self.verbose>0 then
local text=string.format("%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d",
self.lid,NumberOfSARPilots,PilotsInFieldN,self.maxdownedpilots,PilotsBoarded,self.rescues,self.rescuedpilots)
self:T(text)
if self.verbose<2 then
self:I(text)
elseif self.verbose>1 then
self:I(text)
local m=MESSAGE:New(text,"10","Status",true):ToCoalition(self.coalition)
end
end
self:__Status(-20)
return self
end
function CSAR:onafterStop(From,Event,To)
self:T({From,Event,To})
self:UnHandleEvent(EVENTS.Takeoff)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.LandingAfterEjection)
self:UnHandleEvent(EVENTS.PlayerEnterUnit)
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
self:UnHandleEvent(EVENTS.PilotDead)
self:T(self.lid.."Stopped.")
return self
end
function CSAR:onbeforeApproach(From,Event,To,Heliname,Woundedgroupname)
self:T({From,Event,To,Heliname,Woundedgroupname})
self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname)
return self
end
function CSAR:onbeforeBoarded(From,Event,To,Heliname,Woundedgroupname)
self:T({From,Event,To,Heliname,Woundedgroupname})
self:_ScheduledSARFlight(Heliname,Woundedgroupname)
local Unit=UNIT:FindByName(Heliname)
if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then
local playername=Unit:GetPlayerName()
local dropcoord=Unit:GetCoordinate()or COORDINATE:New(0,0,0)
local dropvec2=dropcoord:GetVec2()
self.PlayerTaskQueue:ForEach(
function(Task)
local task=Task
local subtype=task:GetSubType()
if Event==subtype and not task:IsDone()then
local targetzone=task.Target:GetObject()
if(targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and targetzone:IsVec2InZone(dropvec2))
or(string.find(task.CSARPilotName,Woundedgroupname))then
if task.Clients:HasUniqueID(playername)then
task:__Success(-1)
end
end
end
end
)
end
return self
end
function CSAR:onbeforeReturning(From,Event,To,Heliname,Woundedgroupname,IsAirPort)
self:T({From,Event,To,Heliname,Woundedgroupname})
self:_ScheduledSARFlight(Heliname,Woundedgroupname,IsAirPort)
return self
end
function CSAR:onbeforeRescued(From,Event,To,HeliUnit,HeliName,PilotsSaved)
self:T({From,Event,To,HeliName,HeliUnit})
self.rescues=self.rescues+1
self.rescuedpilots=self.rescuedpilots+PilotsSaved
local Unit=HeliUnit or UNIT:FindByName(HeliName)
if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then
local playername=Unit:GetPlayerName()
self.PlayerTaskQueue:ForEach(
function(Task)
local task=Task
local subtype=task:GetSubType()
if Event==subtype and not task:IsDone()then
if task.Clients:HasUniqueID(playername)then
task:__Success(-1)
end
end
end
)
end
return self
end
function CSAR:onbeforePilotDown(From,Event,To,Group,Frequency,Leadername,CoordinatesText)
self:T({From,Event,To,Group,Frequency,Leadername,CoordinatesText})
return self
end
function CSAR:onbeforeLanded(From,Event,To,HeliName,Airbase)
self:T({From,Event,To,HeliName,Airbase})
return self
end
function CSAR:onbeforeSave(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
if not io then
self:E(self.lid.."ERROR: io not desanitized. Can't save current state.")
return false
end
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
return true
end
function CSAR:onafterSave(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
local function _savefile(filename,data)
local f=assert(io.open(filename,"wb"))
f:write(data)
f:close()
end
if lfs then
path=self.filepath or lfs.writedir()
end
filename=filename or self.filename
if path~=nil then
filename=path.."\\"..filename
end
local pilots=self.downedPilots
local data="playerName,x,y,z,coalition,country,description,typeName,unitName,freq\n"
local n=0
for _,_grp in pairs(pilots)do
local DownedPilot=_grp
if DownedPilot and DownedPilot.alive then
local playerName=DownedPilot.player
local group=DownedPilot.group
local coalition=group:GetCoalition()
local country=group:GetCountry()
local description=DownedPilot.desc
local typeName=DownedPilot.typename
local freq=DownedPilot.frequency
local location=group:GetVec3()
local unitName=DownedPilot.originalUnit
local txt=string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq)
self:I(self.lid.."Saving to CSAR File: "..txt)
data=data..txt
end
end
_savefile(filename,data)
if self.enableLoadSave then
local interval=self.saveinterval
local filename=self.filename
local filepath=self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
function CSAR:onbeforeLoad(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
filename=filename or self.filename
path=path or self.filepath
if not io then
self:E(self.lid.."WARNING: io not desanitized. Cannot load file.")
return false
end
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
if lfs then
path=path or lfs.writedir()
end
if path~=nil then
filename=path.."\\"..filename
end
local exists=_fileexists(filename)
if exists then
return true
else
self:E(self.lid..string.format("WARNING: State file %s might not exist.",filename))
return false
end
end
function CSAR:onafterLoad(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
local function _loadfile(filename)
local f=assert(io.open(filename,"rb"))
local data=f:read("*all")
f:close()
return data
end
filename=filename or self.filename
path=path or self.filepath
if lfs then
path=path or lfs.writedir()
end
if path~=nil then
filename=path.."\\"..filename
end
local text=string.format("Loading CSAR state from file %s",filename)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:I(self.lid..text)
local file=assert(io.open(filename,"rb"))
local loadeddata={}
for line in file:lines()do
loadeddata[#loadeddata+1]=line
end
file:close()
table.remove(loadeddata,1)
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local playerName=dataset[1]
local vec3={}
vec3.x=tonumber(dataset[2])
vec3.y=tonumber(dataset[3])
vec3.z=tonumber(dataset[4])
local point=COORDINATE:NewFromVec3(vec3)
local coalition=dataset[5]
local country=dataset[6]
local description=dataset[7]
local typeName=dataset[8]
local unitName=dataset[9]
local freq=dataset[10]
self:_AddCsar(coalition,country,point,typeName,unitName,playerName,freq,nil,description,nil)
end
return self
end
AI_BALANCER={
ClassName="AI_BALANCER",
PatrolZones={},
AIGroups={},
Earliest=5,
Latest=60,
}
function AI_BALANCER:New(SetClient,SpawnAI)
local self=BASE:Inherit(self,FSM_SET:New(SET_GROUP:New()))
self:SetStartState("None")
self:AddTransition("*","Monitor","Monitoring")
self:AddTransition("*","Spawn","Spawning")
self:AddTransition("Spawning","Spawned","Spawned")
self:AddTransition("*","Destroy","Destroying")
self:AddTransition("*","Return","Returning")
self.SetClient=SetClient
self.SetClient:FilterOnce()
self.SpawnAI=SpawnAI
self.SpawnQueue={}
self.ToNearestAirbase=false
self.ToHomeAirbase=false
self:__Monitor(1)
return self
end
function AI_BALANCER:InitSpawnInterval(Earliest,Latest)
self.Earliest=Earliest
self.Latest=Latest
return self
end
function AI_BALANCER:ReturnToNearestAirbases(ReturnThresholdRange,ReturnAirbaseSet)
self.ToNearestAirbase=true
self.ReturnThresholdRange=ReturnThresholdRange
self.ReturnAirbaseSet=ReturnAirbaseSet
end
function AI_BALANCER:ReturnToHomeAirbase(ReturnThresholdRange)
self.ToHomeAirbase=true
self.ReturnThresholdRange=ReturnThresholdRange
end
function AI_BALANCER:onenterSpawning(SetGroup,From,Event,To,ClientName)
local AIGroup=self.SpawnAI:Spawn()
if AIGroup then
AIGroup:T({"Spawning new AIGroup",ClientName=ClientName})
SetGroup:Remove(ClientName)
SetGroup:Add(ClientName,AIGroup)
self.SpawnQueue[ClientName]=nil
self:Spawned(AIGroup)
end
end
function AI_BALANCER:onenterDestroying(SetGroup,From,Event,To,ClientName,AIGroup)
AIGroup:Destroy()
SetGroup:Flush(self)
SetGroup:Remove(ClientName)
SetGroup:Flush(self)
end
function AI_BALANCER:onenterReturning(SetGroup,From,Event,To,AIGroup)
local AIGroupTemplate=AIGroup:GetTemplate()
if self.ToHomeAirbase==true then
local WayPointCount=#AIGroupTemplate.route.points
local SwitchWayPointCommand=AIGroup:CommandSwitchWayPoint(1,WayPointCount,1)
AIGroup:SetCommand(SwitchWayPointCommand)
AIGroup:MessageToRed("Returning to home base ...",30)
else
local PointVec2=POINT_VEC2:New(AIGroup:GetVec2().x,AIGroup:GetVec2().y)
local ClosestAirbase=self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2(PointVec2)
self:T(ClosestAirbase.AirbaseName)
AIGroup:RouteRTB(ClosestAirbase)
end
end
function AI_BALANCER:onenterMonitoring(SetGroup)
self:T2({self.SetClient:Count()})
self.SetClient:ForEachClient(
function(Client)
self:T3(Client.ClientName)
local AIGroup=self.Set:Get(Client.UnitName)
if AIGroup then self:T({AIGroup=AIGroup:GetName(),IsAlive=AIGroup:IsAlive()})end
if Client:IsAlive()==true then
if AIGroup and AIGroup:IsAlive()==true then
if self.ToNearestAirbase==false and self.ToHomeAirbase==false then
self:Destroy(Client.UnitName,AIGroup)
else
local PlayerInRange={Value=false}
local RangeZone=ZONE_RADIUS:New('RangeZone',AIGroup:GetVec2(),self.ReturnThresholdRange)
self:T2(RangeZone)
_DATABASE:ForEachPlayerUnit(
function(RangeTestUnit,RangeZone,AIGroup,PlayerInRange)
self:T2({PlayerInRange,RangeTestUnit.UnitName,RangeZone.ZoneName})
if RangeTestUnit:IsInZone(RangeZone)==true then
self:T2("in zone")
if RangeTestUnit:GetCoalition()~=AIGroup:GetCoalition()then
self:T2("in range")
PlayerInRange.Value=true
end
end
end,
function(RangeZone,AIGroup,PlayerInRange)
if PlayerInRange.Value==false then
self:Return(AIGroup)
end
end
,RangeZone,AIGroup,PlayerInRange
)
end
self.Set:Remove(Client.UnitName)
end
else
if not AIGroup or not AIGroup:IsAlive()==true then
self:T("Client "..Client.UnitName.." not alive.")
self:T({Queue=self.SpawnQueue[Client.UnitName]})
if not self.SpawnQueue[Client.UnitName]then
self:__Spawn(math.random(self.Earliest,self.Latest),Client.UnitName)
self.SpawnQueue[Client.UnitName]=true
self:T("New AI Spawned for Client "..Client.UnitName)
end
end
end
return true
end
)
self:__Monitor(10)
end
AI_AIR={
ClassName="AI_AIR",
}
AI_AIR.TaskDelay=0.5
function AI_AIR:New(AIGroup)
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
self:SetControllable(AIGroup)
self:SetStartState("Stopped")
self:AddTransition("*","Queue","Queued")
self:AddTransition("*","Start","Started")
self:AddTransition("*","Stop","Stopped")
self:AddTransition("*","Status","*")
self:AddTransition("*","RTB","*")
self:AddTransition("Patrolling","Refuel","Refuelling")
self:AddTransition("*","Takeoff","Airborne")
self:AddTransition("*","Return","Returning")
self:AddTransition("*","Hold","Holding")
self:AddTransition("*","Home","Home")
self:AddTransition("*","LostControl","LostControl")
self:AddTransition("*","Fuel","Fuel")
self:AddTransition("*","Damaged","Damaged")
self:AddTransition("*","Eject","*")
self:AddTransition("*","Crash","Crashed")
self:AddTransition("*","PilotDead","*")
self.IdleCount=0
self.RTBSpeedMaxFactor=0.6
self.RTBSpeedMinFactor=0.5
return self
end
function GROUP:OnEventTakeoff(EventData,Fsm)
Fsm:Takeoff()
self:UnHandleEvent(EVENTS.Takeoff)
end
function AI_AIR:SetDispatcher(Dispatcher)
self.Dispatcher=Dispatcher
end
function AI_AIR:GetDispatcher()
return self.Dispatcher
end
function AI_AIR:SetTargetDistance(Coordinate)
local CurrentCoord=self.Controllable:GetCoordinate()
self.TargetDistance=CurrentCoord:Get2DDistance(Coordinate)
self.ClosestTargetDistance=(not self.ClosestTargetDistance or self.ClosestTargetDistance>self.TargetDistance)and self.TargetDistance or self.ClosestTargetDistance
end
function AI_AIR:ClearTargetDistance()
self.TargetDistance=nil
self.ClosestTargetDistance=nil
end
function AI_AIR:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed)
self:F2({PatrolMinSpeed,PatrolMaxSpeed})
self.PatrolMinSpeed=PatrolMinSpeed
self.PatrolMaxSpeed=PatrolMaxSpeed
end
function AI_AIR:SetRTBSpeed(RTBMinSpeed,RTBMaxSpeed)
self:F({RTBMinSpeed,RTBMaxSpeed})
self.RTBMinSpeed=RTBMinSpeed
self.RTBMaxSpeed=RTBMaxSpeed
end
function AI_AIR:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude)
self:F2({PatrolFloorAltitude,PatrolCeilingAltitude})
self.PatrolFloorAltitude=PatrolFloorAltitude
self.PatrolCeilingAltitude=PatrolCeilingAltitude
end
function AI_AIR:SetHomeAirbase(HomeAirbase)
self:F2({HomeAirbase})
self.HomeAirbase=HomeAirbase
end
function AI_AIR:SetTanker(TankerName)
self:F2({TankerName})
self.TankerName=TankerName
end
function AI_AIR:SetDisengageRadius(DisengageRadius)
self:F2({DisengageRadius})
self.DisengageRadius=DisengageRadius
end
function AI_AIR:SetStatusOff()
self:F2()
self.CheckStatus=false
end
function AI_AIR:SetFuelThreshold(FuelThresholdPercentage,OutOfFuelOrbitTime)
self.FuelThresholdPercentage=FuelThresholdPercentage
self.OutOfFuelOrbitTime=OutOfFuelOrbitTime
self.Controllable:OptionRTBBingoFuel(false)
return self
end
function AI_AIR:SetDamageThreshold(PatrolDamageThreshold)
self.PatrolManageDamage=true
self.PatrolDamageThreshold=PatrolDamageThreshold
return self
end
function AI_AIR:onafterStart(Controllable,From,Event,To)
self:__Status(10)
self:HandleEvent(EVENTS.PilotDead,self.OnPilotDead)
self:HandleEvent(EVENTS.Crash,self.OnCrash)
self:HandleEvent(EVENTS.Ejection,self.OnEjection)
Controllable:OptionROEHoldFire()
Controllable:OptionROTVertical()
end
function AI_AIR:onafterReturn(Controllable,From,Event,To)
self:__RTB(self.TaskDelay)
end
function AI_AIR:onbeforeStatus()
return self.CheckStatus
end
function AI_AIR:onafterStatus()
if self.Controllable and self.Controllable:IsAlive()then
local RTB=false
local DistanceFromHomeBase=self.HomeAirbase:GetCoordinate():Get2DDistance(self.Controllable:GetCoordinate())
if not self:Is("Holding")and not self:Is("Returning")then
local DistanceFromHomeBase=self.HomeAirbase:GetCoordinate():Get2DDistance(self.Controllable:GetCoordinate())
if DistanceFromHomeBase>self.DisengageRadius then
self:I(self.Controllable:GetName().." is too far from home base, RTB!")
self:Hold(300)
RTB=false
end
end
if not self:Is("Fuel")and not self:Is("Home")and not self:is("Refuelling")then
local Fuel=self.Controllable:GetFuelMin()
if Fuel<self.FuelThresholdPercentage then
if self.TankerName then
self:I(self.Controllable:GetName().." is out of fuel: "..Fuel.." ... Refuelling at Tanker!")
self:Refuel()
else
self:I(self.Controllable:GetName().." is out of fuel: "..Fuel.." ... RTB!")
local OldAIControllable=self.Controllable
local OrbitTask=OldAIControllable:TaskOrbitCircle(math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude),self.PatrolMinSpeed)
local TimedOrbitTask=OldAIControllable:TaskControlled(OrbitTask,OldAIControllable:TaskCondition(nil,nil,nil,nil,self.OutOfFuelOrbitTime,nil))
OldAIControllable:SetTask(TimedOrbitTask,10)
self:Fuel()
RTB=true
end
else
end
end
if self:Is("Fuel")and not self:Is("Home")and not self:is("Refuelling")then
RTB=true
end
local Damage=self.Controllable:GetLife()
local InitialLife=self.Controllable:GetLife0()
if(Damage/InitialLife)<self.PatrolDamageThreshold then
self:I(self.Controllable:GetName().." is damaged: "..Damage.." ... RTB!")
self:Damaged()
RTB=true
self:SetStatusOff()
end
if self.Controllable:HasTask()==false then
if not self:Is("Started")and
not self:Is("Stopped")and
not self:Is("Fuel")and
not self:Is("Damaged")and
not self:Is("Home")then
if self.IdleCount>=10 then
if Damage~=InitialLife then
self:Damaged()
else
self:I(self.Controllable:GetName().." control lost! ")
self:LostControl()
end
else
self.IdleCount=self.IdleCount+1
end
end
else
self.IdleCount=0
end
if RTB==true then
self:__RTB(self.TaskDelay)
end
if not self:Is("Home")then
self:__Status(10)
end
end
end
function AI_AIR.RTBRoute(AIGroup,Fsm)
AIGroup:F({"AI_AIR.RTBRoute:",AIGroup:GetName()})
if AIGroup:IsAlive()then
Fsm:RTB()
end
end
function AI_AIR.RTBHold(AIGroup,Fsm)
AIGroup:F({"AI_AIR.RTBHold:",AIGroup:GetName()})
if AIGroup:IsAlive()then
Fsm:__RTB(Fsm.TaskDelay)
Fsm:Return()
local Task=AIGroup:TaskOrbitCircle(4000,400)
AIGroup:SetTask(Task)
end
end
function AI_AIR:SetRTBSpeedFactors(MinFactor,MaxFactor)
self.RTBSpeedMaxFactor=MaxFactor or 0.6
self.RTBSpeedMinFactor=MinFactor or 0.5
return self
end
function AI_AIR:onafterRTB(AIGroup,From,Event,To)
self:F({AIGroup,From,Event,To})
if AIGroup and AIGroup:IsAlive()then
self:T("Group "..AIGroup:GetName().." ... RTB! ( "..self:GetState().." )")
self:ClearTargetDistance()
AIGroup:OptionProhibitAfterburner(true)
local EngageRoute={}
local FromCoord=AIGroup:GetCoordinate()
local ToTargetCoord=self.HomeAirbase:GetCoordinate()
local ToTargetVec3=ToTargetCoord:GetVec3()
ToTargetVec3.y=ToTargetCoord:GetLandHeight()+3000
local ToTargetCoord2=COORDINATE:NewFromVec3(ToTargetVec3)
if not self.RTBMinSpeed or not self.RTBMaxSpeed then
local RTBSpeedMax=AIGroup:GetSpeedMax()
local RTBSpeedMaxFactor=self.RTBSpeedMaxFactor or 0.6
local RTBSpeedMinFactor=self.RTBSpeedMinFactor or 0.5
self:SetRTBSpeed(RTBSpeedMax*RTBSpeedMinFactor,RTBSpeedMax*RTBSpeedMaxFactor)
end
local RTBSpeed=math.random(self.RTBMinSpeed,self.RTBMaxSpeed)
local Distance=FromCoord:Get2DDistance(ToTargetCoord2)
local ToAirbaseCoord=ToTargetCoord2
if Distance<5000 then
self:I("RTB and near the airbase!")
self:Home()
return
end
if not AIGroup:InAir()==true then
self:I("Not anymore in the air, considered Home.")
self:Home()
return
end
local FromRTBRoutePoint=FromCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
RTBSpeed,
true
)
local ToRTBRoutePoint=ToAirbaseCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
RTBSpeed,
true
)
EngageRoute[#EngageRoute+1]=FromRTBRoutePoint
EngageRoute[#EngageRoute+1]=ToRTBRoutePoint
local Tasks={}
Tasks[#Tasks+1]=AIGroup:TaskFunction("AI_AIR.RTBRoute",self)
EngageRoute[#EngageRoute].task=AIGroup:TaskCombo(Tasks)
AIGroup:OptionROEHoldFire()
AIGroup:OptionROTEvadeFire()
AIGroup:Route(EngageRoute,self.TaskDelay)
end
end
function AI_AIR:onafterHome(AIGroup,From,Event,To)
self:F({AIGroup,From,Event,To})
self:I("Group "..self.Controllable:GetName().." ... Home! ( "..self:GetState().." )")
if AIGroup and AIGroup:IsAlive()then
end
end
function AI_AIR:onafterHold(AIGroup,From,Event,To,HoldTime)
self:F({AIGroup,From,Event,To})
self:I("Group "..self.Controllable:GetName().." ... Holding! ( "..self:GetState().." )")
if AIGroup and AIGroup:IsAlive()then
local OrbitTask=AIGroup:TaskOrbitCircle(math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude),self.PatrolMinSpeed)
local TimedOrbitTask=AIGroup:TaskControlled(OrbitTask,AIGroup:TaskCondition(nil,nil,nil,nil,HoldTime,nil))
local RTBTask=AIGroup:TaskFunction("AI_AIR.RTBHold",self)
local OrbitHoldTask=AIGroup:TaskOrbitCircle(4000,self.PatrolMinSpeed)
AIGroup:SetTask(AIGroup:TaskCombo({TimedOrbitTask,RTBTask,OrbitHoldTask}),1)
end
end
function AI_AIR.Resume(AIGroup,Fsm)
AIGroup:I({"AI_AIR.Resume:",AIGroup:GetName()})
if AIGroup:IsAlive()then
Fsm:__RTB(Fsm.TaskDelay)
end
end
function AI_AIR:onafterRefuel(AIGroup,From,Event,To)
self:F({AIGroup,From,Event,To})
if AIGroup and AIGroup:IsAlive()then
local Tanker=GROUP:FindByName(self.TankerName)
if Tanker and Tanker:IsAlive()and Tanker:IsAirPlane()then
self:I("Group "..self.Controllable:GetName().." ... Refuelling! State="..self:GetState()..", Refuelling tanker "..self.TankerName)
local RefuelRoute={}
local FromRefuelCoord=AIGroup:GetCoordinate()
local ToRefuelCoord=Tanker:GetCoordinate()
local ToRefuelSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
local FromRefuelRoutePoint=FromRefuelCoord:WaypointAir(self.PatrolAltType,POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToRefuelSpeed,true)
local ToRefuelRoutePoint=Tanker:GetCoordinate():WaypointAir(self.PatrolAltType,POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToRefuelSpeed,true)
self:F({ToRefuelSpeed=ToRefuelSpeed})
RefuelRoute[#RefuelRoute+1]=FromRefuelRoutePoint
RefuelRoute[#RefuelRoute+1]=ToRefuelRoutePoint
AIGroup:OptionROEHoldFire()
AIGroup:OptionROTEvadeFire()
local classname=self:GetClassName()
if classname=="AI_A2A_CAP"then
classname="AI_AIR_PATROL"
end
env.info("FF refueling classname="..classname)
local Tasks={}
Tasks[#Tasks+1]=AIGroup:TaskRefueling()
Tasks[#Tasks+1]=AIGroup:TaskFunction(classname..".Resume",self)
RefuelRoute[#RefuelRoute].task=AIGroup:TaskCombo(Tasks)
AIGroup:Route(RefuelRoute,self.TaskDelay)
else
self:RTB()
end
end
end
function AI_AIR:onafterDead()
self:SetStatusOff()
end
function AI_AIR:OnCrash(EventData)
if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then
if#self.Controllable:GetUnits()==1 then
self:__Crash(self.TaskDelay,EventData)
end
end
end
function AI_AIR:OnEjection(EventData)
if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then
self:__Eject(self.TaskDelay,EventData)
end
end
function AI_AIR:OnPilotDead(EventData)
if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then
self:__PilotDead(self.TaskDelay,EventData)
end
end
AI_AIR_PATROL={
ClassName="AI_AIR_PATROL",
}
function AI_AIR_PATROL:New(AI_Air,AIGroup,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
local self=BASE:Inherit(self,AI_Air)
local SpeedMax=AIGroup:GetSpeedMax()
self.PatrolZone=PatrolZone
self.PatrolFloorAltitude=PatrolFloorAltitude or 1000
self.PatrolCeilingAltitude=PatrolCeilingAltitude or 1500
self.PatrolMinSpeed=PatrolMinSpeed or SpeedMax*0.5
self.PatrolMaxSpeed=PatrolMaxSpeed or SpeedMax*0.75
self.PatrolAltType=PatrolAltType or"RADIO"
self:AddTransition({"Started","Airborne","Refuelling"},"Patrol","Patrolling")
self:AddTransition("Patrolling","PatrolRoute","Patrolling")
self:AddTransition("*","Reset","Patrolling")
return self
end
function AI_AIR_PATROL:SetEngageRange(EngageRange)
self:F2()
if EngageRange then
self.EngageRange=EngageRange
else
self.EngageRange=nil
end
end
function AI_AIR_PATROL:SetRaceTrackPattern(LegMin,LegMax,HeadingMin,HeadingMax,DurationMin,DurationMax,CapCoordinates)
self.racetrack=true
self.racetracklegmin=LegMin or 10000
self.racetracklegmax=LegMax or 15000
self.racetrackheadingmin=HeadingMin or 0
self.racetrackheadingmax=HeadingMax or 180
self.racetrackdurationmin=DurationMin
self.racetrackdurationmax=DurationMax
if self.racetrackdurationmax and not self.racetrackdurationmin then
self.racetrackdurationmin=self.racetrackdurationmax
end
self.racetrackcapcoordinates=CapCoordinates
end
function AI_AIR_PATROL:onafterPatrol(AIPatrol,From,Event,To)
self:F2()
self:ClearTargetDistance()
self:__PatrolRoute(self.TaskDelay)
AIPatrol:OnReSpawn(
function(PatrolGroup)
self:__Reset(self.TaskDelay)
self:__PatrolRoute(self.TaskDelay)
end
)
end
function AI_AIR_PATROL.___PatrolRoute(AIPatrol,Fsm)
AIPatrol:F({"AI_AIR_PATROL.___PatrolRoute:",AIPatrol:GetName()})
if AIPatrol and AIPatrol:IsAlive()then
Fsm:PatrolRoute()
end
end
function AI_AIR_PATROL:onafterPatrolRoute(AIPatrol,From,Event,To)
self:F2()
if From=="RTB"then
return
end
if AIPatrol and AIPatrol:IsAlive()then
local PatrolRoute={}
local CurrentCoord=AIPatrol:GetCoordinate()
local altitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude)
local ToTargetCoord=self.PatrolZone:GetRandomPointVec2()
ToTargetCoord:SetAlt(altitude)
self:SetTargetDistance(ToTargetCoord)
local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
local speedkmh=ToTargetSpeed
local FromWP=CurrentCoord:WaypointAir(self.PatrolAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToTargetSpeed,true)
PatrolRoute[#PatrolRoute+1]=FromWP
if self.racetrack then
local heading=math.random(self.racetrackheadingmin,self.racetrackheadingmax)
local leg=math.random(self.racetracklegmin,self.racetracklegmax)
local duration=self.racetrackdurationmin
if self.racetrackdurationmax then
duration=math.random(self.racetrackdurationmin,self.racetrackdurationmax)
end
local c0=self.PatrolZone:GetRandomCoordinate()
if self.racetrackcapcoordinates and#self.racetrackcapcoordinates>0 then
c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)]
end
local c1=c0:SetAltitude(altitude)
local c2=c1:Translate(leg,heading):SetAltitude(altitude)
self:SetTargetDistance(c0)
self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec",UTILS.KmphToKnots(speedkmh),UTILS.MetersToFeet(altitude),heading,leg,tostring(duration)))
local taskOrbit=AIPatrol:TaskOrbit(c1,altitude,UTILS.KmphToMps(speedkmh),c2)
local taskPatrol=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute",self)
local taskCond=AIPatrol:TaskCondition(nil,nil,nil,nil,duration,nil)
local taskCont=AIPatrol:TaskControlled(taskOrbit,taskCond)
PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskCont,taskPatrol},"CAP Orbit")
else
local ToWP=ToTargetCoord:WaypointAir(self.PatrolAltType,POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToTargetSpeed,true)
PatrolRoute[#PatrolRoute+1]=ToWP
local Tasks={}
Tasks[#Tasks+1]=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute",self)
PatrolRoute[#PatrolRoute].task=AIPatrol:TaskCombo(Tasks)
end
AIPatrol:OptionROEReturnFire()
AIPatrol:OptionROTEvadeFire()
AIPatrol:Route(PatrolRoute,self.TaskDelay)
end
end
function AI_AIR_PATROL.Resume(AIPatrol,Fsm)
AIPatrol:F({"AI_AIR_PATROL.Resume:",AIPatrol:GetName()})
if AIPatrol and AIPatrol:IsAlive()then
Fsm:__Reset(Fsm.TaskDelay)
Fsm:__PatrolRoute(Fsm.TaskDelay)
end
end
AI_AIR_ENGAGE={
ClassName="AI_AIR_ENGAGE",
}
function AI_AIR_ENGAGE:New(AI_Air,AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local self=BASE:Inherit(self,AI_Air)
self.Accomplished=false
self.Engaging=false
local SpeedMax=AIGroup:GetSpeedMax()
self.EngageMinSpeed=EngageMinSpeed or SpeedMax*0.5
self.EngageMaxSpeed=EngageMaxSpeed or SpeedMax*0.75
self.EngageFloorAltitude=EngageFloorAltitude or 1000
self.EngageCeilingAltitude=EngageCeilingAltitude or 1500
self.EngageAltType=EngageAltType or"RADIO"
self:AddTransition({"Started","Engaging","Returning","Airborne","Patrolling"},"EngageRoute","Engaging")
self:AddTransition({"Started","Engaging","Returning","Airborne","Patrolling"},"Engage","Engaging")
self:AddTransition("Engaging","Fired","Engaging")
self:AddTransition("*","Destroy","*")
self:AddTransition("Engaging","Abort","Patrolling")
self:AddTransition("Engaging","Accomplish","Patrolling")
self:AddTransition({"Patrolling","Engaging"},"Refuel","Refuelling")
return self
end
function AI_AIR_ENGAGE:onafterStart(AIGroup,From,Event,To)
self:GetParent(self,AI_AIR_ENGAGE).onafterStart(self,AIGroup,From,Event,To)
AIGroup:HandleEvent(EVENTS.Takeoff,nil,self)
end
function AI_AIR_ENGAGE:onafterEngage(AIGroup,From,Event,To)
self:HandleEvent(EVENTS.Dead)
end
function AI_AIR_ENGAGE:onbeforeEngage(AIGroup,From,Event,To)
if self.Accomplished==true then
return false
end
return true
end
function AI_AIR_ENGAGE:onafterAbort(AIGroup,From,Event,To)
AIGroup:ClearTasks()
self:Return()
end
function AI_AIR_ENGAGE:onafterAccomplish(AIGroup,From,Event,To)
self.Accomplished=true
end
function AI_AIR_ENGAGE:onafterDestroy(AIGroup,From,Event,To,EventData)
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit]=nil
end
end
function AI_AIR_ENGAGE:OnEventDead(EventData)
self:F({"EventDead",EventData})
if EventData.IniDCSUnit then
if self.AttackUnits and self.AttackUnits[EventData.IniUnit]then
self:__Destroy(self.TaskDelay,EventData)
end
end
end
function AI_AIR_ENGAGE.___EngageRoute(AIGroup,Fsm,AttackSetUnit)
Fsm:I(string.format("AI_AIR_ENGAGE.___EngageRoute: %s",tostring(AIGroup:GetName())))
if AIGroup and AIGroup:IsAlive()then
Fsm:__EngageRoute(Fsm.TaskDelay or 0.1,AttackSetUnit)
end
end
function AI_AIR_ENGAGE:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit)
self:I({DefenderGroup,From,Event,To,AttackSetUnit})
local DefenderGroupName=DefenderGroup:GetName()
self.AttackSetUnit=AttackSetUnit
local AttackCount=AttackSetUnit:CountAlive()
if AttackCount>0 then
if DefenderGroup:IsAlive()then
local EngageAltitude=math.random(self.EngageFloorAltitude,self.EngageCeilingAltitude)
local EngageSpeed=math.random(self.EngageMinSpeed,self.EngageMaxSpeed)
local DefenderCoord=DefenderGroup:GetPointVec3()
DefenderCoord:SetY(EngageAltitude)
local TargetCoord=AttackSetUnit:GetFirst():GetPointVec3()
TargetCoord:SetY(EngageAltitude)
local TargetDistance=DefenderCoord:Get2DDistance(TargetCoord)
local EngageDistance=(DefenderGroup:IsHelicopter()and 5000)or(DefenderGroup:IsAirPlane()and 10000)
if TargetDistance<=EngageDistance*9 then
self:I(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km",TargetDistance/1000))
self:__Engage(0.1,AttackSetUnit)
else
self:I(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km",TargetDistance/1000))
local EngageRoute={}
local AttackTasks={}
local FromWP=DefenderCoord:WaypointAir(self.PatrolAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true)
EngageRoute[#EngageRoute+1]=FromWP
self:SetTargetDistance(TargetCoord)
local FromEngageAngle=DefenderCoord:GetAngleDegrees(DefenderCoord:GetDirectionVec3(TargetCoord))
local ToCoord=DefenderCoord:Translate(EngageDistance,FromEngageAngle,true)
local ToWP=ToCoord:WaypointAir(self.PatrolAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true)
EngageRoute[#EngageRoute+1]=ToWP
AttackTasks[#AttackTasks+1]=DefenderGroup:TaskFunction("AI_AIR_ENGAGE.___EngageRoute",self,AttackSetUnit)
EngageRoute[#EngageRoute].task=DefenderGroup:TaskCombo(AttackTasks)
DefenderGroup:OptionROEReturnFire()
DefenderGroup:OptionROTEvadeFire()
DefenderGroup:Route(EngageRoute,self.TaskDelay or 0.1)
end
end
else
self:I(DefenderGroupName..": No targets found -> Going RTB")
self:Return()
end
end
function AI_AIR_ENGAGE.___Engage(AIGroup,Fsm,AttackSetUnit)
Fsm:I(string.format("AI_AIR_ENGAGE.___Engage: %s",tostring(AIGroup:GetName())))
if AIGroup and AIGroup:IsAlive()then
local delay=Fsm.TaskDelay or 0.1
Fsm:__Engage(delay,AttackSetUnit)
end
end
function AI_AIR_ENGAGE:onafterEngage(DefenderGroup,From,Event,To,AttackSetUnit)
self:F({DefenderGroup,From,Event,To,AttackSetUnit})
local DefenderGroupName=DefenderGroup:GetName()
self.AttackSetUnit=AttackSetUnit
local AttackCount=AttackSetUnit:CountAlive()
self:T({AttackCount=AttackCount})
if AttackCount>0 then
if DefenderGroup and DefenderGroup:IsAlive()then
local EngageAltitude=math.random(self.EngageFloorAltitude or 500,self.EngageCeilingAltitude or 1000)
local EngageSpeed=math.random(self.EngageMinSpeed,self.EngageMaxSpeed)
local DefenderCoord=DefenderGroup:GetPointVec3()
DefenderCoord:SetY(EngageAltitude)
local TargetCoord=AttackSetUnit:GetFirst():GetPointVec3()
if not TargetCoord then
self:Return()
return
end
TargetCoord:SetY(EngageAltitude)
local TargetDistance=DefenderCoord:Get2DDistance(TargetCoord)
local EngageDistance=(DefenderGroup:IsHelicopter()and 5000)or(DefenderGroup:IsAirPlane()and 10000)
local EngageRoute={}
local AttackTasks={}
local FromWP=DefenderCoord:WaypointAir(self.EngageAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true)
EngageRoute[#EngageRoute+1]=FromWP
self:SetTargetDistance(TargetCoord)
local FromEngageAngle=DefenderCoord:GetAngleDegrees(DefenderCoord:GetDirectionVec3(TargetCoord))
local ToCoord=DefenderCoord:Translate(EngageDistance,FromEngageAngle,true)
local ToWP=ToCoord:WaypointAir(self.EngageAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true)
EngageRoute[#EngageRoute+1]=ToWP
if TargetDistance<=EngageDistance*9 then
local AttackUnitTasks=self:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude)
if#AttackUnitTasks==0 then
self:I(DefenderGroupName..": No valid targets found -> Going RTB")
self:Return()
return
else
local text=string.format("%s: Engaging targets at distance %.2f NM",DefenderGroupName,UTILS.MetersToNM(TargetDistance))
self:I(text)
DefenderGroup:OptionROEOpenFire()
DefenderGroup:OptionROTEvadeFire()
DefenderGroup:OptionKeepWeaponsOnThreat()
AttackTasks[#AttackTasks+1]=DefenderGroup:TaskCombo(AttackUnitTasks)
end
end
AttackTasks[#AttackTasks+1]=DefenderGroup:TaskFunction("AI_AIR_ENGAGE.___Engage",self,AttackSetUnit)
EngageRoute[#EngageRoute].task=DefenderGroup:TaskCombo(AttackTasks)
DefenderGroup:Route(EngageRoute,self.TaskDelay or 0.1)
end
else
self:I(DefenderGroupName..": No targets found -> returning.")
self:Return()
return
end
end
function AI_AIR_ENGAGE.Resume(AIEngage,Fsm)
AIEngage:F({"Resume:",AIEngage:GetName()})
if AIEngage and AIEngage:IsAlive()then
Fsm:__Reset(Fsm.TaskDelay or 0.1)
Fsm:__EngageRoute(Fsm.TaskDelay or 0.2,Fsm.AttackSetUnit)
end
end
AI_A2A_PATROL={
ClassName="AI_A2A_PATROL",
}
function AI_A2A_PATROL:New(AIPatrol,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
local AI_Air=AI_AIR:New(AIPatrol)
local AI_Air_Patrol=AI_AIR_PATROL:New(AI_Air,AIPatrol,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
local self=BASE:Inherit(self,AI_Air_Patrol)
self:SetFuelThreshold(.2,60)
self:SetDamageThreshold(0.4)
self:SetDisengageRadius(70000)
self.PatrolZone=PatrolZone
self.PatrolFloorAltitude=PatrolFloorAltitude
self.PatrolCeilingAltitude=PatrolCeilingAltitude
self.PatrolMinSpeed=PatrolMinSpeed
self.PatrolMaxSpeed=PatrolMaxSpeed
self.PatrolAltType=PatrolAltType or"BARO"
self:AddTransition({"Started","Airborne","Refuelling"},"Patrol","Patrolling")
self:AddTransition("Patrolling","Route","Patrolling")
self:AddTransition("*","Reset","Patrolling")
return self
end
function AI_A2A_PATROL:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed)
self:F2({PatrolMinSpeed,PatrolMaxSpeed})
self.PatrolMinSpeed=PatrolMinSpeed
self.PatrolMaxSpeed=PatrolMaxSpeed
end
function AI_A2A_PATROL:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude)
self:F2({PatrolFloorAltitude,PatrolCeilingAltitude})
self.PatrolFloorAltitude=PatrolFloorAltitude
self.PatrolCeilingAltitude=PatrolCeilingAltitude
end
function AI_A2A_PATROL:onafterPatrol(AIPatrol,From,Event,To)
self:F2()
self:ClearTargetDistance()
self:__Route(1)
AIPatrol:OnReSpawn(
function(PatrolGroup)
self:__Reset(1)
self:__Route(5)
end
)
end
function AI_A2A_PATROL.PatrolRoute(AIPatrol,Fsm)
AIPatrol:F({"AI_A2A_PATROL.PatrolRoute:",AIPatrol:GetName()})
if AIPatrol and AIPatrol:IsAlive()then
Fsm:Route()
end
end
function AI_A2A_PATROL:onafterRoute(AIPatrol,From,Event,To)
self:F2()
if From=="RTB"then
return
end
if AIPatrol and AIPatrol:IsAlive()then
local PatrolRoute={}
local CurrentCoord=AIPatrol:GetCoordinate()
local altitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude)
local speedkmh=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil,speedkmh,{},"Current")
if self.racetrack then
local heading=math.random(self.racetrackheadingmin,self.racetrackheadingmax)
local leg=math.random(self.racetracklegmin,self.racetracklegmax)
local duration=self.racetrackdurationmin
if self.racetrackdurationmax then
duration=math.random(self.racetrackdurationmin,self.racetrackdurationmax)
end
local c0=self.PatrolZone:GetRandomCoordinate()
if self.racetrackcapcoordinates and#self.racetrackcapcoordinates>0 then
c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)]
end
local c1=c0:SetAltitude(altitude)
local c2=c1:Translate(leg,heading):SetAltitude(altitude)
self:SetTargetDistance(c0)
self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec",UTILS.KmphToKnots(speedkmh),UTILS.MetersToFeet(altitude),heading,leg,tostring(duration)))
local taskOrbit=AIPatrol:TaskOrbit(c1,altitude,UTILS.KmphToMps(speedkmh),c2)
local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute",self)
local taskCond=AIPatrol:TaskCondition(nil,nil,nil,nil,duration,nil)
local taskCont=AIPatrol:TaskControlled(taskOrbit,taskCond)
PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskCont,taskPatrol},"CAP Orbit")
else
local ToTargetCoord=self.PatrolZone:GetRandomCoordinate()
ToTargetCoord:SetAltitude(altitude)
self:SetTargetDistance(ToTargetCoord)
local taskReRoute=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute",self)
PatrolRoute[2]=ToTargetCoord:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskReRoute},"Patrol Point")
end
AIPatrol:OptionROEReturnFire()
AIPatrol:OptionROTEvadeFire()
AIPatrol:Route(PatrolRoute,0.5)
end
end
AI_A2A_CAP={
ClassName="AI_A2A_CAP",
}
function AI_A2A_CAP:New2(AICap,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType,PatrolZone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType)
local AI_Air=AI_AIR:New(AICap)
local AI_Air_Patrol=AI_AIR_PATROL:New(AI_Air,AICap,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
local AI_Air_Engage=AI_AIR_ENGAGE:New(AI_Air_Patrol,AICap,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local self=BASE:Inherit(self,AI_Air_Engage)
self:SetFuelThreshold(.2,60)
self:SetDamageThreshold(0.4)
self:SetDisengageRadius(70000)
return self
end
function AI_A2A_CAP:New(AICap,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,PatrolAltType)
return self:New2(AICap,EngageMinSpeed,EngageMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,PatrolZone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType)
end
function AI_A2A_CAP:onafterStart(AICap,From,Event,To)
self:GetParent(self,AI_A2A_CAP).onafterStart(self,AICap,From,Event,To)
AICap:HandleEvent(EVENTS.Takeoff,nil,self)
end
function AI_A2A_CAP:SetEngageZone(EngageZone)
self:F2()
if EngageZone then
self.EngageZone=EngageZone
else
self.EngageZone=nil
end
end
function AI_A2A_CAP:SetEngageRange(EngageRange)
self:F2()
if EngageRange then
self.EngageRange=EngageRange
else
self.EngageRange=nil
end
end
function AI_A2A_CAP:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude)
local AttackUnitTasks={}
for AttackUnitID,AttackUnit in pairs(self.AttackSetUnit:GetSet())do
local AttackUnit=AttackUnit
if AttackUnit and AttackUnit:IsAlive()and AttackUnit:IsAir()then
self:T({"Attacking Task:",AttackUnit:GetName(),AttackUnit:IsAlive(),AttackUnit:IsAir()})
AttackUnitTasks[#AttackUnitTasks+1]=DefenderGroup:TaskAttackUnit(AttackUnit)
end
end
return AttackUnitTasks
end
AI_A2A_GCI={
ClassName="AI_A2A_GCI",
}
function AI_A2A_GCI:New2(AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local AI_Air=AI_AIR:New(AIIntercept)
local AI_Air_Engage=AI_AIR_ENGAGE:New(AI_Air,AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local self=BASE:Inherit(self,AI_Air_Engage)
self:SetFuelThreshold(.2,60)
self:SetDamageThreshold(0.4)
self:SetDisengageRadius(70000)
return self
end
function AI_A2A_GCI:New(AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
return self:New2(AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
end
function AI_A2A_GCI:onafterStart(AIIntercept,From,Event,To)
self:GetParent(self,AI_A2A_GCI).onafterStart(self,AIIntercept,From,Event,To)
end
function AI_A2A_GCI:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude)
local AttackUnitTasks={}
for AttackUnitID,AttackUnit in pairs(self.AttackSetUnit:GetSet())do
local AttackUnit=AttackUnit
self:T({"Attacking Unit:",AttackUnit:GetName(),AttackUnit:IsAlive(),AttackUnit:IsAir()})
if AttackUnit:IsAlive()and AttackUnit:IsAir()then
AttackUnitTasks[#AttackUnitTasks+1]=DefenderGroup:TaskAttackUnit(AttackUnit)
end
end
return AttackUnitTasks
end
do
AI_A2A_DISPATCHER={
ClassName="AI_A2A_DISPATCHER",
Detection=nil,
}
AI_A2A_DISPATCHER.Takeoff=GROUP.Takeoff
AI_A2A_DISPATCHER.Landing={
NearAirbase=1,
AtRunway=2,
AtEngineShutdown=3,
}
function AI_A2A_DISPATCHER:New(Detection)
local self=BASE:Inherit(self,DETECTION_MANAGER:New(nil,Detection))
self.Detection=Detection
self.DefenderSquadrons={}
self.DefenderSpawns={}
self.DefenderTasks={}
self.DefenderDefault={}
self.SetSendPlayerMessages=false
self.Detection:FilterCategories({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
self.Detection:SetRefreshTimeInterval(30)
self:SetEngageRadius()
self:SetGciRadius()
self:SetIntercept(300)
self:SetDisengageRadius(300000)
self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Air)
self:SetDefaultTakeoffInAirAltitude(500)
self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.NearAirbase)
self:SetDefaultOverhead(1)
self:SetDefaultGrouping(1)
self:SetDefaultFuelThreshold(0.15,0)
self:SetDefaultDamageThreshold(0.4)
self:SetDefaultCapTimeInterval(180,600)
self:SetDefaultCapLimit(1)
self:AddTransition("Started","Assign","Started")
self:AddTransition("*","CAP","*")
self:AddTransition("*","GCI","*")
self:AddTransition("*","ENGAGE","*")
self:HandleEvent(EVENTS.Crash,self.OnEventCrashOrDead)
self:HandleEvent(EVENTS.Dead,self.OnEventCrashOrDead)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.EngineShutdown)
self:HandleEvent(EVENTS.BaseCaptured)
self:SetTacticalDisplay(false)
self.DefenderCAPIndex=0
self:__Start(5)
return self
end
function AI_A2A_DISPATCHER:onafterStart(From,Event,To)
self:GetParent(self,AI_A2A_DISPATCHER).onafterStart(self,From,Event,To)
for SquadronName,_DefenderSquadron in pairs(self.DefenderSquadrons)do
local DefenderSquadron=_DefenderSquadron
DefenderSquadron.Resources={}
if DefenderSquadron.ResourceCount then
for Resource=1,DefenderSquadron.ResourceCount do
self:ParkDefender(DefenderSquadron)
end
end
end
end
function AI_A2A_DISPATCHER:ParkDefender(DefenderSquadron)
local TemplateID=math.random(1,#DefenderSquadron.Spawn)
local Spawn=DefenderSquadron.Spawn[TemplateID]
Spawn:InitGrouping(1)
local SpawnGroup
if self:IsSquadronVisible(DefenderSquadron.Name)then
local Grouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping
Grouping=1
Spawn:InitGrouping(Grouping)
SpawnGroup=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,SPAWN.Takeoff.Cold)
local GroupName=SpawnGroup:GetName()
DefenderSquadron.Resources=DefenderSquadron.Resources or{}
DefenderSquadron.Resources[TemplateID]=DefenderSquadron.Resources[TemplateID]or{}
DefenderSquadron.Resources[TemplateID][GroupName]={}
DefenderSquadron.Resources[TemplateID][GroupName]=SpawnGroup
self.uncontrolled=self.uncontrolled or{}
self.uncontrolled[DefenderSquadron.Name]=self.uncontrolled[DefenderSquadron.Name]or{}
table.insert(self.uncontrolled[DefenderSquadron.Name],{group=SpawnGroup,name=GroupName,grouping=Grouping})
end
end
function AI_A2A_DISPATCHER:OnEventBaseCaptured(EventData)
local AirbaseName=EventData.PlaceName
self:I("Captured "..AirbaseName)
for SquadronName,Squadron in pairs(self.DefenderSquadrons)do
if Squadron.AirbaseName==AirbaseName then
Squadron.ResourceCount=-999
Squadron.Captured=true
self:I("Squadron "..SquadronName.." captured.")
end
end
end
function AI_A2A_DISPATCHER:OnEventCrashOrDead(EventData)
self.Detection:ForgetDetectedUnit(EventData.IniUnitName)
end
function AI_A2A_DISPATCHER:OnEventLand(EventData)
self:F("Landed")
local DefenderUnit=EventData.IniUnit
local Defender=EventData.IniGroup
local Squadron=self:GetSquadronFromDefender(Defender)
if Squadron then
self:F({SquadronName=Squadron.Name})
local LandingMethod=self:GetSquadronLanding(Squadron.Name)
if LandingMethod==AI_A2A_DISPATCHER.Landing.AtRunway then
local DefenderSize=Defender:GetSize()
if DefenderSize==1 then
self:RemoveDefenderFromSquadron(Squadron,Defender)
end
DefenderUnit:Destroy()
self:ParkDefender(Squadron)
return
end
if DefenderUnit:GetLife()~=DefenderUnit:GetLife0()then
DefenderUnit:Destroy()
return
end
end
end
function AI_A2A_DISPATCHER:OnEventEngineShutdown(EventData)
local DefenderUnit=EventData.IniUnit
local Defender=EventData.IniGroup
local Squadron=self:GetSquadronFromDefender(Defender)
if Squadron then
self:F({SquadronName=Squadron.Name})
local LandingMethod=self:GetSquadronLanding(Squadron.Name)
if LandingMethod==AI_A2A_DISPATCHER.Landing.AtEngineShutdown and not DefenderUnit:InAir()then
local DefenderSize=Defender:GetSize()
if DefenderSize==1 then
self:RemoveDefenderFromSquadron(Squadron,Defender)
end
DefenderUnit:Destroy()
self:ParkDefender(Squadron)
end
end
end
function AI_A2A_DISPATCHER:SetEngageRadius(EngageRadius)
self.Detection:SetFriendliesRange(EngageRadius or 100000)
return self
end
function AI_A2A_DISPATCHER:SetDisengageRadius(DisengageRadius)
self.DisengageRadius=DisengageRadius or 300000
return self
end
function AI_A2A_DISPATCHER:SetGciRadius(GciRadius)
self.GciRadius=GciRadius or 200000
return self
end
function AI_A2A_DISPATCHER:SetBorderZone(BorderZone)
self.Detection:SetAcceptZones(BorderZone)
return self
end
function AI_A2A_DISPATCHER:SetTacticalDisplay(TacticalDisplay)
self.TacticalDisplay=TacticalDisplay
return self
end
function AI_A2A_DISPATCHER:SetDefaultDamageThreshold(DamageThreshold)
self.DefenderDefault.DamageThreshold=DamageThreshold
return self
end
function AI_A2A_DISPATCHER:SetDefaultCapTimeInterval(CapMinSeconds,CapMaxSeconds)
self.DefenderDefault.CapMinSeconds=CapMinSeconds
self.DefenderDefault.CapMaxSeconds=CapMaxSeconds
return self
end
function AI_A2A_DISPATCHER:SetDefaultCapLimit(CapLimit)
self.DefenderDefault.CapLimit=CapLimit
return self
end
function AI_A2A_DISPATCHER:SetIntercept(InterceptDelay)
self.DefenderDefault.InterceptDelay=InterceptDelay
local Detection=self.Detection
Detection:SetIntercept(true,InterceptDelay)
return self
end
function AI_A2A_DISPATCHER:GetAIFriendliesNearBy(DetectedItem)
local FriendliesNearBy=self.Detection:GetFriendliesDistance(DetectedItem)
return FriendliesNearBy
end
function AI_A2A_DISPATCHER:GetDefenderTasks()
return self.DefenderTasks or{}
end
function AI_A2A_DISPATCHER:GetDefenderTask(Defender)
return self.DefenderTasks[Defender]
end
function AI_A2A_DISPATCHER:GetDefenderTaskFsm(Defender)
return self:GetDefenderTask(Defender).Fsm
end
function AI_A2A_DISPATCHER:GetDefenderTaskTarget(Defender)
return self:GetDefenderTask(Defender).Target
end
function AI_A2A_DISPATCHER:GetDefenderTaskSquadronName(Defender)
return self:GetDefenderTask(Defender).SquadronName
end
function AI_A2A_DISPATCHER:ClearDefenderTask(Defender)
if Defender and Defender:IsAlive()and self.DefenderTasks[Defender]then
local Target=self.DefenderTasks[Defender].Target
local Message="Clearing ("..self.DefenderTasks[Defender].Type..") "
Message=Message..Defender:GetName()
if Target then
Message=Message..(Target and(" from "..Target.Index.." ["..Target.Set:Count().."]"))or""
end
self:F({Target=Message})
end
self.DefenderTasks[Defender]=nil
return self
end
function AI_A2A_DISPATCHER:ClearDefenderTaskTarget(Defender)
local DefenderTask=self:GetDefenderTask(Defender)
if Defender and Defender:IsAlive()and DefenderTask then
local Target=DefenderTask.Target
local Message="Clearing ("..DefenderTask.Type..") "
Message=Message..Defender:GetName()
if Target then
Message=Message..((Target and(" from "..Target.Index.." ["..Target.Set:Count().."]"))or"")
end
self:F({Target=Message})
end
if Defender and DefenderTask and DefenderTask.Target then
DefenderTask.Target=nil
end
return self
end
function AI_A2A_DISPATCHER:SetDefenderTask(SquadronName,Defender,Type,Fsm,Target)
self:F({SquadronName=SquadronName,Defender=Defender:GetName(),Type=Type,Target=Target})
self.DefenderTasks[Defender]=self.DefenderTasks[Defender]or{}
self.DefenderTasks[Defender].Type=Type
self.DefenderTasks[Defender].Fsm=Fsm
self.DefenderTasks[Defender].SquadronName=SquadronName
if Target then
self:SetDefenderTaskTarget(Defender,Target)
end
return self
end
function AI_A2A_DISPATCHER:SetDefenderTaskTarget(Defender,AttackerDetection)
local Message="("..self.DefenderTasks[Defender].Type..") "
Message=Message..Defender:GetName()
Message=Message..((AttackerDetection and(" target "..AttackerDetection.Index.." ["..AttackerDetection.Set:Count().."]"))or"")
self:F({AttackerDetection=Message})
if AttackerDetection then
self.DefenderTasks[Defender].Target=AttackerDetection
end
return self
end
function AI_A2A_DISPATCHER:SetSquadron(SquadronName,AirbaseName,TemplatePrefixes,ResourceCount)
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
local DefenderSquadron=self.DefenderSquadrons[SquadronName]
DefenderSquadron.Name=SquadronName
DefenderSquadron.Airbase=AIRBASE:FindByName(AirbaseName)
DefenderSquadron.AirbaseName=DefenderSquadron.Airbase:GetName()
if not DefenderSquadron.Airbase then
error("Cannot find airbase with name:"..AirbaseName)
end
DefenderSquadron.Spawn={}
if type(TemplatePrefixes)=="string"then
local SpawnTemplate=TemplatePrefixes
self.DefenderSpawns[SpawnTemplate]=self.DefenderSpawns[SpawnTemplate]or SPAWN:New(SpawnTemplate)
DefenderSquadron.Spawn[1]=self.DefenderSpawns[SpawnTemplate]
else
for TemplateID,SpawnTemplate in pairs(TemplatePrefixes)do
self.DefenderSpawns[SpawnTemplate]=self.DefenderSpawns[SpawnTemplate]or SPAWN:New(SpawnTemplate)
DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1]=self.DefenderSpawns[SpawnTemplate]
end
end
DefenderSquadron.ResourceCount=ResourceCount
DefenderSquadron.TemplatePrefixes=TemplatePrefixes
DefenderSquadron.Captured=false
self:SetSquadronLanguage(SquadronName,"EN")
self:F({Squadron={SquadronName,AirbaseName,TemplatePrefixes,ResourceCount}})
return self
end
function AI_A2A_DISPATCHER:GetSquadron(SquadronName)
local DefenderSquadron=self.DefenderSquadrons[SquadronName]
if not DefenderSquadron then
error("Unknown Squadron:"..SquadronName)
end
return DefenderSquadron
end
function AI_A2A_DISPATCHER:SetSquadronVisible(SquadronName)
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.Uncontrolled=true
DefenderSquadron.Grouping=1
local nfreeparking=DefenderSquadron.Airbase:GetFreeParkingSpotsNumber(AIRBASE.TerminalType.FighterAircraft,true)
DefenderSquadron.ResourceCount=DefenderSquadron.ResourceCount or nfreeparking
DefenderSquadron.ResourceCount=math.min(DefenderSquadron.ResourceCount,nfreeparking)
for SpawnTemplate,_DefenderSpawn in pairs(self.DefenderSpawns)do
local DefenderSpawn=_DefenderSpawn
DefenderSpawn:InitUnControlled(true)
end
end
function AI_A2A_DISPATCHER:IsSquadronVisible(SquadronName)
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
local DefenderSquadron=self:GetSquadron(SquadronName)
if DefenderSquadron then
return DefenderSquadron.Uncontrolled==true
end
return nil
end
function AI_A2A_DISPATCHER:SetSquadronCap2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType)
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{}
local DefenderSquadron=self:GetSquadron(SquadronName)
local Cap=self.DefenderSquadrons[SquadronName].Cap
Cap.Name=SquadronName
Cap.EngageMinSpeed=EngageMinSpeed
Cap.EngageMaxSpeed=EngageMaxSpeed
Cap.EngageFloorAltitude=EngageFloorAltitude
Cap.EngageCeilingAltitude=EngageCeilingAltitude
Cap.Zone=Zone
Cap.PatrolMinSpeed=PatrolMinSpeed
Cap.PatrolMaxSpeed=PatrolMaxSpeed
Cap.PatrolFloorAltitude=PatrolFloorAltitude
Cap.PatrolCeilingAltitude=PatrolCeilingAltitude
Cap.PatrolAltType=PatrolAltType
Cap.EngageAltType=EngageAltType
self:SetSquadronCapInterval(SquadronName,self.DefenderDefault.CapLimit,self.DefenderDefault.CapMinSeconds,self.DefenderDefault.CapMaxSeconds,1)
self:I({CAP={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageAltType}})
local RecceSet=self.Detection:GetDetectionSet()
RecceSet:FilterPrefixes(DefenderSquadron.TemplatePrefixes)
RecceSet:FilterStart()
self.Detection:SetFriendlyPrefixes(DefenderSquadron.TemplatePrefixes)
return self
end
function AI_A2A_DISPATCHER:SetSquadronCap(SquadronName,Zone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType)
return self:SetSquadronCap2(SquadronName,EngageMinSpeed,EngageMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,AltType,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,AltType)
end
function AI_A2A_DISPATCHER:SetSquadronCapInterval(SquadronName,CapLimit,LowInterval,HighInterval,Probability)
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{}
local DefenderSquadron=self:GetSquadron(SquadronName)
local Cap=self.DefenderSquadrons[SquadronName].Cap
if Cap then
Cap.LowInterval=LowInterval or 180
Cap.HighInterval=HighInterval or 600
Cap.Probability=Probability or 1
Cap.CapLimit=CapLimit or 1
Cap.Scheduler=Cap.Scheduler or SCHEDULER:New(self)
local Scheduler=Cap.Scheduler
local ScheduleID=Cap.ScheduleID
local Variance=(Cap.HighInterval-Cap.LowInterval)/2
local Repeat=Cap.LowInterval+Variance
local Randomization=Variance/Repeat
local Start=math.random(1,Cap.HighInterval)
if ScheduleID then
Scheduler:Stop(ScheduleID)
end
Cap.ScheduleID=Scheduler:Schedule(self,self.SchedulerCAP,{SquadronName},Start,Repeat,Randomization)
else
error("This squadron does not exist:"..SquadronName)
end
end
function AI_A2A_DISPATCHER:GetCAPDelay(SquadronName)
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{}
local DefenderSquadron=self:GetSquadron(SquadronName)
local Cap=self.DefenderSquadrons[SquadronName].Cap
if Cap then
return math.random(Cap.LowInterval,Cap.HighInterval)
else
error("This squadron does not exist:"..SquadronName)
end
end
function AI_A2A_DISPATCHER:CanCAP(SquadronName)
self:F({SquadronName=SquadronName})
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{}
local DefenderSquadron=self:GetSquadron(SquadronName)
if DefenderSquadron.Captured==false then
if(not DefenderSquadron.ResourceCount)or(DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount>0)then
local Cap=DefenderSquadron.Cap
if Cap then
local CapCount=self:CountCapAirborne(SquadronName)
self:F({CapCount=CapCount})
if CapCount<Cap.CapLimit then
local Probability=math.random()
if Probability<=Cap.Probability then
return DefenderSquadron
end
end
end
end
end
return nil
end
function AI_A2A_DISPATCHER:SetDefaultCapRacetrack(LeglengthMin,LeglengthMax,HeadingMin,HeadingMax,DurationMin,DurationMax,CapCoordinates)
self.DefenderDefault.Racetrack=true
self.DefenderDefault.RacetrackLengthMin=LeglengthMin
self.DefenderDefault.RacetrackLengthMax=LeglengthMax
self.DefenderDefault.RacetrackHeadingMin=HeadingMin
self.DefenderDefault.RacetrackHeadingMax=HeadingMax
self.DefenderDefault.RacetrackDurationMin=DurationMin
self.DefenderDefault.RacetrackDurationMax=DurationMax
self.DefenderDefault.RacetrackCoordinates=CapCoordinates
return self
end
function AI_A2A_DISPATCHER:SetSquadronCapRacetrack(SquadronName,LeglengthMin,LeglengthMax,HeadingMin,HeadingMax,DurationMin,DurationMax,CapCoordinates)
local DefenderSquadron=self:GetSquadron(SquadronName)
if DefenderSquadron then
DefenderSquadron.Racetrack=true
DefenderSquadron.RacetrackLengthMin=LeglengthMin
DefenderSquadron.RacetrackLengthMax=LeglengthMax
DefenderSquadron.RacetrackHeadingMin=HeadingMin
DefenderSquadron.RacetrackHeadingMax=HeadingMax
DefenderSquadron.RacetrackDurationMin=DurationMin
DefenderSquadron.RacetrackDurationMax=DurationMax
DefenderSquadron.RacetrackCoordinates=CapCoordinates
end
return self
end
function AI_A2A_DISPATCHER:CanGCI(SquadronName)
self:F({SquadronName=SquadronName})
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
self.DefenderSquadrons[SquadronName].Gci=self.DefenderSquadrons[SquadronName].Gci or{}
local DefenderSquadron=self:GetSquadron(SquadronName)
if DefenderSquadron.Captured==false then
if(not DefenderSquadron.ResourceCount)or(DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount>0)then
local Gci=DefenderSquadron.Gci
if Gci then
return DefenderSquadron
end
end
end
return nil
end
function AI_A2A_DISPATCHER:SetSquadronGci2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
self.DefenderSquadrons[SquadronName].Gci=self.DefenderSquadrons[SquadronName].Gci or{}
local Intercept=self.DefenderSquadrons[SquadronName].Gci
Intercept.Name=SquadronName
Intercept.EngageMinSpeed=EngageMinSpeed
Intercept.EngageMaxSpeed=EngageMaxSpeed
Intercept.EngageFloorAltitude=EngageFloorAltitude
Intercept.EngageCeilingAltitude=EngageCeilingAltitude
Intercept.EngageAltType=EngageAltType
self:I({GCI={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}})
end
function AI_A2A_DISPATCHER:SetSquadronGci(SquadronName,EngageMinSpeed,EngageMaxSpeed)
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
self.DefenderSquadrons[SquadronName].Gci=self.DefenderSquadrons[SquadronName].Gci or{}
local Intercept=self.DefenderSquadrons[SquadronName].Gci
Intercept.Name=SquadronName
Intercept.EngageMinSpeed=EngageMinSpeed
Intercept.EngageMaxSpeed=EngageMaxSpeed
self:F({GCI={SquadronName,EngageMinSpeed,EngageMaxSpeed}})
end
function AI_A2A_DISPATCHER:SetDefaultOverhead(Overhead)
self.DefenderDefault.Overhead=Overhead
return self
end
function AI_A2A_DISPATCHER:SetSquadronOverhead(SquadronName,Overhead)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.Overhead=Overhead
return self
end
function AI_A2A_DISPATCHER:SetDefaultGrouping(Grouping)
self.DefenderDefault.Grouping=Grouping
return self
end
function AI_A2A_DISPATCHER:SetSquadronGrouping(SquadronName,Grouping)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.Grouping=Grouping
return self
end
function AI_A2A_DISPATCHER:SetDefaultTakeoff(Takeoff)
self.DefenderDefault.Takeoff=Takeoff
return self
end
function AI_A2A_DISPATCHER:SetSquadronTakeoff(SquadronName,Takeoff)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.Takeoff=Takeoff
return self
end
function AI_A2A_DISPATCHER:GetDefaultTakeoff()
return self.DefenderDefault.Takeoff
end
function AI_A2A_DISPATCHER:GetSquadronTakeoff(SquadronName)
local DefenderSquadron=self:GetSquadron(SquadronName)
return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff
end
function AI_A2A_DISPATCHER:SetDefaultTakeoffInAir()
self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Air)
return self
end
function AI_A2A_DISPATCHER:SetSendMessages(onoff)
self.SetSendPlayerMessages=onoff
end
function AI_A2A_DISPATCHER:SetSquadronTakeoffInAir(SquadronName,TakeoffAltitude)
self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Air)
if TakeoffAltitude then
self:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude)
end
return self
end
function AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway()
self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Runway)
return self
end
function AI_A2A_DISPATCHER:SetSquadronTakeoffFromRunway(SquadronName)
self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Runway)
return self
end
function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot()
self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Hot)
return self
end
function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingHot(SquadronName)
self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Hot)
return self
end
function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold()
self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Cold)
return self
end
function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingCold(SquadronName)
self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Cold)
return self
end
function AI_A2A_DISPATCHER:SetDefaultTakeoffInAirAltitude(TakeoffAltitude)
self.DefenderDefault.TakeoffAltitude=TakeoffAltitude
return self
end
function AI_A2A_DISPATCHER:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.TakeoffAltitude=TakeoffAltitude
return self
end
function AI_A2A_DISPATCHER:SetDefaultLanding(Landing)
self.DefenderDefault.Landing=Landing
return self
end
function AI_A2A_DISPATCHER:SetSquadronLanding(SquadronName,Landing)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.Landing=Landing
return self
end
function AI_A2A_DISPATCHER:GetDefaultLanding()
return self.DefenderDefault.Landing
end
function AI_A2A_DISPATCHER:GetSquadronLanding(SquadronName)
local DefenderSquadron=self:GetSquadron(SquadronName)
return DefenderSquadron.Landing or self.DefenderDefault.Landing
end
function AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase()
self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.NearAirbase)
return self
end
function AI_A2A_DISPATCHER:SetSquadronLandingNearAirbase(SquadronName)
self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.NearAirbase)
return self
end
function AI_A2A_DISPATCHER:SetDefaultLandingAtRunway()
self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.AtRunway)
return self
end
function AI_A2A_DISPATCHER:SetSquadronLandingAtRunway(SquadronName)
self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.AtRunway)
return self
end
function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown()
self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.AtEngineShutdown)
return self
end
function AI_A2A_DISPATCHER:SetSquadronLandingAtEngineShutdown(SquadronName)
self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.AtEngineShutdown)
return self
end
function AI_A2A_DISPATCHER:SetDefaultFuelThreshold(FuelThreshold)
self.DefenderDefault.FuelThreshold=FuelThreshold
return self
end
function AI_A2A_DISPATCHER:SetSquadronFuelThreshold(SquadronName,FuelThreshold)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.FuelThreshold=FuelThreshold
return self
end
function AI_A2A_DISPATCHER:SetDefaultTanker(TankerName)
self.DefenderDefault.TankerName=TankerName
return self
end
function AI_A2A_DISPATCHER:SetSquadronTanker(SquadronName,TankerName)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.TankerName=TankerName
return self
end
function AI_A2A_DISPATCHER:SetSquadronLanguage(SquadronName,Language)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.Language=Language
if DefenderSquadron.RadioQueue then
DefenderSquadron.RadioQueue:SetLanguage(Language)
end
return self
end
function AI_A2A_DISPATCHER:SetSquadronRadioFrequency(SquadronName,RadioFrequency,RadioModulation,RadioPower)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.RadioFrequency=RadioFrequency
DefenderSquadron.RadioModulation=RadioModulation or radio.modulation.AM
DefenderSquadron.RadioPower=RadioPower or 100
if DefenderSquadron.RadioQueue then
DefenderSquadron.RadioQueue:Stop()
end
DefenderSquadron.RadioQueue=nil
DefenderSquadron.RadioQueue=RADIOSPEECH:New(DefenderSquadron.RadioFrequency,DefenderSquadron.RadioModulation)
DefenderSquadron.RadioQueue.power=DefenderSquadron.RadioPower
DefenderSquadron.RadioQueue:Start(0.5)
DefenderSquadron.RadioQueue:SetLanguage(DefenderSquadron.Language)
end
function AI_A2A_DISPATCHER:AddDefenderToSquadron(Squadron,Defender,Size)
self.Defenders=self.Defenders or{}
local DefenderName=Defender:GetName()
self.Defenders[DefenderName]=Squadron
if Squadron.ResourceCount then
Squadron.ResourceCount=Squadron.ResourceCount-Size
end
self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount})
end
function AI_A2A_DISPATCHER:RemoveDefenderFromSquadron(Squadron,Defender)
self.Defenders=self.Defenders or{}
local DefenderName=Defender:GetName()
if Squadron.ResourceCount then
Squadron.ResourceCount=Squadron.ResourceCount+Defender:GetSize()
end
self.Defenders[DefenderName]=nil
self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount})
end
function AI_A2A_DISPATCHER:GetSquadronFromDefender(Defender)
self.Defenders=self.Defenders or{}
if Defender~=nil then
local DefenderName=Defender:GetName()
self:F({DefenderName=DefenderName})
return self.Defenders[DefenderName]
else
return nil
end
end
function AI_A2A_DISPATCHER:EvaluateSWEEP(DetectedItem)
self:F({DetectedItem.ItemID})
local DetectedSet=DetectedItem.Set
local DetectedZone=DetectedItem.Zone
if DetectedItem.IsDetected==false then
local TargetSetUnit=SET_UNIT:New()
TargetSetUnit:SetDatabase(DetectedSet)
TargetSetUnit:FilterOnce()
return TargetSetUnit
end
return nil
end
function AI_A2A_DISPATCHER:CountCapAirborne(SquadronName)
local CapCount=0
local DefenderSquadron=self.DefenderSquadrons[SquadronName]
if DefenderSquadron then
for AIGroup,DefenderTask in pairs(self:GetDefenderTasks())do
if DefenderTask.SquadronName==SquadronName then
if DefenderTask.Type=="CAP"then
if AIGroup and AIGroup:IsAlive()then
if DefenderTask.Fsm:Is("Patrolling")or DefenderTask.Fsm:Is("Engaging")or DefenderTask.Fsm:Is("Refuelling")or DefenderTask.Fsm:Is("Started")then
CapCount=CapCount+1
end
end
end
end
end
end
return CapCount
end
function AI_A2A_DISPATCHER:CountDefendersEngaged(AttackerDetection)
local DefenderCount=0
local DetectedSet=AttackerDetection.Set
local DefenderTasks=self:GetDefenderTasks()
for DefenderGroup,DefenderTask in pairs(DefenderTasks)do
local Defender=DefenderGroup
local DefenderTaskTarget=DefenderTask.Target
local DefenderSquadronName=DefenderTask.SquadronName
if DefenderTaskTarget and DefenderTaskTarget.Index==AttackerDetection.Index then
local Squadron=self:GetSquadron(DefenderSquadronName)
local SquadronOverhead=Squadron.Overhead or self.DefenderDefault.Overhead
local DefenderSize=Defender:GetInitialSize()
if DefenderSize then
DefenderCount=DefenderCount+DefenderSize/SquadronOverhead
self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize)
else
DefenderCount=0
end
end
end
self:F({DefenderCount=DefenderCount})
return DefenderCount
end
function AI_A2A_DISPATCHER:CountDefendersToBeEngaged(AttackerDetection,DefenderCount)
local Friendlies=nil
local AttackerSet=AttackerDetection.Set
local AttackerCount=AttackerSet:Count()
local DefenderFriendlies=self:GetAIFriendliesNearBy(AttackerDetection)
for FriendlyDistance,AIFriendly in UTILS.spairs(DefenderFriendlies or{})do
if AttackerCount>DefenderCount then
if AIFriendly then
local classname=AIFriendly.ClassName or"No Class Name"
local unitname=AIFriendly.IdentifiableName or"No Unit Name"
end
local Friendly=nil
if AIFriendly and AIFriendly:IsAlive()then
Friendly=AIFriendly:GetGroup()
end
if Friendly and Friendly:IsAlive()then
local DefenderTask=self:GetDefenderTask(Friendly)
if DefenderTask then
if DefenderTask.Type=="CAP"or DefenderTask.Type=="GCI"then
if DefenderTask.Target==nil then
if DefenderTask.Fsm:Is("Returning")or DefenderTask.Fsm:Is("Patrolling")then
Friendlies=Friendlies or{}
Friendlies[Friendly]=Friendly
DefenderCount=DefenderCount+Friendly:GetSize()
self:F({Friendly=Friendly:GetName(),FriendlyDistance=FriendlyDistance})
end
end
end
end
end
else
break
end
end
return Friendlies
end
function AI_A2A_DISPATCHER:ResourceActivate(DefenderSquadron,DefendersNeeded)
local SquadronName=DefenderSquadron.Name
DefendersNeeded=DefendersNeeded or 4
local DefenderGrouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping
DefenderGrouping=(DefenderGrouping<DefendersNeeded)and DefenderGrouping or DefendersNeeded
if self:IsSquadronVisible(SquadronName)then
local n=#self.uncontrolled[SquadronName]
if n>0 then
local id=math.random(n)
local Defender=self.uncontrolled[SquadronName][id].group
Defender:StartUncontrolled()
DefenderGrouping=self.uncontrolled[SquadronName][id].grouping
self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping)
table.remove(self.uncontrolled[SquadronName],id)
return Defender,DefenderGrouping
else
return nil,0
end
local TemplateID=math.random(1,#DefenderSquadron.Spawn)
else
local Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)]
if DefenderGrouping then
Spawn:InitGrouping(DefenderGrouping)
else
Spawn:InitGrouping()
end
local TakeoffMethod=self:GetSquadronTakeoff(SquadronName)
local Defender=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,TakeoffMethod,DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude)
self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping)
return Defender,DefenderGrouping
end
return nil,nil
end
function AI_A2A_DISPATCHER:onafterCAP(From,Event,To,SquadronName)
self:F({SquadronName=SquadronName})
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{}
local DefenderSquadron=self:CanCAP(SquadronName)
if DefenderSquadron then
local Cap=DefenderSquadron.Cap
if Cap then
local DefenderCAP,DefenderGrouping=self:ResourceActivate(DefenderSquadron)
if DefenderCAP then
local AI_A2A_Fsm=AI_A2A_CAP:New2(DefenderCAP,Cap.EngageMinSpeed,Cap.EngageMaxSpeed,Cap.EngageFloorAltitude,Cap.EngageCeilingAltitude,Cap.EngageAltType,Cap.Zone,Cap.PatrolMinSpeed,Cap.PatrolMaxSpeed,Cap.PatrolFloorAltitude,Cap.PatrolCeilingAltitude,Cap.PatrolAltType)
AI_A2A_Fsm:SetDispatcher(self)
AI_A2A_Fsm:SetHomeAirbase(DefenderSquadron.Airbase)
AI_A2A_Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60)
AI_A2A_Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold)
AI_A2A_Fsm:SetDisengageRadius(self.DisengageRadius)
AI_A2A_Fsm:SetTanker(DefenderSquadron.TankerName or self.DefenderDefault.TankerName)
if DefenderSquadron.Racetrack or self.DefenderDefault.Racetrack then
AI_A2A_Fsm:SetRaceTrackPattern(DefenderSquadron.RacetrackLengthMin or self.DefenderDefault.RacetrackLengthMin,
DefenderSquadron.RacetrackLengthMax or self.DefenderDefault.RacetrackLengthMax,
DefenderSquadron.RacetrackHeadingMin or self.DefenderDefault.RacetrackHeadingMin,
DefenderSquadron.RacetrackHeadingMax or self.DefenderDefault.RacetrackHeadingMax,
DefenderSquadron.RacetrackDurationMin or self.DefenderDefault.RacetrackDurationMin,
DefenderSquadron.RacetrackDurationMax or self.DefenderDefault.RacetrackDurationMax,
DefenderSquadron.RacetrackCoordinates or self.DefenderDefault.RacetrackCoordinates)
end
AI_A2A_Fsm:Start()
self:SetDefenderTask(SquadronName,DefenderCAP,"CAP",AI_A2A_Fsm)
function AI_A2A_Fsm:onafterTakeoff(DefenderGroup,From,Event,To)
if DefenderGroup and DefenderGroup:IsAlive()then
self:F({"CAP Takeoff",DefenderGroup:GetName()})
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=AI_A2A_Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if Squadron then
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName.." Wheels up.",DefenderGroup)
end
AI_A2A_Fsm:__Patrol(2)
end
end
end
function AI_A2A_Fsm:onafterPatrolRoute(DefenderGroup,From,Event,To)
if DefenderGroup and DefenderGroup:IsAlive()then
self:F({"CAP PatrolRoute",DefenderGroup:GetName()})
self:GetParent(self).onafterPatrolRoute(self,DefenderGroup,From,Event,To)
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=self:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if Squadron and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", patrolling.",DefenderGroup)
end
Dispatcher:ClearDefenderTaskTarget(DefenderGroup)
end
end
function AI_A2A_Fsm:onafterRTB(DefenderGroup,From,Event,To)
if DefenderGroup and DefenderGroup:IsAlive()then
self:F({"CAP RTB",DefenderGroup:GetName()})
self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To)
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=self:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if Squadron and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName.." returning to base.",DefenderGroup)
end
Dispatcher:ClearDefenderTaskTarget(DefenderGroup)
end
end
function AI_A2A_Fsm:onafterHome(Defender,From,Event,To,Action)
if Defender and Defender:IsAlive()then
self:F({"CAP Home",Defender:GetName()})
self:GetParent(self).onafterHome(self,Defender,From,Event,To)
local Dispatcher=self:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(Defender)
if Action and Action=="Destroy"then
Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender)
Defender:Destroy()
end
if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2A_DISPATCHER.Landing.NearAirbase then
Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender)
Defender:Destroy()
Dispatcher:ParkDefender(Squadron)
end
end
end
end
end
end
end
function AI_A2A_DISPATCHER:onafterENGAGE(From,Event,To,AttackerDetection,Defenders)
self:F("ENGAGING Detection ID="..tostring(AttackerDetection.ID))
if Defenders then
for DefenderID,Defender in pairs(Defenders)do
local Fsm=self:GetDefenderTaskFsm(Defender)
Fsm:EngageRoute(AttackerDetection.Set)
self:SetDefenderTaskTarget(Defender,AttackerDetection)
end
end
end
function AI_A2A_DISPATCHER:onafterGCI(From,Event,To,AttackerDetection,DefendersMissing,DefenderFriendlies)
self:F("GCI Detection ID="..tostring(AttackerDetection.ID))
self:F({From,Event,To,AttackerDetection.Index,DefendersMissing,DefenderFriendlies})
local AttackerSet=AttackerDetection.Set
local AttackerUnit=AttackerSet:GetFirst()
if AttackerUnit and AttackerUnit:IsAlive()then
local AttackerCount=AttackerSet:Count()
local DefenderCount=0
for DefenderID,DefenderGroup in pairs(DefenderFriendlies or{})do
local Fsm=self:GetDefenderTaskFsm(DefenderGroup)
Fsm:__EngageRoute(0.1,AttackerSet)
self:SetDefenderTaskTarget(DefenderGroup,AttackerDetection)
DefenderCount=DefenderCount+DefenderGroup:GetSize()
end
self:F({DefenderCount=DefenderCount,DefendersMissing=DefendersMissing})
DefenderCount=DefendersMissing
local ClosestDistance=0
local ClosestDefenderSquadronName=nil
local BreakLoop=false
while(DefenderCount>0 and not BreakLoop)do
self:F({DefenderSquadrons=self.DefenderSquadrons})
for SquadronName,DefenderSquadron in pairs(self.DefenderSquadrons or{})do
self:F({GCI=DefenderSquadron.Gci})
for InterceptID,Intercept in pairs(DefenderSquadron.Gci or{})do
self:F({DefenderSquadron})
local SpawnCoord=DefenderSquadron.Airbase:GetCoordinate()
local AttackerCoord=AttackerUnit:GetCoordinate()
local InterceptCoord=AttackerDetection.InterceptCoord
self:F({InterceptCoord=InterceptCoord})
if InterceptCoord then
local InterceptDistance=SpawnCoord:Get2DDistance(InterceptCoord)
local AirbaseDistance=SpawnCoord:Get2DDistance(AttackerCoord)
self:F({InterceptDistance=InterceptDistance,AirbaseDistance=AirbaseDistance,InterceptCoord=InterceptCoord})
if ClosestDistance==0 or InterceptDistance<ClosestDistance then
if AirbaseDistance<=self.GciRadius then
ClosestDistance=InterceptDistance
ClosestDefenderSquadronName=SquadronName
end
end
end
end
end
if ClosestDefenderSquadronName then
local DefenderSquadron=self:CanGCI(ClosestDefenderSquadronName)
if DefenderSquadron then
local Gci=self.DefenderSquadrons[ClosestDefenderSquadronName].Gci
if Gci then
local DefenderOverhead=DefenderSquadron.Overhead or self.DefenderDefault.Overhead
local DefenderGrouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping
local DefendersNeeded=math.ceil(DefenderCount*DefenderOverhead)
self:F({Overhead=DefenderOverhead,SquadronOverhead=DefenderSquadron.Overhead,DefaultOverhead=self.DefenderDefault.Overhead})
self:F({Grouping=DefenderGrouping,SquadronGrouping=DefenderSquadron.Grouping,DefaultGrouping=self.DefenderDefault.Grouping})
self:F({DefendersCount=DefenderCount,DefendersNeeded=DefendersNeeded})
if DefenderSquadron.ResourceCount and DefendersNeeded>DefenderSquadron.ResourceCount then
DefendersNeeded=DefenderSquadron.ResourceCount
BreakLoop=true
end
while(DefendersNeeded>0)do
local DefenderGCI,DefenderGrouping=self:ResourceActivate(DefenderSquadron,DefendersNeeded)
DefendersNeeded=DefendersNeeded-DefenderGrouping
if DefenderGCI then
DefenderCount=DefenderCount-DefenderGrouping/DefenderOverhead
local Fsm=AI_A2A_GCI:New2(DefenderGCI,Gci.EngageMinSpeed,Gci.EngageMaxSpeed,Gci.EngageFloorAltitude,Gci.EngageCeilingAltitude,Gci.EngageAltType)
Fsm:SetDispatcher(self)
Fsm:SetHomeAirbase(DefenderSquadron.Airbase)
Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60)
Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold)
Fsm:SetDisengageRadius(self.DisengageRadius)
Fsm:Start()
self:SetDefenderTask(ClosestDefenderSquadronName,DefenderGCI,"GCI",Fsm,AttackerDetection)
function Fsm:onafterTakeoff(DefenderGroup,From,Event,To)
self:F({"GCI Birth",DefenderGroup:GetName()})
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
local DefenderTarget=Dispatcher:GetDefenderTaskTarget(DefenderGroup)
if DefenderTarget then
if Squadron.Language=="EN"and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName.." wheels up.",DefenderGroup)
elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName.." колёса вверх.",DefenderGroup)
end
Fsm:EngageRoute(DefenderTarget.Set)
end
end
function Fsm:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit)
self:F({"GCI Route",DefenderGroup:GetName()})
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if Squadron and AttackSetUnit:Count()>0 then
local FirstUnit=AttackSetUnit:GetFirst()
local Coordinate=FirstUnit:GetCoordinate()
if Squadron.Language=="EN"and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", intercepting bogeys at "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup)
elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", перехватывая боги в "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup)
elseif Squadron.Language=="DE"and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", Eindringlinge abfangen bei"..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup)
end
end
self:GetParent(Fsm).onafterEngageRoute(self,DefenderGroup,From,Event,To,AttackSetUnit)
end
function Fsm:onafterEngage(DefenderGroup,From,Event,To,AttackSetUnit)
self:F({"GCI Engage",DefenderGroup:GetName()})
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if Squadron and AttackSetUnit:Count()>0 then
local FirstUnit=AttackSetUnit:GetFirst()
local Coordinate=FirstUnit:GetCoordinate()
if Squadron.Language=="EN"and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", engaging bogeys at "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup)
elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", задействуя боги в "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup)
end
end
self:GetParent(Fsm).onafterEngage(self,DefenderGroup,From,Event,To,AttackSetUnit)
end
function Fsm:onafterRTB(DefenderGroup,From,Event,To)
self:F({"GCI RTB",DefenderGroup:GetName()})
self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To)
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=self:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if Squadron then
if Squadron.Language=="EN"and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName.." returning to base.",DefenderGroup)
elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", возвращение на базу.",DefenderGroup)
end
end
Dispatcher:ClearDefenderTaskTarget(DefenderGroup)
end
function Fsm:onafterLostControl(Defender,From,Event,To)
self:F({"GCI LostControl",Defender:GetName()})
self:GetParent(self).onafterHome(self,Defender,From,Event,To)
local Dispatcher=Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(Defender)
if Defender:IsAboveRunway()then
Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender)
Defender:Destroy()
end
end
function Fsm:onafterHome(DefenderGroup,From,Event,To,Action)
self:F({"GCI Home",DefenderGroup:GetName()})
self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To)
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=self:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if Squadron.Language=="EN"and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName.." landing at base.",DefenderGroup)
elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", посадка на базу.",DefenderGroup)
end
if Action and Action=="Destroy"then
Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup)
DefenderGroup:Destroy()
end
if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2A_DISPATCHER.Landing.NearAirbase then
Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup)
DefenderGroup:Destroy()
Dispatcher:ParkDefender(Squadron)
end
end
end
end
end
else
BreakLoop=true
break
end
else
break
end
end
end
end
function AI_A2A_DISPATCHER:EvaluateENGAGE(DetectedItem)
self:F({DetectedItem.ItemID})
local DefenderCount=self:CountDefendersEngaged(DetectedItem)
local DefenderGroups=self:CountDefendersToBeEngaged(DetectedItem,DefenderCount)
self:F({DefenderCount=DefenderCount})
if DefenderGroups and DetectedItem.IsDetected==true then
return DefenderGroups
end
return nil
end
function AI_A2A_DISPATCHER:EvaluateGCI(DetectedItem)
self:F({DetectedItem.ItemID})
local AttackerSet=DetectedItem.Set
local AttackerCount=AttackerSet:Count()
local DefenderCount=self:CountDefendersEngaged(DetectedItem)
local DefendersMissing=AttackerCount-DefenderCount
self:F({AttackerCount=AttackerCount,DefenderCount=DefenderCount,DefendersMissing=DefendersMissing})
local Friendlies=self:CountDefendersToBeEngaged(DetectedItem,DefenderCount)
if DetectedItem.IsDetected==true then
return DefendersMissing,Friendlies
end
return nil,nil
end
function AI_A2A_DISPATCHER:Order(DetectedItem)
local detection=self.Detection
local ShortestDistance=999999999
local AttackCoordinate=detection:GetDetectedItemCoordinate(DetectedItem)
if AttackCoordinate then
for DefenderSquadronName,DefenderSquadron in pairs(self.DefenderSquadrons)do
self:T({DefenderSquadron=DefenderSquadron.Name})
local Airbase=DefenderSquadron.Airbase
local AirbaseCoordinate=Airbase:GetCoordinate()
local EvaluateDistance=AttackCoordinate:Get2DDistance(AirbaseCoordinate)
if EvaluateDistance<=ShortestDistance then
ShortestDistance=EvaluateDistance
end
end
end
return ShortestDistance
end
function AI_A2A_DISPATCHER:ShowTacticalDisplay(Detection)
local AreaMsg={}
local TaskMsg={}
local ChangeMsg={}
local TaskReport=REPORT:New()
local Report=REPORT:New("Tactical Overview:")
local DefenderGroupCount=0
for DetectedItemID,DetectedItem in UTILS.spairs(Detection:GetDetectedItems(),function(t,a,b)
return self:Order(t[a])<self:Order(t[b])
end)do
local DetectedItem=DetectedItem
local DetectedSet=DetectedItem.Set
local DetectedCount=DetectedSet:Count()
local DetectedZone=DetectedItem.Zone
self:F({"Target ID",DetectedItem.ItemID})
DetectedSet:Flush(self)
local DetectedID=DetectedItem.ID
local DetectionIndex=DetectedItem.Index
local DetectedItemChanged=DetectedItem.Changed
Report:Add(string.format("\n- Target %s (%s): (#%d) %s",DetectedItem.ItemID,DetectedItem.Index,DetectedItem.Set:Count(),DetectedItem.Set:GetObjectNames()))
for Defender,DefenderTask in pairs(self:GetDefenderTasks())do
local Defender=Defender
if DefenderTask.Target and DefenderTask.Target.Index==DetectedItem.Index then
if Defender and Defender:IsAlive()then
DefenderGroupCount=DefenderGroupCount+1
local Fuel=Defender:GetFuelMin()*100
local Damage=Defender:GetLife()/Defender:GetLife0()*100
Report:Add(string.format(" - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s",
Defender:GetName(),
Defender:GetSize(),
Defender:GetInitialSize(),
DefenderTask.Type,
DefenderTask.Fsm:GetState(),
Defender:GetSize(),
Fuel,
Damage,
Defender:HasTask()==true and"Executing"or"Idle"))
end
end
end
end
Report:Add("\n- No Targets:")
local TaskCount=0
for Defender,DefenderTask in pairs(self:GetDefenderTasks())do
TaskCount=TaskCount+1
local Defender=Defender
if not DefenderTask.Target then
if Defender:IsAlive()then
local DefenderHasTask=Defender:HasTask()
local Fuel=Defender:GetFuelMin()*100
local Damage=Defender:GetLife()/Defender:GetLife0()*100
DefenderGroupCount=DefenderGroupCount+1
Report:Add(string.format(" - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s",
Defender:GetName(),
Defender:GetSize(),
Defender:GetInitialSize(),
DefenderTask.Type,
DefenderTask.Fsm:GetState(),
Defender:GetSize(),
Fuel,
Damage,
Defender:HasTask()==true and"Executing"or"Idle"))
end
end
end
Report:Add(string.format("\n- %d Tasks - %d Defender Groups",TaskCount,DefenderGroupCount))
self:F(Report:Text("\n"))
trigger.action.outText(Report:Text("\n"),25)
return true
end
function AI_A2A_DISPATCHER:ProcessDetected(Detection)
local AreaMsg={}
local TaskMsg={}
local ChangeMsg={}
local TaskReport=REPORT:New()
for AIGroup,DefenderTask in pairs(self:GetDefenderTasks())do
local AIGroup=AIGroup
if not AIGroup:IsAlive()then
local DefenderTaskFsm=self:GetDefenderTaskFsm(AIGroup)
self:F({Defender=AIGroup:GetName(),DefenderState=DefenderTaskFsm:GetState()})
if not DefenderTaskFsm:Is("Started")then
self:ClearDefenderTask(AIGroup)
end
else
if DefenderTask.Target then
local AttackerItem=Detection:GetDetectedItemByIndex(DefenderTask.Target.Index)
if not AttackerItem then
self:F({"Removing obsolete Target:",DefenderTask.Target.Index})
self:ClearDefenderTaskTarget(AIGroup)
else
if DefenderTask.Target.Set then
local AttackerCount=DefenderTask.Target.Set:Count()
if AttackerCount==0 then
self:F({"All Targets destroyed in Target, removing:",DefenderTask.Target.Index})
self:ClearDefenderTaskTarget(AIGroup)
end
end
end
end
end
end
local Report=REPORT:New("Tactical Overviews")
local DefenderGroupCount=0
for DetectedItemID,DetectedItem in UTILS.spairs(Detection:GetDetectedItems(),function(t,a,b)
return self:Order(t[a])<self:Order(t[b])
end)do
local DetectedItem=DetectedItem
local DetectedSet=DetectedItem.Set
local DetectedCount=DetectedSet:Count()
local DetectedZone=DetectedItem.Zone
self:F({"Target ID",DetectedItem.ItemID})
DetectedSet:Flush(self)
local DetectedID=DetectedItem.ID
local DetectionIndex=DetectedItem.Index
local DetectedItemChanged=DetectedItem.Changed
do
local Friendlies=self:EvaluateENGAGE(DetectedItem)
if Friendlies then
self:F({AIGroups=Friendlies})
self:ENGAGE(DetectedItem,Friendlies)
end
end
do
local DefendersMissing,Friendlies=self:EvaluateGCI(DetectedItem)
if DefendersMissing and DefendersMissing>0 then
self:F({DefendersMissing=DefendersMissing})
self:GCI(DetectedItem,DefendersMissing,Friendlies)
end
end
end
if self.TacticalDisplay then
self:ShowTacticalDisplay(Detection)
end
return true
end
end
do
function AI_A2A_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem)
local DetectedSet=DetectedItem.Set
local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem)
local PlayerTypes={}
local PlayersCount=0
if PlayersNearBy then
local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G()
for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do
local PlayerUnit=PlayerUnitData
local PlayerName=PlayerUnit:GetPlayerName()
if PlayerUnit:IsAirPlane()and PlayerName~=nil then
local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel()
PlayersCount=PlayersCount+1
local PlayerType=PlayerUnit:GetTypeName()
PlayerTypes[PlayerName]=PlayerType
if DetectedTreatLevel<FriendlyUnitThreatLevel+2 then
end
end
end
end
local PlayerTypesReport=REPORT:New()
if PlayersCount>0 then
for PlayerName,PlayerType in pairs(PlayerTypes)do
PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType))
end
else
PlayerTypesReport:Add("-")
end
return PlayersCount,PlayerTypesReport
end
function AI_A2A_DISPATCHER:GetFriendliesNearBy(DetectedItem)
local DetectedSet=DetectedItem.Set
local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem)
local FriendlyTypes={}
local FriendliesCount=0
if FriendlyUnitsNearBy then
local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G()
for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do
local FriendlyUnit=FriendlyUnitData
if FriendlyUnit:IsAirPlane()then
local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel()
FriendliesCount=FriendliesCount+1
local FriendlyType=FriendlyUnit:GetTypeName()
FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1
if DetectedTreatLevel<FriendlyUnitThreatLevel+2 then
end
end
end
end
local FriendlyTypesReport=REPORT:New()
if FriendliesCount>0 then
for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do
FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType))
end
else
FriendlyTypesReport:Add("-")
end
return FriendliesCount,FriendlyTypesReport
end
function AI_A2A_DISPATCHER:SchedulerCAP(SquadronName)
self:CAP(SquadronName)
end
function AI_A2A_DISPATCHER:AddToSquadron(Squadron,Amount)
local Squadron=self:GetSquadron(Squadron)
if Squadron.ResourceCount then
Squadron.ResourceCount=Squadron.ResourceCount+Amount
end
self:T({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount})
end
function AI_A2A_DISPATCHER:RemoveFromSquadron(Squadron,Amount)
local Squadron=self:GetSquadron(Squadron)
if Squadron.ResourceCount then
Squadron.ResourceCount=Squadron.ResourceCount-Amount
end
self:T({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount})
end
end
do
AI_A2A_GCICAP={
ClassName="AI_A2A_GCICAP",
Detection=nil,
}
function AI_A2A_GCICAP:New(EWRPrefixes,TemplatePrefixes,CapPrefixes,CapLimit,GroupingRadius,EngageRadius,GciRadius,ResourceCount)
local EWRSetGroup=SET_GROUP:New()
EWRSetGroup:FilterPrefixes(EWRPrefixes)
EWRSetGroup:FilterStart()
local Detection=DETECTION_AREAS:New(EWRSetGroup,GroupingRadius or 30000)
local self=BASE:Inherit(self,AI_A2A_DISPATCHER:New(Detection))
self:SetEngageRadius(EngageRadius)
self:SetGciRadius(GciRadius)
local EWRFirst=EWRSetGroup:GetFirst()
local EWRCoalition=EWRFirst:GetCoalition()
local AirbaseNames={}
for AirbaseID,AirbaseData in pairs(_DATABASE.AIRBASES)do
local Airbase=AirbaseData
local AirbaseName=Airbase:GetName()
if Airbase:GetCoalition()==EWRCoalition then
table.insert(AirbaseNames,AirbaseName)
end
end
self.Templates=SET_GROUP:New():FilterPrefixes(TemplatePrefixes):FilterOnce()
self:I({Airbases=AirbaseNames})
self:I("Defining Templates for Airbases ...")
for AirbaseID,AirbaseName in pairs(AirbaseNames)do
local Airbase=_DATABASE:FindAirbase(AirbaseName)
local AirbaseName=Airbase:GetName()
local AirbaseCoord=Airbase:GetCoordinate()
local AirbaseZone=ZONE_RADIUS:New("Airbase",AirbaseCoord:GetVec2(),3000)
local Templates=nil
self:I({Airbase=AirbaseName})
for TemplateID,Template in pairs(self.Templates:GetSet())do
local Template=Template
local TemplateCoord=Template:GetCoordinate()
if AirbaseZone:IsVec2InZone(TemplateCoord:GetVec2())then
Templates=Templates or{}
table.insert(Templates,Template:GetName())
self:I({Template=Template:GetName()})
end
end
if Templates then
self:SetSquadron(AirbaseName,AirbaseName,Templates,ResourceCount)
end
end
self.CAPTemplates=SET_GROUP:New()
self.CAPTemplates:FilterPrefixes(CapPrefixes)
self.CAPTemplates:FilterOnce()
self:I("Setting up CAP ...")
for CAPID,CAPTemplate in pairs(self.CAPTemplates:GetSet())do
local CAPZone=ZONE_POLYGON:New(CAPTemplate:GetName(),CAPTemplate)
local AirbaseDistance=99999999
local AirbaseClosest=nil
self:I({CAPZoneGroup=CAPID})
for AirbaseID,AirbaseName in pairs(AirbaseNames)do
local Airbase=_DATABASE:FindAirbase(AirbaseName)
local AirbaseName=Airbase:GetName()
local AirbaseCoord=Airbase:GetCoordinate()
local Squadron=self.DefenderSquadrons[AirbaseName]
if Squadron then
local Distance=AirbaseCoord:Get2DDistance(CAPZone:GetCoordinate())
self:I({AirbaseDistance=Distance})
if Distance<AirbaseDistance then
AirbaseDistance=Distance
AirbaseClosest=Airbase
end
end
end
if AirbaseClosest then
self:I({CAPAirbase=AirbaseClosest:GetName()})
self:SetSquadronCap(AirbaseClosest:GetName(),CAPZone,6000,10000,500,800,800,1200,"RADIO")
self:SetSquadronCapInterval(AirbaseClosest:GetName(),CapLimit,300,600,1)
end
end
self:I("Setting up GCI ...")
for AirbaseID,AirbaseName in pairs(AirbaseNames)do
local Airbase=_DATABASE:FindAirbase(AirbaseName)
local AirbaseName=Airbase:GetName()
local Squadron=self.DefenderSquadrons[AirbaseName]
self:F({Airbase=AirbaseName})
if Squadron then
self:I({GCIAirbase=AirbaseName})
self:SetSquadronGci(AirbaseName,800,1200)
end
end
self:__Start(5)
self:HandleEvent(EVENTS.Crash,self.OnEventCrashOrDead)
self:HandleEvent(EVENTS.Dead,self.OnEventCrashOrDead)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.EngineShutdown)
return self
end
function AI_A2A_GCICAP:NewWithBorder(EWRPrefixes,TemplatePrefixes,BorderPrefix,CapPrefixes,CapLimit,GroupingRadius,EngageRadius,GciRadius,ResourceCount)
local self=AI_A2A_GCICAP:New(EWRPrefixes,TemplatePrefixes,CapPrefixes,CapLimit,GroupingRadius,EngageRadius,GciRadius,ResourceCount)
if BorderPrefix then
self:SetBorderZone(ZONE_POLYGON:New(BorderPrefix,GROUP:FindByName(BorderPrefix)))
end
return self
end
end
AI_A2G_BAI={
ClassName="AI_A2G_BAI",
}
function AI_A2G_BAI:New2(AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
local AI_Air=AI_AIR:New(AIGroup)
local AI_Air_Patrol=AI_AIR_PATROL:New(AI_Air,AIGroup,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
local AI_Air_Engage=AI_AIR_ENGAGE:New(AI_Air_Patrol,AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local self=BASE:Inherit(self,AI_Air_Engage)
return self
end
function AI_A2G_BAI:New(AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
return self:New2(AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,PatrolAltType,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
end
function AI_A2G_BAI:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude)
local AttackUnitTasks={}
local AttackSetUnitPerThreatLevel=AttackSetUnit:GetSetPerThreatLevel(10,0)
for AttackUnitIndex,AttackUnit in ipairs(AttackSetUnitPerThreatLevel or{})do
if AttackUnit then
if AttackUnit:IsAlive()and AttackUnit:IsGround()then
self:T({"BAI Unit:",AttackUnit:GetName()})
AttackUnitTasks[#AttackUnitTasks+1]=DefenderGroup:TaskAttackUnit(AttackUnit,true,false,nil,nil,EngageAltitude)
end
end
end
return AttackUnitTasks
end
AI_A2G_CAS={
ClassName="AI_A2G_CAS",
}
function AI_A2G_CAS:New2(AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
local AI_Air=AI_AIR:New(AIGroup)
local AI_Air_Patrol=AI_AIR_PATROL:New(AI_Air,AIGroup,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
local AI_Air_Engage=AI_AIR_ENGAGE:New(AI_Air_Patrol,AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local self=BASE:Inherit(self,AI_Air_Engage)
return self
end
function AI_A2G_CAS:New(AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
return self:New2(AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,PatrolAltType,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
end
function AI_A2G_CAS:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude)
local AttackUnitTasks={}
local AttackSetUnitPerThreatLevel=AttackSetUnit:GetSetPerThreatLevel(10,0)
for AttackUnitIndex,AttackUnit in ipairs(AttackSetUnitPerThreatLevel or{})do
if AttackUnit then
if AttackUnit:IsAlive()and AttackUnit:IsGround()then
self:T({"CAS Unit:",AttackUnit:GetName()})
AttackUnitTasks[#AttackUnitTasks+1]=DefenderGroup:TaskAttackUnit(AttackUnit,true,false,nil,nil,EngageAltitude)
end
end
end
return AttackUnitTasks
end
AI_A2G_SEAD={
ClassName="AI_A2G_SEAD",
}
function AI_A2G_SEAD:New2(AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
local AI_Air=AI_AIR:New(AIGroup)
local AI_Air_Patrol=AI_AIR_PATROL:New(AI_Air,AIGroup,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
local AI_Air_Engage=AI_AIR_ENGAGE:New(AI_Air_Patrol,AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local self=BASE:Inherit(self,AI_Air_Engage)
return self
end
function AI_A2G_SEAD:New(AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
return self:New2(AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,PatrolAltType,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
end
function AI_A2G_SEAD:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude)
local AttackUnitTasks={}
local AttackSetUnitPerThreatLevel=AttackSetUnit:GetSetPerThreatLevel(10,0)
for AttackUnitID,AttackUnit in ipairs(AttackSetUnitPerThreatLevel)do
if AttackUnit then
if AttackUnit:IsAlive()and AttackUnit:IsGround()then
local HasRadar=AttackUnit:HasSEAD()
if HasRadar then
self:F({"SEAD Unit:",AttackUnit:GetName()})
AttackUnitTasks[#AttackUnitTasks+1]=DefenderGroup:TaskAttackUnit(AttackUnit,true,false,nil,nil,EngageAltitude)
end
end
end
end
return AttackUnitTasks
end
do
AI_A2G_DISPATCHER={
ClassName="AI_A2G_DISPATCHER",
Detection=nil,
}
AI_A2G_DISPATCHER.DefenseCoordinates={}
AI_A2G_DISPATCHER.Takeoff=GROUP.Takeoff
AI_A2G_DISPATCHER.Landing={
NearAirbase=1,
AtRunway=2,
AtEngineShutdown=3,
}
AI_A2G_DISPATCHER.DefenseQueue={}
AI_A2G_DISPATCHER.DefenseApproach={
Random=1,
Distance=2,
}
function AI_A2G_DISPATCHER:New(Detection)
local self=BASE:Inherit(self,DETECTION_MANAGER:New(nil,Detection))
self.Detection=Detection
self.Detection:FilterCategories(Unit.Category.GROUND_UNIT)
self.DefenderSquadrons={}
self.DefenderSpawns={}
self.DefenderTasks={}
self.DefenderDefault={}
self.SetSendPlayerMessages=false
self:SetDefenseRadius()
self:SetDefenseLimit(nil)
self:SetDefenseApproach(AI_A2G_DISPATCHER.DefenseApproach.Random)
self:SetIntercept(300)
self:SetDisengageRadius(300000)
self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Air)
self:SetDefaultTakeoffInAirAltitude(500)
self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.NearAirbase)
self:SetDefaultOverhead(1)
self:SetDefaultGrouping(1)
self:SetDefaultFuelThreshold(0.15,0)
self:SetDefaultDamageThreshold(0.4)
self:SetDefaultPatrolTimeInterval(180,600)
self:SetDefaultPatrolLimit(1)
self:AddTransition("Started","Assign","Started")
self:AddTransition("*","Patrol","*")
self:AddTransition("*","Defend","*")
self:AddTransition("*","Engage","*")
self:HandleEvent(EVENTS.Crash,self.OnEventCrashOrDead)
self:HandleEvent(EVENTS.Dead,self.OnEventCrashOrDead)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.EngineShutdown)
self:HandleEvent(EVENTS.BaseCaptured)
self:SetTacticalDisplay(false)
self.DefenderPatrolIndex=0
self:SetDefenseReactivityMedium()
self.TakeoffScheduleID=self:ScheduleRepeat(10,10,0,nil,self.ResourceTakeoff,self)
self:__Start(1)
return self
end
function AI_A2G_DISPATCHER:onafterStart(From,Event,To)
self:GetParent(self).onafterStart(self,From,Event,To)
for SquadronName,DefenderSquadron in pairs(self.DefenderSquadrons)do
DefenderSquadron.Resource={}
for Resource=1,DefenderSquadron.ResourceCount or 0 do
self:ResourcePark(DefenderSquadron)
end
self:I("Parked resources for squadron "..DefenderSquadron.Name)
end
end
function AI_A2G_DISPATCHER:Lock(DetectedItemIndex)
self:F({DetectedItemIndex=DetectedItemIndex})
local DetectedItem=self.Detection:GetDetectedItemByIndex(DetectedItemIndex)
if DetectedItem then
self:F({Locked=DetectedItem})
self.Detection:LockDetectedItem(DetectedItem)
end
end
function AI_A2G_DISPATCHER:Unlock(DetectedItemIndex)
self:F({DetectedItemIndex=DetectedItemIndex})
self:F({Index=self.Detection.DetectedItemsByIndex})
local DetectedItem=self.Detection:GetDetectedItemByIndex(DetectedItemIndex)
if DetectedItem then
self:F({Unlocked=DetectedItem})
self.Detection:UnlockDetectedItem(DetectedItem)
end
end
function AI_A2G_DISPATCHER:SetDefenseLimit(DefenseLimit)
self:F({DefenseLimit=DefenseLimit})
self.DefenseLimit=DefenseLimit
end
function AI_A2G_DISPATCHER:SetDefenseApproach(DefenseApproach)
self:F({DefenseApproach=DefenseApproach})
self._DefenseApproach=DefenseApproach
end
function AI_A2G_DISPATCHER:ResourcePark(DefenderSquadron)
local TemplateID=math.random(1,#DefenderSquadron.Spawn)
local Spawn=DefenderSquadron.Spawn[TemplateID]
Spawn:InitGrouping(1)
local SpawnGroup
if self:IsSquadronVisible(DefenderSquadron.Name)then
SpawnGroup=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,SPAWN.Takeoff.Cold)
local GroupName=SpawnGroup:GetName()
DefenderSquadron.Resources=DefenderSquadron.Resources or{}
DefenderSquadron.Resources[TemplateID]=DefenderSquadron.Resources[TemplateID]or{}
DefenderSquadron.Resources[TemplateID][GroupName]={}
DefenderSquadron.Resources[TemplateID][GroupName]=SpawnGroup
end
end
function AI_A2G_DISPATCHER:OnEventBaseCaptured(EventData)
local AirbaseName=EventData.PlaceName
self:I("Captured "..AirbaseName)
for SquadronName,Squadron in pairs(self.DefenderSquadrons)do
if Squadron.AirbaseName==AirbaseName then
Squadron.ResourceCount=-999
Squadron.Captured=true
self:I("Squadron "..SquadronName.." captured.")
end
end
end
function AI_A2G_DISPATCHER:OnEventCrashOrDead(EventData)
self.Detection:ForgetDetectedUnit(EventData.IniUnitName)
end
function AI_A2G_DISPATCHER:OnEventLand(EventData)
self:F("Landed")
local DefenderUnit=EventData.IniUnit
local Defender=EventData.IniGroup
local Squadron=self:GetSquadronFromDefender(Defender)
if Squadron then
self:F({SquadronName=Squadron.Name})
local LandingMethod=self:GetSquadronLanding(Squadron.Name)
if LandingMethod==AI_A2G_DISPATCHER.Landing.AtRunway then
local DefenderSize=Defender:GetSize()
if DefenderSize==1 then
self:RemoveDefenderFromSquadron(Squadron,Defender)
end
DefenderUnit:Destroy()
self:ResourcePark(Squadron,Defender)
return
end
if DefenderUnit:GetLife()~=DefenderUnit:GetLife0()then
DefenderUnit:Destroy()
return
end
end
end
function AI_A2G_DISPATCHER:OnEventEngineShutdown(EventData)
local DefenderUnit=EventData.IniUnit
local Defender=EventData.IniGroup
local Squadron=self:GetSquadronFromDefender(Defender)
if Squadron then
self:F({SquadronName=Squadron.Name})
local LandingMethod=self:GetSquadronLanding(Squadron.Name)
if LandingMethod==AI_A2G_DISPATCHER.Landing.AtEngineShutdown and
not DefenderUnit:InAir()then
local DefenderSize=Defender:GetSize()
if DefenderSize==1 then
self:RemoveDefenderFromSquadron(Squadron,Defender)
end
DefenderUnit:Destroy()
self:ResourcePark(Squadron,Defender)
end
end
end
do
function AI_A2G_DISPATCHER:AddDefenseCoordinate(DefenseCoordinateName,DefenseCoordinate)
self.DefenseCoordinates[DefenseCoordinateName]=DefenseCoordinate
end
function AI_A2G_DISPATCHER:SetDefenseReactivityLow()
self.DefenseReactivity=0.05
end
function AI_A2G_DISPATCHER:SetDefenseReactivityMedium()
self.DefenseReactivity=0.15
end
function AI_A2G_DISPATCHER:SetDefenseReactivityHigh()
self.DefenseReactivity=0.5
end
end
function AI_A2G_DISPATCHER:SetDisengageRadius(DisengageRadius)
self.DisengageRadius=DisengageRadius or 300000
return self
end
function AI_A2G_DISPATCHER:SetDefenseRadius(DefenseRadius)
self.DefenseRadius=DefenseRadius or 100000
self.Detection:SetAcceptRange(self.DefenseRadius)
return self
end
function AI_A2G_DISPATCHER:SetBorderZone(BorderZone)
self.Detection:SetAcceptZones(BorderZone)
return self
end
function AI_A2G_DISPATCHER:SetTacticalDisplay(TacticalDisplay)
self.TacticalDisplay=TacticalDisplay
return self
end
function AI_A2G_DISPATCHER:SetDefaultDamageThreshold(DamageThreshold)
self.DefenderDefault.DamageThreshold=DamageThreshold
return self
end
function AI_A2G_DISPATCHER:SetDefaultPatrolTimeInterval(PatrolMinSeconds,PatrolMaxSeconds)
self.DefenderDefault.PatrolMinSeconds=PatrolMinSeconds
self.DefenderDefault.PatrolMaxSeconds=PatrolMaxSeconds
return self
end
function AI_A2G_DISPATCHER:SetDefaultPatrolLimit(PatrolLimit)
self.DefenderDefault.PatrolLimit=PatrolLimit
return self
end
function AI_A2G_DISPATCHER:SetDefaultEngageLimit(EngageLimit)
self.DefenderDefault.EngageLimit=EngageLimit
return self
end
function AI_A2G_DISPATCHER:SetIntercept(InterceptDelay)
self.DefenderDefault.InterceptDelay=InterceptDelay
local Detection=self.Detection
Detection:SetIntercept(true,InterceptDelay)
return self
end
function AI_A2G_DISPATCHER:GetDefenderFriendliesNearBy(DetectedItem)
local DefenderFriendliesNearBy={}
local DetectionCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem)
local ScanZone=ZONE_RADIUS:New("ScanZone",DetectionCoordinate:GetVec2(),self.DefenseRadius)
ScanZone:Scan(Object.Category.UNIT,{Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
local DefenderUnits=ScanZone:GetScannedUnits()
for DefenderUnitID,DefenderUnit in pairs(DefenderUnits)do
local DefenderUnit=UNIT:FindByName(DefenderUnit:getName())
DefenderFriendliesNearBy[#DefenderFriendliesNearBy+1]=DefenderUnit
end
return DefenderFriendliesNearBy
end
function AI_A2G_DISPATCHER:GetDefenderTasks()
return self.DefenderTasks or{}
end
function AI_A2G_DISPATCHER:GetDefenderTask(Defender)
return self.DefenderTasks[Defender]
end
function AI_A2G_DISPATCHER:GetDefenderTaskFsm(Defender)
return self:GetDefenderTask(Defender).Fsm
end
function AI_A2G_DISPATCHER:GetDefenderTaskTarget(Defender)
return self:GetDefenderTask(Defender).Target
end
function AI_A2G_DISPATCHER:GetDefenderTaskSquadronName(Defender)
return self:GetDefenderTask(Defender).SquadronName
end
function AI_A2G_DISPATCHER:ClearDefenderTask(Defender)
if Defender:IsAlive()and self.DefenderTasks[Defender]then
local Target=self.DefenderTasks[Defender].Target
local Message="Clearing ("..self.DefenderTasks[Defender].Type..") "
Message=Message..Defender:GetName()
if Target then
Message=Message..(Target and(" from "..Target.Index.." ["..Target.Set:Count().."]"))or""
end
self:F({Target=Message})
end
self.DefenderTasks[Defender]=nil
return self
end
function AI_A2G_DISPATCHER:ClearDefenderTaskTarget(Defender)
local DefenderTask=self:GetDefenderTask(Defender)
if Defender:IsAlive()and DefenderTask then
local Target=DefenderTask.Target
local Message="Clearing ("..DefenderTask.Type..") "
Message=Message..Defender:GetName()
if Target then
Message=Message..(Target and(" from "..Target.Index.." ["..Target.Set:Count().."]"))or""
end
self:F({Target=Message})
end
if Defender and DefenderTask and DefenderTask.Target then
DefenderTask.Target=nil
end
return self
end
function AI_A2G_DISPATCHER:SetDefenderTask(SquadronName,Defender,Type,Fsm,Target,Size)
self:F({SquadronName=SquadronName,Defender=Defender:GetName()})
self.DefenderTasks[Defender]=self.DefenderTasks[Defender]or{}
self.DefenderTasks[Defender].Type=Type
self.DefenderTasks[Defender].Fsm=Fsm
self.DefenderTasks[Defender].SquadronName=SquadronName
self.DefenderTasks[Defender].Size=Size
if Target then
self:SetDefenderTaskTarget(Defender,Target)
end
return self
end
function AI_A2G_DISPATCHER:SetDefenderTaskTarget(Defender,AttackerDetection)
local Message="("..self.DefenderTasks[Defender].Type..") "
Message=Message..Defender:GetName()
Message=Message..(AttackerDetection and(" target "..AttackerDetection.Index.." ["..AttackerDetection.Set:Count().."]"))or""
self:F({AttackerDetection=Message})
if AttackerDetection then
self.DefenderTasks[Defender].Target=AttackerDetection
end
return self
end
function AI_A2G_DISPATCHER:SetSquadron(SquadronName,AirbaseName,TemplatePrefixes,ResourceCount)
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
local DefenderSquadron=self.DefenderSquadrons[SquadronName]
DefenderSquadron.Name=SquadronName
DefenderSquadron.Airbase=AIRBASE:FindByName(AirbaseName)
DefenderSquadron.AirbaseName=DefenderSquadron.Airbase:GetName()
if not DefenderSquadron.Airbase then
error("Cannot find airbase with name:"..AirbaseName)
end
DefenderSquadron.Spawn={}
if type(TemplatePrefixes)=="string"then
local SpawnTemplate=TemplatePrefixes
self.DefenderSpawns[SpawnTemplate]=self.DefenderSpawns[SpawnTemplate]or SPAWN:New(SpawnTemplate)
DefenderSquadron.Spawn[1]=self.DefenderSpawns[SpawnTemplate]
else
for TemplateID,SpawnTemplate in pairs(TemplatePrefixes)do
self.DefenderSpawns[SpawnTemplate]=self.DefenderSpawns[SpawnTemplate]or SPAWN:New(SpawnTemplate)
DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1]=self.DefenderSpawns[SpawnTemplate]
end
end
DefenderSquadron.ResourceCount=ResourceCount
DefenderSquadron.TemplatePrefixes=TemplatePrefixes
DefenderSquadron.Captured=false
self:SetSquadronTakeoffInterval(SquadronName,0)
self:F({Squadron={SquadronName,AirbaseName,TemplatePrefixes,ResourceCount}})
return self
end
function AI_A2G_DISPATCHER:GetSquadron(SquadronName)
local DefenderSquadron=self.DefenderSquadrons[SquadronName]
if not DefenderSquadron then
error("Unknown Squadron:"..SquadronName)
end
return DefenderSquadron
end
function AI_A2G_DISPATCHER:IsSquadronVisible(SquadronName)
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
local DefenderSquadron=self:GetSquadron(SquadronName)
if DefenderSquadron then
return DefenderSquadron.Uncontrolled==true
end
return nil
end
function AI_A2G_DISPATCHER:SetSquadronTakeoffInterval(SquadronName,TakeoffInterval)
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
local DefenderSquadron=self:GetSquadron(SquadronName)
if DefenderSquadron then
DefenderSquadron.TakeoffInterval=TakeoffInterval or 0
DefenderSquadron.TakeoffTime=0
end
end
function AI_A2G_DISPATCHER:SetSquadronPatrolInterval(SquadronName,PatrolLimit,LowInterval,HighInterval,Probability,DefenseTaskType)
local DefenderSquadron=self:GetSquadron(SquadronName)
local Patrol=DefenderSquadron[DefenseTaskType]
if Patrol then
Patrol.LowInterval=LowInterval or 180
Patrol.HighInterval=HighInterval or 600
Patrol.Probability=Probability or 1
Patrol.PatrolLimit=PatrolLimit or 1
Patrol.Scheduler=Patrol.Scheduler or SCHEDULER:New(self)
local Scheduler=Patrol.Scheduler
local ScheduleID=Patrol.ScheduleID
local Variance=(Patrol.HighInterval-Patrol.LowInterval)/2
local Repeat=Patrol.LowInterval+Variance
local Randomization=Variance/Repeat
local Start=math.random(1,Patrol.HighInterval)
if ScheduleID then
Scheduler:Stop(ScheduleID)
end
Patrol.ScheduleID=Scheduler:Schedule(self,self.SchedulerPatrol,{SquadronName},Start,Repeat,Randomization)
else
error("This squadron does not exist:"..SquadronName)
end
end
function AI_A2G_DISPATCHER:SetSquadronSeadPatrolInterval(SquadronName,PatrolLimit,LowInterval,HighInterval,Probability)
self:SetSquadronPatrolInterval(SquadronName,PatrolLimit,LowInterval,HighInterval,Probability,"SEAD")
end
function AI_A2G_DISPATCHER:SetSquadronCasPatrolInterval(SquadronName,PatrolLimit,LowInterval,HighInterval,Probability)
self:SetSquadronPatrolInterval(SquadronName,PatrolLimit,LowInterval,HighInterval,Probability,"CAS")
end
function AI_A2G_DISPATCHER:SetSquadronBaiPatrolInterval(SquadronName,PatrolLimit,LowInterval,HighInterval,Probability)
self:SetSquadronPatrolInterval(SquadronName,PatrolLimit,LowInterval,HighInterval,Probability,"BAI")
end
function AI_A2G_DISPATCHER:GetPatrolDelay(SquadronName)
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
self.DefenderSquadrons[SquadronName].Patrol=self.DefenderSquadrons[SquadronName].Patrol or{}
local DefenderSquadron=self:GetSquadron(SquadronName)
local Patrol=self.DefenderSquadrons[SquadronName].Patrol
if Patrol then
return math.random(Patrol.LowInterval,Patrol.HighInterval)
else
error("This squadron does not exist:"..SquadronName)
end
end
function AI_A2G_DISPATCHER:CanPatrol(SquadronName,DefenseTaskType)
self:F({SquadronName=SquadronName})
local DefenderSquadron=self:GetSquadron(SquadronName)
if DefenderSquadron.Captured==false then
if(not DefenderSquadron.ResourceCount)or(DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount>0)then
local Patrol=DefenderSquadron[DefenseTaskType]
if Patrol and Patrol.Patrol==true then
local PatrolCount=self:CountPatrolAirborne(SquadronName,DefenseTaskType)
self:F({PatrolCount=PatrolCount,PatrolLimit=Patrol.PatrolLimit,PatrolProbability=Patrol.Probability})
if PatrolCount<Patrol.PatrolLimit then
local Probability=math.random()
if Probability<=Patrol.Probability then
return DefenderSquadron,Patrol
end
end
else
self:F("No patrol for "..SquadronName)
end
end
end
return nil
end
function AI_A2G_DISPATCHER:CanDefend(SquadronName,DefenseTaskType)
self:F({SquadronName=SquadronName,DefenseTaskType})
local DefenderSquadron=self:GetSquadron(SquadronName)
if DefenderSquadron.Captured==false then
if(not DefenderSquadron.ResourceCount)or(DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount>0)then
if DefenderSquadron[DefenseTaskType]and(DefenderSquadron[DefenseTaskType].Defend==true)then
return DefenderSquadron,DefenderSquadron[DefenseTaskType]
end
end
end
return nil
end
function AI_A2G_DISPATCHER:SetSquadronEngageLimit(SquadronName,EngageLimit,DefenseTaskType)
local DefenderSquadron=self:GetSquadron(SquadronName)
local Defense=DefenderSquadron[DefenseTaskType]
if Defense then
Defense.EngageLimit=EngageLimit or 1
else
error("This squadron does not exist:"..SquadronName)
end
end
function AI_A2G_DISPATCHER:SetSquadronSead2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.SEAD=DefenderSquadron.SEAD or{}
local Sead=DefenderSquadron.SEAD
Sead.Name=SquadronName
Sead.EngageMinSpeed=EngageMinSpeed
Sead.EngageMaxSpeed=EngageMaxSpeed
Sead.EngageFloorAltitude=EngageFloorAltitude or 500
Sead.EngageCeilingAltitude=EngageCeilingAltitude or 1000
Sead.EngageAltType=EngageAltType
Sead.Defend=true
self:I({SEAD={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}})
return self
end
function AI_A2G_DISPATCHER:SetSquadronSead(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude)
return self:SetSquadronSead2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,"RADIO")
end
function AI_A2G_DISPATCHER:SetSquadronSeadEngageLimit(SquadronName,EngageLimit)
self:SetSquadronEngageLimit(SquadronName,EngageLimit,"SEAD")
end
function AI_A2G_DISPATCHER:SetSquadronSeadPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.SEAD=DefenderSquadron.SEAD or{}
local SeadPatrol=DefenderSquadron.SEAD
SeadPatrol.Name=SquadronName
SeadPatrol.Zone=Zone
SeadPatrol.PatrolFloorAltitude=PatrolFloorAltitude
SeadPatrol.PatrolCeilingAltitude=PatrolCeilingAltitude
SeadPatrol.EngageFloorAltitude=EngageFloorAltitude
SeadPatrol.EngageCeilingAltitude=EngageCeilingAltitude
SeadPatrol.PatrolMinSpeed=PatrolMinSpeed
SeadPatrol.PatrolMaxSpeed=PatrolMaxSpeed
SeadPatrol.EngageMinSpeed=EngageMinSpeed
SeadPatrol.EngageMaxSpeed=EngageMaxSpeed
SeadPatrol.PatrolAltType=PatrolAltType
SeadPatrol.EngageAltType=EngageAltType
SeadPatrol.Patrol=true
self:SetSquadronPatrolInterval(SquadronName,self.DefenderDefault.PatrolLimit,self.DefenderDefault.PatrolMinSeconds,self.DefenderDefault.PatrolMaxSeconds,1,"SEAD")
self:I({SEAD={Zone:GetName(),PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}})
end
function AI_A2G_DISPATCHER:SetSquadronSeadPatrol(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType)
self:SetSquadronSeadPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,FloorAltitude,CeilingAltitude,AltType,EngageMinSpeed,EngageMaxSpeed,FloorAltitude,CeilingAltitude,AltType)
end
function AI_A2G_DISPATCHER:SetSquadronCas2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.CAS=DefenderSquadron.CAS or{}
local Cas=DefenderSquadron.CAS
Cas.Name=SquadronName
Cas.EngageMinSpeed=EngageMinSpeed
Cas.EngageMaxSpeed=EngageMaxSpeed
Cas.EngageFloorAltitude=EngageFloorAltitude or 500
Cas.EngageCeilingAltitude=EngageCeilingAltitude or 1000
Cas.EngageAltType=EngageAltType
Cas.Defend=true
self:I({CAS={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}})
return self
end
function AI_A2G_DISPATCHER:SetSquadronCas(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude)
return self:SetSquadronCas2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,"RADIO")
end
function AI_A2G_DISPATCHER:SetSquadronCasEngageLimit(SquadronName,EngageLimit)
self:SetSquadronEngageLimit(SquadronName,EngageLimit,"CAS")
end
function AI_A2G_DISPATCHER:SetSquadronCasPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.CAS=DefenderSquadron.CAS or{}
local CasPatrol=DefenderSquadron.CAS
CasPatrol.Name=SquadronName
CasPatrol.Zone=Zone
CasPatrol.PatrolFloorAltitude=PatrolFloorAltitude
CasPatrol.PatrolCeilingAltitude=PatrolCeilingAltitude
CasPatrol.EngageFloorAltitude=EngageFloorAltitude
CasPatrol.EngageCeilingAltitude=EngageCeilingAltitude
CasPatrol.PatrolMinSpeed=PatrolMinSpeed
CasPatrol.PatrolMaxSpeed=PatrolMaxSpeed
CasPatrol.EngageMinSpeed=EngageMinSpeed
CasPatrol.EngageMaxSpeed=EngageMaxSpeed
CasPatrol.PatrolAltType=PatrolAltType
CasPatrol.EngageAltType=EngageAltType
CasPatrol.Patrol=true
self:SetSquadronPatrolInterval(SquadronName,self.DefenderDefault.PatrolLimit,self.DefenderDefault.PatrolMinSeconds,self.DefenderDefault.PatrolMaxSeconds,1,"CAS")
self:I({CAS={Zone:GetName(),PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}})
end
function AI_A2G_DISPATCHER:SetSquadronCasPatrol(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType)
self:SetSquadronCasPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,FloorAltitude,CeilingAltitude,AltType,EngageMinSpeed,EngageMaxSpeed,FloorAltitude,CeilingAltitude,AltType)
end
function AI_A2G_DISPATCHER:SetSquadronBai2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.BAI=DefenderSquadron.BAI or{}
local Bai=DefenderSquadron.BAI
Bai.Name=SquadronName
Bai.EngageMinSpeed=EngageMinSpeed
Bai.EngageMaxSpeed=EngageMaxSpeed
Bai.EngageFloorAltitude=EngageFloorAltitude or 500
Bai.EngageCeilingAltitude=EngageCeilingAltitude or 1000
Bai.EngageAltType=EngageAltType
Bai.Defend=true
self:I({BAI={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}})
return self
end
function AI_A2G_DISPATCHER:SetSquadronBai(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude)
return self:SetSquadronBai2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,"RADIO")
end
function AI_A2G_DISPATCHER:SetSquadronBaiEngageLimit(SquadronName,EngageLimit)
self:SetSquadronEngageLimit(SquadronName,EngageLimit,"BAI")
end
function AI_A2G_DISPATCHER:SetSquadronBaiPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.BAI=DefenderSquadron.BAI or{}
local BaiPatrol=DefenderSquadron.BAI
BaiPatrol.Name=SquadronName
BaiPatrol.Zone=Zone
BaiPatrol.PatrolFloorAltitude=PatrolFloorAltitude
BaiPatrol.PatrolCeilingAltitude=PatrolCeilingAltitude
BaiPatrol.EngageFloorAltitude=EngageFloorAltitude
BaiPatrol.EngageCeilingAltitude=EngageCeilingAltitude
BaiPatrol.PatrolMinSpeed=PatrolMinSpeed
BaiPatrol.PatrolMaxSpeed=PatrolMaxSpeed
BaiPatrol.EngageMinSpeed=EngageMinSpeed
BaiPatrol.EngageMaxSpeed=EngageMaxSpeed
BaiPatrol.PatrolAltType=PatrolAltType
BaiPatrol.EngageAltType=EngageAltType
BaiPatrol.Patrol=true
self:SetSquadronPatrolInterval(SquadronName,self.DefenderDefault.PatrolLimit,self.DefenderDefault.PatrolMinSeconds,self.DefenderDefault.PatrolMaxSeconds,1,"BAI")
self:I({BAI={Zone:GetName(),PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}})
end
function AI_A2G_DISPATCHER:SetSquadronBaiPatrol(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType)
self:SetSquadronBaiPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,FloorAltitude,CeilingAltitude,AltType,EngageMinSpeed,EngageMaxSpeed,FloorAltitude,CeilingAltitude,AltType)
end
function AI_A2G_DISPATCHER:SetDefaultOverhead(Overhead)
self.DefenderDefault.Overhead=Overhead
return self
end
function AI_A2G_DISPATCHER:SetSquadronOverhead(SquadronName,Overhead)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.Overhead=Overhead
return self
end
function AI_A2G_DISPATCHER:GetSquadronOverhead(SquadronName)
local DefenderSquadron=self:GetSquadron(SquadronName)
return DefenderSquadron.Overhead or self.DefenderDefault.Overhead
end
function AI_A2G_DISPATCHER:SetDefaultGrouping(Grouping)
self.DefenderDefault.Grouping=Grouping
return self
end
function AI_A2G_DISPATCHER:SetSquadronGrouping(SquadronName,Grouping)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.Grouping=Grouping
return self
end
function AI_A2G_DISPATCHER:SetSquadronEngageProbability(SquadronName,EngageProbability)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.EngageProbability=EngageProbability
return self
end
function AI_A2G_DISPATCHER:SetDefaultTakeoff(Takeoff)
self.DefenderDefault.Takeoff=Takeoff
return self
end
function AI_A2G_DISPATCHER:SetSquadronTakeoff(SquadronName,Takeoff)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.Takeoff=Takeoff
return self
end
function AI_A2G_DISPATCHER:GetDefaultTakeoff()
return self.DefenderDefault.Takeoff
end
function AI_A2G_DISPATCHER:GetSquadronTakeoff(SquadronName)
local DefenderSquadron=self:GetSquadron(SquadronName)
return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff
end
function AI_A2G_DISPATCHER:SetDefaultTakeoffInAir()
self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Air)
return self
end
function AI_A2G_DISPATCHER:SetSquadronTakeoffInAir(SquadronName,TakeoffAltitude)
self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Air)
if TakeoffAltitude then
self:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude)
end
return self
end
function AI_A2G_DISPATCHER:SetDefaultTakeoffFromRunway()
self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Runway)
return self
end
function AI_A2G_DISPATCHER:SetSquadronTakeoffFromRunway(SquadronName)
self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Runway)
return self
end
function AI_A2G_DISPATCHER:SetDefaultTakeoffFromParkingHot()
self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Hot)
return self
end
function AI_A2G_DISPATCHER:SetSquadronTakeoffFromParkingHot(SquadronName)
self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Hot)
return self
end
function AI_A2G_DISPATCHER:SetDefaultTakeoffFromParkingCold()
self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Cold)
return self
end
function AI_A2G_DISPATCHER:SetSquadronTakeoffFromParkingCold(SquadronName)
self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Cold)
return self
end
function AI_A2G_DISPATCHER:SetDefaultTakeoffInAirAltitude(TakeoffAltitude)
self.DefenderDefault.TakeoffAltitude=TakeoffAltitude
return self
end
function AI_A2G_DISPATCHER:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.TakeoffAltitude=TakeoffAltitude
return self
end
function AI_A2G_DISPATCHER:SetDefaultLanding(Landing)
self.DefenderDefault.Landing=Landing
return self
end
function AI_A2G_DISPATCHER:SetSquadronLanding(SquadronName,Landing)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.Landing=Landing
return self
end
function AI_A2G_DISPATCHER:GetDefaultLanding()
return self.DefenderDefault.Landing
end
function AI_A2G_DISPATCHER:GetSquadronLanding(SquadronName)
local DefenderSquadron=self:GetSquadron(SquadronName)
return DefenderSquadron.Landing or self.DefenderDefault.Landing
end
function AI_A2G_DISPATCHER:SetDefaultLandingNearAirbase()
self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.NearAirbase)
return self
end
function AI_A2G_DISPATCHER:SetSquadronLandingNearAirbase(SquadronName)
self:SetSquadronLanding(SquadronName,AI_A2G_DISPATCHER.Landing.NearAirbase)
return self
end
function AI_A2G_DISPATCHER:SetDefaultLandingAtRunway()
self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.AtRunway)
return self
end
function AI_A2G_DISPATCHER:SetSquadronLandingAtRunway(SquadronName)
self:SetSquadronLanding(SquadronName,AI_A2G_DISPATCHER.Landing.AtRunway)
return self
end
function AI_A2G_DISPATCHER:SetDefaultLandingAtEngineShutdown()
self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.AtEngineShutdown)
return self
end
function AI_A2G_DISPATCHER:SetSquadronLandingAtEngineShutdown(SquadronName)
self:SetSquadronLanding(SquadronName,AI_A2G_DISPATCHER.Landing.AtEngineShutdown)
return self
end
function AI_A2G_DISPATCHER:SetDefaultFuelThreshold(FuelThreshold)
self.DefenderDefault.FuelThreshold=FuelThreshold
return self
end
function AI_A2G_DISPATCHER:SetSquadronFuelThreshold(SquadronName,FuelThreshold)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.FuelThreshold=FuelThreshold
return self
end
function AI_A2G_DISPATCHER:SetDefaultTanker(TankerName)
self.DefenderDefault.TankerName=TankerName
return self
end
function AI_A2G_DISPATCHER:SetSquadronTanker(SquadronName,TankerName)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.TankerName=TankerName
return self
end
function AI_A2G_DISPATCHER:SetSquadronRadioFrequency(SquadronName,RadioFrequency,RadioModulation,RadioPower)
local DefenderSquadron=self:GetSquadron(SquadronName)
DefenderSquadron.RadioFrequency=RadioFrequency
DefenderSquadron.RadioModulation=RadioModulation or radio.modulation.AM
DefenderSquadron.RadioPower=RadioPower or 100
if DefenderSquadron.RadioQueue then
DefenderSquadron.RadioQueue:Stop()
end
DefenderSquadron.RadioQueue=nil
DefenderSquadron.RadioQueue=RADIOSPEECH:New(DefenderSquadron.RadioFrequency,DefenderSquadron.RadioModulation)
DefenderSquadron.RadioQueue.power=DefenderSquadron.RadioPower
DefenderSquadron.RadioQueue:Start(0.5)
DefenderSquadron.RadioQueue:SetLanguage(DefenderSquadron.Language)
end
function AI_A2G_DISPATCHER:AddDefenderToSquadron(Squadron,Defender,Size)
self.Defenders=self.Defenders or{}
local DefenderName=Defender:GetName()
self.Defenders[DefenderName]=Squadron
if Squadron.ResourceCount then
Squadron.ResourceCount=Squadron.ResourceCount-Size
end
self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount})
end
function AI_A2G_DISPATCHER:RemoveDefenderFromSquadron(Squadron,Defender)
self.Defenders=self.Defenders or{}
local DefenderName=Defender:GetName()
if Squadron.ResourceCount then
Squadron.ResourceCount=Squadron.ResourceCount+Defender:GetSize()
end
self.Defenders[DefenderName]=nil
self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount})
end
function AI_A2G_DISPATCHER:GetSquadronFromDefender(Defender)
self.Defenders=self.Defenders or{}
local DefenderName=Defender:GetName()
self:F({DefenderName=DefenderName})
return self.Defenders[DefenderName]
end
function AI_A2G_DISPATCHER:CountPatrolAirborne(SquadronName,DefenseTaskType)
local PatrolCount=0
local DefenderSquadron=self.DefenderSquadrons[SquadronName]
if DefenderSquadron then
for AIGroup,DefenderTask in pairs(self:GetDefenderTasks())do
if DefenderTask.SquadronName==SquadronName then
if DefenderTask.Type==DefenseTaskType then
if AIGroup:IsAlive()then
if DefenderTask.Fsm:Is("Patrolling")or DefenderTask.Fsm:Is("Engaging")or DefenderTask.Fsm:Is("Refuelling")
or DefenderTask.Fsm:Is("Started")then
PatrolCount=PatrolCount+1
end
end
end
end
end
end
return PatrolCount
end
function AI_A2G_DISPATCHER:CountDefendersEngaged(AttackerDetection,AttackerCount)
local DefendersEngaged=0
local DefendersTotal=0
local AttackerSet=AttackerDetection.Set
local DefendersMissing=AttackerCount
local DefenderTasks=self:GetDefenderTasks()
for DefenderGroup,DefenderTask in pairs(DefenderTasks)do
local Defender=DefenderGroup
local DefenderTaskTarget=DefenderTask.Target
local DefenderSquadronName=DefenderTask.SquadronName
local DefenderSize=DefenderTask.Size
if DefenderTask.Target then
self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize)
DefendersTotal=DefendersTotal+DefenderSize
if DefenderTaskTarget and DefenderTaskTarget.Index==AttackerDetection.Index then
local SquadronOverhead=self:GetSquadronOverhead(DefenderSquadronName)
self:F({SquadronOverhead=SquadronOverhead})
if DefenderSize then
DefendersEngaged=DefendersEngaged+DefenderSize
DefendersMissing=DefendersMissing-DefenderSize/SquadronOverhead
self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize)
else
DefendersEngaged=0
end
end
end
end
for QueueID,QueueItem in pairs(self.DefenseQueue)do
local QueueItem=QueueItem
if QueueItem.AttackerDetection and QueueItem.AttackerDetection.ItemID==AttackerDetection.ItemID then
DefendersMissing=DefendersMissing-QueueItem.DefendersNeeded/QueueItem.DefenderSquadron.Overhead
self:F({QueueItemName=QueueItem.Defense,QueueItem_ItemID=QueueItem.AttackerDetection.ItemID,DetectedItem=AttackerDetection.ItemID,DefendersMissing=DefendersMissing})
end
end
self:F({DefenderCount=DefendersEngaged})
return DefendersTotal,DefendersEngaged,DefendersMissing
end
function AI_A2G_DISPATCHER:CountDefenders(AttackerDetection,DefenderCount,DefenderTaskType)
local Friendlies=nil
local AttackerSet=AttackerDetection.Set
local AttackerCount=AttackerSet:Count()
local DefenderFriendlies=self:GetDefenderFriendliesNearBy(AttackerDetection)
for FriendlyDistance,DefenderFriendlyUnit in UTILS.spairs(DefenderFriendlies or{})do
if AttackerCount>DefenderCount then
local FriendlyGroup=DefenderFriendlyUnit:GetGroup()
if FriendlyGroup and FriendlyGroup:IsAlive()then
local DefenderTask=self:GetDefenderTask(FriendlyGroup)
if DefenderTask then
if DefenderTaskType==DefenderTask.Type then
if DefenderTask.Target==nil then
if DefenderTask.Fsm:Is("Returning")
or DefenderTask.Fsm:Is("Patrolling")then
Friendlies=Friendlies or{}
Friendlies[FriendlyGroup]=FriendlyGroup
DefenderCount=DefenderCount+FriendlyGroup:GetSize()
self:F({Friendly=FriendlyGroup:GetName(),FriendlyDistance=FriendlyDistance})
end
end
end
end
end
else
break
end
end
return Friendlies
end
function AI_A2G_DISPATCHER:ResourceActivate(DefenderSquadron,DefendersNeeded)
local SquadronName=DefenderSquadron.Name
DefendersNeeded=DefendersNeeded or 4
local DefenderGrouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping
DefenderGrouping=(DefenderGrouping<DefendersNeeded)and DefenderGrouping or DefendersNeeded
if self:IsSquadronVisible(SquadronName)then
local TemplateID=math.random(1,#DefenderSquadron.Spawn)
self:F({DefenderGrouping=DefenderGrouping})
local DefenderUnitIndex=1
local DefenderPatrolTemplate=nil
local DefenderName=nil
for GroupName,DefenderGroup in pairs(DefenderSquadron.Resources[TemplateID]or{})do
self:F({GroupName=GroupName})
local DefenderTemplate=_DATABASE:GetGroupTemplate(GroupName)
if DefenderUnitIndex==1 then
DefenderPatrolTemplate=UTILS.DeepCopy(DefenderTemplate)
self.DefenderPatrolIndex=self.DefenderPatrolIndex+1
DefenderPatrolTemplate.name=GroupName
DefenderName=DefenderPatrolTemplate.name
else
local DefenderUnitTemplate=DefenderTemplate.units[1]
DefenderPatrolTemplate.units[DefenderUnitIndex]=DefenderUnitTemplate
end
DefenderPatrolTemplate.units[DefenderUnitIndex].name=string.format(DefenderPatrolTemplate.name..'-%02d',DefenderUnitIndex)
DefenderPatrolTemplate.units[DefenderUnitIndex].unitId=nil
DefenderUnitIndex=DefenderUnitIndex+1
DefenderSquadron.Resources[TemplateID][GroupName]=nil
if DefenderUnitIndex>DefenderGrouping then
break
end
end
if DefenderPatrolTemplate then
local TakeoffMethod=self:GetSquadronTakeoff(SquadronName)
local SpawnGroup=GROUP:Register(DefenderName)
DefenderPatrolTemplate.lateActivation=nil
DefenderPatrolTemplate.uncontrolled=nil
local Takeoff=self:GetSquadronTakeoff(SquadronName)
DefenderPatrolTemplate.route.points[1].type=GROUPTEMPLATE.Takeoff[Takeoff][1]
DefenderPatrolTemplate.route.points[1].action=GROUPTEMPLATE.Takeoff[Takeoff][2]
local Defender=_DATABASE:Spawn(DefenderPatrolTemplate)
self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping)
Defender:Activate()
return Defender,DefenderGrouping
end
else
local Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)]
if DefenderGrouping then
Spawn:InitGrouping(DefenderGrouping)
else
Spawn:InitGrouping()
end
local TakeoffMethod=self:GetSquadronTakeoff(SquadronName)
local Defender=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,TakeoffMethod,DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude)
self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping)
return Defender,DefenderGrouping
end
return nil,nil
end
function AI_A2G_DISPATCHER:onafterPatrol(From,Event,To,SquadronName,DefenseTaskType)
local DefenderSquadron,Patrol=self:CanPatrol(SquadronName,DefenseTaskType)
if DefenderSquadron then
local DefendersNeeded
local DefendersGrouping=(DefenderSquadron.Grouping or self.DefenderDefault.Grouping)
if DefenderSquadron.ResourceCount==nil then
DefendersNeeded=DefendersGrouping
else
if DefenderSquadron.ResourceCount>=DefendersGrouping then
DefendersNeeded=DefendersGrouping
else
DefendersNeeded=DefenderSquadron.ResourceCount
end
end
if Patrol then
self:ResourceQueue(true,DefenderSquadron,DefendersNeeded,Patrol,DefenseTaskType,nil,SquadronName)
end
end
end
function AI_A2G_DISPATCHER:ResourceQueue(Patrol,DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,AttackerDetection,SquadronName)
self:F({DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,AttackerDetection,SquadronName})
local DefenseQueueItem={}
DefenseQueueItem.Patrol=Patrol
DefenseQueueItem.DefenderSquadron=DefenderSquadron
DefenseQueueItem.DefendersNeeded=DefendersNeeded
DefenseQueueItem.Defense=Defense
DefenseQueueItem.DefenseTaskType=DefenseTaskType
DefenseQueueItem.AttackerDetection=AttackerDetection
DefenseQueueItem.SquadronName=SquadronName
table.insert(self.DefenseQueue,DefenseQueueItem)
self:F({QueueItems=#self.DefenseQueue})
end
function AI_A2G_DISPATCHER:ResourceTakeoff()
for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do
self:F({DefenseQueueID})
end
for SquadronName,Squadron in pairs(self.DefenderSquadrons)do
if#self.DefenseQueue>0 then
self:F({SquadronName,Squadron.Name,Squadron.TakeoffTime,Squadron.TakeoffInterval,timer.getTime()})
local DefenseQueueItem=self.DefenseQueue[1]
self:F({DefenderSquadron=DefenseQueueItem.DefenderSquadron})
if DefenseQueueItem.SquadronName==SquadronName then
if Squadron.TakeoffTime+Squadron.TakeoffInterval<timer.getTime()then
Squadron.TakeoffTime=timer.getTime()
if DefenseQueueItem.Patrol==true then
self:ResourcePatrol(DefenseQueueItem.DefenderSquadron,DefenseQueueItem.DefendersNeeded,DefenseQueueItem.Defense,DefenseQueueItem.DefenseTaskType,DefenseQueueItem.AttackerDetection,DefenseQueueItem.SquadronName)
else
self:ResourceEngage(DefenseQueueItem.DefenderSquadron,DefenseQueueItem.DefendersNeeded,DefenseQueueItem.Defense,DefenseQueueItem.DefenseTaskType,DefenseQueueItem.AttackerDetection,DefenseQueueItem.SquadronName)
end
table.remove(self.DefenseQueue,1)
end
end
end
end
end
function AI_A2G_DISPATCHER:ResourcePatrol(DefenderSquadron,DefendersNeeded,Patrol,DefenseTaskType,AttackerDetection,SquadronName)
self:F({DefenderSquadron=DefenderSquadron})
self:F({DefendersNeeded=DefendersNeeded})
self:F({Patrol=Patrol})
self:F({DefenseTaskType=DefenseTaskType})
self:F({AttackerDetection=AttackerDetection})
self:F({SquadronName=SquadronName})
local DefenderGroup,DefenderGrouping=self:ResourceActivate(DefenderSquadron,DefendersNeeded)
if DefenderGroup then
local AI_A2G_PATROL={SEAD=AI_A2G_SEAD,BAI=AI_A2G_BAI,CAS=AI_A2G_CAS}
local AI_A2G_Fsm=AI_A2G_PATROL[DefenseTaskType]:New2(DefenderGroup,Patrol.EngageMinSpeed,Patrol.EngageMaxSpeed,Patrol.EngageFloorAltitude,Patrol.EngageCeilingAltitude,Patrol.EngageAltType,Patrol.Zone,Patrol.PatrolFloorAltitude,Patrol.PatrolCeilingAltitude,Patrol.PatrolMinSpeed,Patrol.PatrolMaxSpeed,Patrol.PatrolAltType)
AI_A2G_Fsm:SetDispatcher(self)
AI_A2G_Fsm:SetHomeAirbase(DefenderSquadron.Airbase)
AI_A2G_Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60)
AI_A2G_Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold)
AI_A2G_Fsm:SetDisengageRadius(self.DisengageRadius)
AI_A2G_Fsm:SetTanker(DefenderSquadron.TankerName or self.DefenderDefault.TankerName)
AI_A2G_Fsm:Start()
self:SetDefenderTask(SquadronName,DefenderGroup,DefenseTaskType,AI_A2G_Fsm,nil,DefenderGrouping)
function AI_A2G_Fsm:onafterTakeoff(DefenderGroup,From,Event,To)
self:F({"Takeoff",DefenderGroup:GetName()})
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=AI_A2G_Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if Squadron then
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", wheels up.",DefenderGroup)
end
AI_A2G_Fsm:Patrol()
end
end
function AI_A2G_Fsm:onafterPatrolRoute(DefenderGroup,From,Event,To)
self:F({"PatrolRoute",DefenderGroup:GetName()})
self:GetParent(self).onafterPatrolRoute(self,DefenderGroup,From,Event,To)
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=self:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if Squadron and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", patrolling.",DefenderGroup)
end
Dispatcher:ClearDefenderTaskTarget(DefenderGroup)
end
function AI_A2G_Fsm:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit)
self:F({"Engage Route",DefenderGroup:GetName()})
self:GetParent(self).onafterEngageRoute(self,DefenderGroup,From,Event,To,AttackSetUnit)
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=AI_A2G_Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if Squadron and AttackSetUnit:Count()>0 then
local FirstUnit=AttackSetUnit:GetFirst()
local Coordinate=FirstUnit:GetCoordinate()
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", moving on to ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup)
end
end
end
function AI_A2G_Fsm:OnAfterEngage(DefenderGroup,From,Event,To,AttackSetUnit)
self:F({"Engage Route",DefenderGroup:GetName()})
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=AI_A2G_Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
local FirstUnit=AttackSetUnit:GetFirst()
if FirstUnit then
local Coordinate=FirstUnit:GetCoordinate()
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", engaging ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup)
end
end
end
function AI_A2G_Fsm:onafterRTB(DefenderGroup,From,Event,To)
self:F({"RTB",DefenderGroup:GetName()})
self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To)
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=self:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", returning to base.",DefenderGroup)
end
Dispatcher:ClearDefenderTaskTarget(DefenderGroup)
end
function AI_A2G_Fsm:onafterLostControl(DefenderGroup,From,Event,To)
self:F({"LostControl",DefenderGroup:GetName()})
self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To)
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=AI_A2G_Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", lost control.")
end
if DefenderGroup:IsAboveRunway()then
Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup)
DefenderGroup:Destroy()
end
end
function AI_A2G_Fsm:onafterHome(DefenderGroup,From,Event,To,Action)
self:F({"Home",DefenderGroup:GetName()})
self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To)
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=self:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", landing at base.",DefenderGroup)
end
if Action and Action=="Destroy"then
Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup)
DefenderGroup:Destroy()
end
if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2G_DISPATCHER.Landing.NearAirbase then
Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup)
DefenderGroup:Destroy()
Dispatcher:ResourcePark(Squadron,DefenderGroup)
end
end
end
end
function AI_A2G_DISPATCHER:ResourceEngage(DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,AttackerDetection,SquadronName)
self:F({DefenderSquadron=DefenderSquadron})
self:F({DefendersNeeded=DefendersNeeded})
self:F({Defense=Defense})
self:F({DefenseTaskType=DefenseTaskType})
self:F({AttackerDetection=AttackerDetection})
self:F({SquadronName=SquadronName})
local DefenderGroup,DefenderGrouping=self:ResourceActivate(DefenderSquadron,DefendersNeeded)
if DefenderGroup then
local AI_A2G_ENGAGE={SEAD=AI_A2G_SEAD,BAI=AI_A2G_BAI,CAS=AI_A2G_CAS}
local AI_A2G_Fsm=AI_A2G_ENGAGE[DefenseTaskType]:New(DefenderGroup,Defense.EngageMinSpeed,Defense.EngageMaxSpeed,Defense.EngageFloorAltitude,Defense.EngageCeilingAltitude,Defense.EngageAltType)
AI_A2G_Fsm:SetDispatcher(self)
AI_A2G_Fsm:SetHomeAirbase(DefenderSquadron.Airbase)
AI_A2G_Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60)
AI_A2G_Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold)
AI_A2G_Fsm:SetDisengageRadius(self.DisengageRadius)
AI_A2G_Fsm:Start()
self:SetDefenderTask(SquadronName,DefenderGroup,DefenseTaskType,AI_A2G_Fsm,AttackerDetection,DefenderGrouping)
function AI_A2G_Fsm:onafterTakeoff(DefenderGroup,From,Event,To)
self:F({"Defender Birth",DefenderGroup:GetName()})
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=AI_A2G_Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
local DefenderTarget=Dispatcher:GetDefenderTaskTarget(DefenderGroup)
self:F({DefenderTarget=DefenderTarget})
if DefenderTarget then
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", wheels up.",DefenderGroup)
end
AI_A2G_Fsm:EngageRoute(DefenderTarget.Set)
end
end
function AI_A2G_Fsm:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit)
self:F({"Engage Route",DefenderGroup:GetName()})
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=AI_A2G_Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if Squadron then
local FirstUnit=AttackSetUnit:GetFirst()
local Coordinate=FirstUnit:GetCoordinate()
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", on route to ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup)
end
end
self:GetParent(self).onafterEngageRoute(self,DefenderGroup,From,Event,To,AttackSetUnit)
end
function AI_A2G_Fsm:OnAfterEngage(DefenderGroup,From,Event,To,AttackSetUnit)
self:F({"Engage Route",DefenderGroup:GetName()})
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=AI_A2G_Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
local FirstUnit=AttackSetUnit:GetFirst()
if FirstUnit then
local Coordinate=FirstUnit:GetCoordinate()
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", engaging ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup)
end
end
end
function AI_A2G_Fsm:onafterRTB(DefenderGroup,From,Event,To)
self:F({"Defender RTB",DefenderGroup:GetName()})
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=self:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", returning to base.",DefenderGroup)
end
self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To)
Dispatcher:ClearDefenderTaskTarget(DefenderGroup)
end
function AI_A2G_Fsm:onafterLostControl(DefenderGroup,From,Event,To)
self:F({"Defender LostControl",DefenderGroup:GetName()})
self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To)
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=AI_A2G_Fsm:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,"Squadron "..Squadron.Name..", "..DefenderName.." lost control.")
end
if DefenderGroup:IsAboveRunway()then
Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup)
DefenderGroup:Destroy()
end
end
function AI_A2G_Fsm:onafterHome(DefenderGroup,From,Event,To,Action)
self:F({"Defender Home",DefenderGroup:GetName()})
self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To)
local DefenderName=DefenderGroup:GetCallsign()
local Dispatcher=self:GetDispatcher()
local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup)
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers(Squadron,DefenderName..", landing at base.",DefenderGroup)
end
if Action and Action=="Destroy"then
Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup)
DefenderGroup:Destroy()
end
if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2G_DISPATCHER.Landing.NearAirbase then
Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup)
DefenderGroup:Destroy()
Dispatcher:ResourcePark(Squadron,DefenderGroup)
end
end
end
end
function AI_A2G_DISPATCHER:onafterEngage(From,Event,To,AttackerDetection,Defenders)
if Defenders then
for DefenderID,Defender in pairs(Defenders or{})do
local Fsm=self:GetDefenderTaskFsm(Defender)
Fsm:Engage(AttackerDetection.Set)
self:SetDefenderTaskTarget(Defender,AttackerDetection)
end
end
end
function AI_A2G_DISPATCHER:HasDefenseLine(DefenseCoordinate,DetectedItem)
local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem)
local EvaluateDistance=AttackCoordinate:Get2DDistance(DefenseCoordinate)
local c1=DefenseCoordinate
local c2=AttackCoordinate
local a=c1.z-c2.z
local b=c2.x-c1.x
local c=c1.x*c2.z-c2.x*c1.z
local ok=true
for AttackItemID,CheckAttackItem in pairs(self.Detection:GetDetectedItems())do
if AttackItemID~=DetectedItem.ID then
local CheckAttackCoordinate=self.Detection:GetDetectedItemCoordinate(CheckAttackItem)
local x=CheckAttackCoordinate.x
local y=CheckAttackCoordinate.z
local r=5000
local IntersectDistance=(math.abs(a*x+b*y+c))/math.sqrt(a*a+b*b)
self:F({IntersectDistance=IntersectDistance,x=x,y=y})
local IntersectAttackDistance=CheckAttackCoordinate:Get2DDistance(DefenseCoordinate)
self:F({IntersectAttackDistance=IntersectAttackDistance,EvaluateDistance=EvaluateDistance})
if IntersectDistance<r and IntersectAttackDistance<EvaluateDistance then
ok=false
break
end
end
end
return ok
end
function AI_A2G_DISPATCHER:onafterDefend(From,Event,To,DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,DefenderFriendlies,DefenseTaskType)
self:F({From,Event,To,DetectedItem.Index,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing,DefenderFriendlies=DefenderFriendlies})
DetectedItem.Type=DefenseTaskType
local AttackerSet=DetectedItem.Set
local AttackerUnit=AttackerSet:GetFirst()
if AttackerUnit and AttackerUnit:IsAlive()then
local AttackerCount=AttackerSet:Count()
local DefenderCount=0
for DefenderID,DefenderGroup in pairs(DefenderFriendlies or{})do
local DefenseCoordinate=DefenderGroup:GetCoordinate()
local HasDefenseLine=self:HasDefenseLine(DefenseCoordinate,DetectedItem)
if HasDefenseLine==true then
local SquadronName=self:GetDefenderTask(DefenderGroup).SquadronName
local SquadronOverhead=self:GetSquadronOverhead(SquadronName)
local Fsm=self:GetDefenderTaskFsm(DefenderGroup)
Fsm:EngageRoute(AttackerSet)
self:SetDefenderTaskTarget(DefenderGroup,DetectedItem)
local DefenderGroupSize=DefenderGroup:GetSize()
DefendersMissing=DefendersMissing-DefenderGroupSize/SquadronOverhead
DefendersTotal=DefendersTotal+DefenderGroupSize/SquadronOverhead
end
if DefendersMissing<=0 then
break
end
end
self:F({DefenderCount=DefenderCount,DefendersMissing=DefendersMissing})
DefenderCount=DefendersMissing
local ClosestDistance=0
local EngageSquadronName=nil
local BreakLoop=false
while(DefenderCount>0 and not BreakLoop)do
self:F({DefenderSquadrons=self.DefenderSquadrons})
for SquadronName,DefenderSquadron in UTILS.rpairs(self.DefenderSquadrons or{})do
if DefenderSquadron[DefenseTaskType]then
local AirbaseCoordinate=DefenderSquadron.Airbase:GetCoordinate()
local AttackerCoord=AttackerUnit:GetCoordinate()
local InterceptCoord=DetectedItem.InterceptCoord
self:F({InterceptCoord=InterceptCoord})
if InterceptCoord then
local InterceptDistance=AirbaseCoordinate:Get2DDistance(InterceptCoord)
local AirbaseDistance=AirbaseCoordinate:Get2DDistance(AttackerCoord)
self:F({InterceptDistance=InterceptDistance,AirbaseDistance=AirbaseDistance,InterceptCoord=InterceptCoord})
if AirbaseDistance<=self.DefenseRadius then
local HasDefenseLine=self:HasDefenseLine(AirbaseCoordinate,DetectedItem)
if HasDefenseLine==true then
local EngageProbability=(DefenderSquadron.EngageProbability or 1)
local Probability=math.random()
if Probability<EngageProbability then
EngageSquadronName=SquadronName
break
end
end
end
end
end
end
if EngageSquadronName then
local DefenderSquadron,Defense=self:CanDefend(EngageSquadronName,DefenseTaskType)
if Defense then
local DefenderOverhead=DefenderSquadron.Overhead or self.DefenderDefault.Overhead
local DefenderGrouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping
local DefendersNeeded=math.ceil(DefenderCount*DefenderOverhead)
self:F({Overhead=DefenderOverhead,SquadronOverhead=DefenderSquadron.Overhead,DefaultOverhead=self.DefenderDefault.Overhead})
self:F({Grouping=DefenderGrouping,SquadronGrouping=DefenderSquadron.Grouping,DefaultGrouping=self.DefenderDefault.Grouping})
self:F({DefendersCount=DefenderCount,DefendersNeeded=DefendersNeeded})
local DefendersLimit=DefenderSquadron.EngageLimit or self.DefenderDefault.EngageLimit
if DefendersLimit then
if DefendersTotal>=DefendersLimit then
DefendersNeeded=0
BreakLoop=true
else
if DefendersTotal+DefendersNeeded>DefendersLimit then
DefendersNeeded=DefendersLimit-DefendersTotal
end
end
end
if DefenderSquadron.ResourceCount and DefendersNeeded>DefenderSquadron.ResourceCount then
DefendersNeeded=DefenderSquadron.ResourceCount
BreakLoop=true
end
while(DefendersNeeded>0)do
self:ResourceQueue(false,DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,DetectedItem,EngageSquadronName)
DefendersNeeded=DefendersNeeded-DefenderGrouping
DefenderCount=DefenderCount-DefenderGrouping/DefenderOverhead
end
else
BreakLoop=true
break
end
else
break
end
end
end
end
function AI_A2G_DISPATCHER:Evaluate_SEAD(DetectedItem)
self:F({DetectedItem.ItemID})
local AttackerSet=DetectedItem.Set
local AttackerCount=AttackerSet:HasSEAD()
if(AttackerCount>0)then
local DefendersTotal,DefendersEngaged,DefendersMissing=self:CountDefendersEngaged(DetectedItem,AttackerCount)
self:F({AttackerCount=AttackerCount,DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing})
local DefenderGroups=self:CountDefenders(DetectedItem,DefendersEngaged,"SEAD")
if DetectedItem.IsDetected==true then
return DefendersTotal,DefendersEngaged,DefendersMissing,DefenderGroups
end
end
return 0,0,0
end
function AI_A2G_DISPATCHER:Evaluate_CAS(DetectedItem)
self:F({DetectedItem.ItemID})
local AttackerSet=DetectedItem.Set
local AttackerCount=AttackerSet:Count()
local AttackerRadarCount=AttackerSet:HasSEAD()
local IsFriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT)
local IsCas=(AttackerRadarCount==0)and(IsFriendliesNearBy==true)
if IsCas==true then
local DefendersTotal,DefendersEngaged,DefendersMissing=self:CountDefendersEngaged(DetectedItem,AttackerCount)
self:F({AttackerCount=AttackerCount,DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing})
local DefenderGroups=self:CountDefenders(DetectedItem,DefendersEngaged,"CAS")
if DetectedItem.IsDetected==true then
return DefendersTotal,DefendersEngaged,DefendersMissing,DefenderGroups
end
end
return 0,0,0
end
function AI_A2G_DISPATCHER:Evaluate_BAI(DetectedItem)
self:F({DetectedItem.ItemID})
local AttackerSet=DetectedItem.Set
local AttackerCount=AttackerSet:Count()
local AttackerRadarCount=AttackerSet:HasSEAD()
local IsFriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT)
local IsBai=(AttackerRadarCount==0)and(IsFriendliesNearBy==false)
if IsBai==true then
local DefendersTotal,DefendersEngaged,DefendersMissing=self:CountDefendersEngaged(DetectedItem,AttackerCount)
self:F({AttackerCount=AttackerCount,DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing})
local DefenderGroups=self:CountDefenders(DetectedItem,DefendersEngaged,"BAI")
if DetectedItem.IsDetected==true then
return DefendersTotal,DefendersEngaged,DefendersMissing,DefenderGroups
end
end
return 0,0,0
end
function AI_A2G_DISPATCHER:Keys(DetectedItem)
self:F({DetectedItem=DetectedItem})
local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem)
local ShortestDistance=999999999
for DefenseCoordinateName,DefenseCoordinate in pairs(self.DefenseCoordinates)do
local DefenseCoordinate=DefenseCoordinate
local EvaluateDistance=AttackCoordinate:Get2DDistance(DefenseCoordinate)
if EvaluateDistance<=ShortestDistance then
ShortestDistance=EvaluateDistance
end
end
return ShortestDistance
end
function AI_A2G_DISPATCHER:Order(DetectedItem)
local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem)
local ShortestDistance=999999999
for DefenseCoordinateName,DefenseCoordinate in pairs(self.DefenseCoordinates)do
local DefenseCoordinate=DefenseCoordinate
local EvaluateDistance=AttackCoordinate:Get2DDistance(DefenseCoordinate)
if EvaluateDistance<=ShortestDistance then
ShortestDistance=EvaluateDistance
end
end
return ShortestDistance
end
function AI_A2G_DISPATCHER:ShowTacticalDisplay(Detection)
local AreaMsg={}
local TaskMsg={}
local ChangeMsg={}
local TaskReport=REPORT:New()
local DefenseTotal=0
local Report=REPORT:New("\nTactical Overview")
local DefenderGroupCount=0
local DefendersTotal=0
for DetectedItemID,DetectedItem in UTILS.spairs(Detection:GetDetectedItems(),function(t,a,b)return self:Order(t[a])<self:Order(t[b])end)do
if not self.Detection:IsDetectedItemLocked(DetectedItem)==true then
local DetectedItem=DetectedItem
local DetectedSet=DetectedItem.Set
local DetectedCount=DetectedSet:Count()
local DetectedZone=DetectedItem.Zone
self:F({"Target ID",DetectedItem.ItemID})
self:F({DefenseLimit=self.DefenseLimit,DefenseTotal=DefenseTotal})
DetectedSet:Flush(self)
local DetectedID=DetectedItem.ID
local DetectionIndex=DetectedItem.Index
local DetectedItemChanged=DetectedItem.Changed
local ThreatLevel=DetectedItem.Set:CalculateThreatLevelA2G()
Report:Add(string.format(" - %1s%s ( %04s ): ( #%02d - %-4s ) %s",(DetectedItem.IsDetected==true)and"!"or" ",DetectedItem.ItemID,DetectedItem.Index,DetectedItem.Set:Count(),DetectedItem.Type or" --- ",string.rep("■",ThreatLevel)))
for Defender,DefenderTask in pairs(self:GetDefenderTasks())do
local Defender=Defender
if DefenderTask.Target and DefenderTask.Target.Index==DetectedItem.Index then
if Defender:IsAlive()then
DefenderGroupCount=DefenderGroupCount+1
local Fuel=Defender:GetFuelMin()*100
local Damage=Defender:GetLife()/Defender:GetLife0()*100
Report:Add(string.format("   - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s",
Defender:GetName(),
DefenderTask.Type,
DefenderTask.Fsm:GetState(),
Defender:GetSize(),
Fuel,
Damage,
Defender:HasTask()==true and"Executing"or"Idle"))
end
end
end
end
end
Report:Add("\n - No Targets:")
local TaskCount=0
for Defender,DefenderTask in pairs(self:GetDefenderTasks())do
TaskCount=TaskCount+1
local Defender=Defender
if not DefenderTask.Target then
if Defender:IsAlive()then
local DefenderHasTask=Defender:HasTask()
local Fuel=Defender:GetFuelMin()*100
local Damage=Defender:GetLife()/Defender:GetLife0()*100
DefenderGroupCount=DefenderGroupCount+1
Report:Add(string.format("   - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s",
Defender:GetName(),
DefenderTask.Type,
DefenderTask.Fsm:GetState(),
Defender:GetSize(),
Fuel,
Damage,
Defender:HasTask()==true and"Executing"or"Idle"))
end
end
end
Report:Add(string.format("\n - %d Tasks - %d Defender Groups",TaskCount,DefenderGroupCount))
Report:Add(string.format("\n - %d Queued Aircraft Launches",#self.DefenseQueue))
for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do
local DefenseQueueItem=DefenseQueueItem
Report:Add(string.format("   - %s - %s",DefenseQueueItem.SquadronName,DefenseQueueItem.DefenderSquadron.TakeoffTime,DefenseQueueItem.DefenderSquadron.TakeoffInterval))
end
Report:Add(string.format("\n - Squadron Resources: ",#self.DefenseQueue))
for DefenderSquadronName,DefenderSquadron in pairs(self.DefenderSquadrons)do
Report:Add(string.format("   - %s - %s",DefenderSquadronName,DefenderSquadron.ResourceCount and tostring(DefenderSquadron.ResourceCount)or"n/a"))
end
self:F(Report:Text("\n"))
trigger.action.outText(Report:Text("\n"),25)
end
function AI_A2G_DISPATCHER:ProcessDetected(Detection)
local AreaMsg={}
local TaskMsg={}
local ChangeMsg={}
local TaskReport=REPORT:New()
local DefenseTotal=0
for DefenderGroup,DefenderTask in pairs(self:GetDefenderTasks())do
local DefenderGroup=DefenderGroup
local DefenderTaskFsm=self:GetDefenderTaskFsm(DefenderGroup)
if not DefenderGroup:IsAlive()then
self:F({Defender=DefenderGroup:GetName(),DefenderState=DefenderTaskFsm:GetState()})
if not DefenderTaskFsm:Is("Started")then
self:ClearDefenderTask(DefenderGroup)
end
else
if DefenderTask.Target then
self:F({TargetIndex=DefenderTask.Target.Index})
local AttackerItem=Detection:GetDetectedItemByIndex(DefenderTask.Target.Index)
if not AttackerItem then
self:F({"Removing obsolete Target:",DefenderTask.Target.Index})
self:ClearDefenderTaskTarget(DefenderGroup)
else
if DefenderTask.Target.Set then
local TargetCount=DefenderTask.Target.Set:Count()
if TargetCount==0 then
self:F({"All Targets destroyed in Target, removing:",DefenderTask.Target.Index})
self:ClearDefenderTask(DefenderGroup)
end
end
end
end
end
end
local Report=REPORT:New("\nTactical Overview")
local DefenderGroupCount=0
local DefendersTotal=0
for DetectedDistance,DetectedItem in UTILS.kpairs(Detection:GetDetectedItems(),function(t)return self:Keys(t)end,function(t,a,b)return self:Order(t[a])<self:Order(t[b])end)do
if not self.Detection:IsDetectedItemLocked(DetectedItem)==true then
local DetectedItem=DetectedItem
local DetectedSet=DetectedItem.Set
local DetectedCount=DetectedSet:Count()
local DetectedZone=DetectedItem.Zone
self:F({"Target ID",DetectedItem.ItemID})
self:F({DefenseLimit=self.DefenseLimit,DefenseTotal=DefenseTotal})
DetectedSet:Flush(self)
local DetectedID=DetectedItem.ID
local DetectionIndex=DetectedItem.Index
local DetectedItemChanged=DetectedItem.Changed
local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem)
local EngageDefenses=nil
self:F({DetectedDistance=DetectedDistance,DefenseRadius=self.DefenseRadius})
if DetectedDistance<=self.DefenseRadius then
self:F({DetectedApproach=self._DefenseApproach})
if self._DefenseApproach==AI_A2G_DISPATCHER.DefenseApproach.Distance then
EngageDefenses=true
self:F({EngageDefenses=EngageDefenses})
end
if self._DefenseApproach==AI_A2G_DISPATCHER.DefenseApproach.Random then
local DistanceProbability=(self.DefenseRadius/DetectedDistance*self.DefenseReactivity)
local DefenseProbability=math.random()
self:F({DistanceProbability=DistanceProbability,DefenseProbability=DefenseProbability})
if DefenseProbability<=DistanceProbability/(300/30)then
EngageDefenses=true
end
end
end
self:F({EngageDefenses=EngageDefenses,DefenseLimit=self.DefenseLimit,DefenseTotal=DefenseTotal})
if(EngageDefenses and(self.DefenseLimit and DefenseTotal<self.DefenseLimit)or not self.DefenseLimit)then
do
local DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies=self:Evaluate_SEAD(DetectedItem)
if DefendersMissing>0 then
self:F({DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing})
self:Defend(DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies,"SEAD")
end
end
do
local DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies=self:Evaluate_CAS(DetectedItem)
if DefendersMissing>0 then
self:F({DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing})
self:Defend(DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies,"CAS")
end
end
do
local DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies=self:Evaluate_BAI(DetectedItem)
if DefendersMissing>0 then
self:F({DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing})
self:Defend(DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies,"BAI")
end
end
end
for Defender,DefenderTask in pairs(self:GetDefenderTasks())do
local Defender=Defender
if DefenderTask.Target and DefenderTask.Target.Index==DetectedItem.Index then
DefenseTotal=DefenseTotal+1
end
end
for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do
local DefenseQueueItem=DefenseQueueItem
if DefenseQueueItem.AttackerDetection and DefenseQueueItem.AttackerDetection.Index and DefenseQueueItem.AttackerDetection.Index==DetectedItem.Index then
DefenseTotal=DefenseTotal+1
end
end
if self.TacticalDisplay then
local ThreatLevel=DetectedItem.Set:CalculateThreatLevelA2G()
Report:Add(string.format(" - %1s%s ( %4s ): ( #%d - %4s ) %s",(DetectedItem.IsDetected==true)and"!"or" ",DetectedItem.ItemID,DetectedItem.Index,DetectedItem.Set:Count(),DetectedItem.Type or" --- ",string.rep("■",ThreatLevel)))
for Defender,DefenderTask in pairs(self:GetDefenderTasks())do
local Defender=Defender
if DefenderTask.Target and DefenderTask.Target.Index==DetectedItem.Index then
if Defender:IsAlive()then
DefenderGroupCount=DefenderGroupCount+1
local Fuel=Defender:GetFuelMin()*100
local Damage=Defender:GetLife()/Defender:GetLife0()*100
Report:Add(string.format("   - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s",
Defender:GetName(),
DefenderTask.Type,
DefenderTask.Fsm:GetState(),
Defender:GetSize(),
Fuel,
Damage,
Defender:HasTask()==true and"Executing"or"Idle"))
end
end
end
end
end
end
if self.TacticalDisplay then
Report:Add("\n - No Targets:")
local TaskCount=0
for Defender,DefenderTask in pairs(self:GetDefenderTasks())do
TaskCount=TaskCount+1
local Defender=Defender
if not DefenderTask.Target then
if Defender:IsAlive()then
local DefenderHasTask=Defender:HasTask()
local Fuel=Defender:GetFuelMin()*100
local Damage=Defender:GetLife()/Defender:GetLife0()*100
DefenderGroupCount=DefenderGroupCount+1
Report:Add(string.format("   - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s",
Defender:GetName(),
DefenderTask.Type,
DefenderTask.Fsm:GetState(),
Defender:GetSize(),
Fuel,
Damage,
Defender:HasTask()==true and"Executing"or"Idle"))
end
end
end
Report:Add(string.format("\n - %d Tasks - %d Defender Groups",TaskCount,DefenderGroupCount))
Report:Add(string.format("\n - %d Queued Aircraft Launches",#self.DefenseQueue))
for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do
local DefenseQueueItem=DefenseQueueItem
Report:Add(string.format("   - %s - %s",DefenseQueueItem.SquadronName,DefenseQueueItem.DefenderSquadron.TakeoffTime,DefenseQueueItem.DefenderSquadron.TakeoffInterval))
end
Report:Add(string.format("\n - Squadron Resources: ",#self.DefenseQueue))
for DefenderSquadronName,DefenderSquadron in pairs(self.DefenderSquadrons)do
Report:Add(string.format("   - %s - %s",DefenderSquadronName,DefenderSquadron.ResourceCount and tostring(DefenderSquadron.ResourceCount)or"n/a"))
end
self:F(Report:Text("\n"))
trigger.action.outText(Report:Text("\n"),25)
end
return true
end
end
do
function AI_A2G_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem)
local DetectedSet=DetectedItem.Set
local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem)
local PlayerTypes={}
local PlayersCount=0
if PlayersNearBy then
local DetectedThreatLevel=DetectedSet:CalculateThreatLevelA2G()
for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do
local PlayerUnit=PlayerUnitData
local PlayerName=PlayerUnit:GetPlayerName()
if PlayerUnit:IsAirPlane()and PlayerName~=nil then
local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel()
PlayersCount=PlayersCount+1
local PlayerType=PlayerUnit:GetTypeName()
PlayerTypes[PlayerName]=PlayerType
if DetectedThreatLevel<FriendlyUnitThreatLevel+2 then
end
end
end
end
local PlayerTypesReport=REPORT:New()
if PlayersCount>0 then
for PlayerName,PlayerType in pairs(PlayerTypes)do
PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType))
end
else
PlayerTypesReport:Add("-")
end
return PlayersCount,PlayerTypesReport
end
function AI_A2G_DISPATCHER:GetFriendliesNearBy(DetectedItem)
local DetectedSet=DetectedItem.Set
local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem)
local FriendlyTypes={}
local FriendliesCount=0
if FriendlyUnitsNearBy then
local DetectedThreatLevel=DetectedSet:CalculateThreatLevelA2G()
for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do
local FriendlyUnit=FriendlyUnitData
if FriendlyUnit:IsAirPlane()then
local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel()
FriendliesCount=FriendliesCount+1
local FriendlyType=FriendlyUnit:GetTypeName()
FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1
if DetectedThreatLevel<FriendlyUnitThreatLevel+2 then
end
end
end
end
local FriendlyTypesReport=REPORT:New()
if FriendliesCount>0 then
for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do
FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType))
end
else
FriendlyTypesReport:Add("-")
end
return FriendliesCount,FriendlyTypesReport
end
function AI_A2G_DISPATCHER:SchedulerPatrol(SquadronName)
local PatrolTaskTypes={"SEAD","CAS","BAI"}
local PatrolTaskType=PatrolTaskTypes[math.random(1,3)]
self:Patrol(SquadronName,PatrolTaskType)
end
function AI_A2G_DISPATCHER:SetSendMessages(onoff)
self.SetSendPlayerMessages=onoff
end
end
function AI_A2G_DISPATCHER:AddToSquadron(Squadron,Amount)
local Squadron=self:GetSquadron(Squadron)
if Squadron.ResourceCount then
Squadron.ResourceCount=Squadron.ResourceCount+Amount
end
self:T({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount})
end
function AI_A2G_DISPATCHER:RemoveFromSquadron(Squadron,Amount)
local Squadron=self:GetSquadron(Squadron)
if Squadron.ResourceCount then
Squadron.ResourceCount=Squadron.ResourceCount-Amount
end
self:T({Squadron=Squadron.Name,SquadronResourceCount=Squadron.ResourceCount})
end
AI_PATROL_ZONE={
ClassName="AI_PATROL_ZONE",
}
function AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
self.PatrolZone=PatrolZone
self.PatrolFloorAltitude=PatrolFloorAltitude
self.PatrolCeilingAltitude=PatrolCeilingAltitude
self.PatrolMinSpeed=PatrolMinSpeed
self.PatrolMaxSpeed=PatrolMaxSpeed
self.PatrolAltType=PatrolAltType or"BARO"
self:SetRefreshTimeInterval(30)
self.CheckStatus=true
self:ManageFuel(.2,60)
self:ManageDamage(1)
self.DetectedUnits={}
self:SetStartState("None")
self:AddTransition("*","Stop","Stopped")
self:AddTransition("None","Start","Patrolling")
self:AddTransition("Patrolling","Route","Patrolling")
self:AddTransition("*","Status","*")
self:AddTransition("*","Detect","*")
self:AddTransition("*","Detected","*")
self:AddTransition("*","RTB","Returning")
self:AddTransition("*","Reset","Patrolling")
self:AddTransition("*","Eject","*")
self:AddTransition("*","Crash","Crashed")
self:AddTransition("*","PilotDead","*")
return self
end
function AI_PATROL_ZONE:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed)
self:F2({PatrolMinSpeed,PatrolMaxSpeed})
self.PatrolMinSpeed=PatrolMinSpeed
self.PatrolMaxSpeed=PatrolMaxSpeed
end
function AI_PATROL_ZONE:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude)
self:F2({PatrolFloorAltitude,PatrolCeilingAltitude})
self.PatrolFloorAltitude=PatrolFloorAltitude
self.PatrolCeilingAltitude=PatrolCeilingAltitude
end
function AI_PATROL_ZONE:SetDetectionOn()
self:F2()
self.DetectOn=true
end
function AI_PATROL_ZONE:SetDetectionOff()
self:F2()
self.DetectOn=false
end
function AI_PATROL_ZONE:SetStatusOff()
self:F2()
self.CheckStatus=false
end
function AI_PATROL_ZONE:SetDetectionActivated()
self:F2()
self:ClearDetectedUnits()
self.DetectActivated=true
self:__Detect(-self.DetectInterval)
end
function AI_PATROL_ZONE:SetDetectionDeactivated()
self:F2()
self:ClearDetectedUnits()
self.DetectActivated=false
end
function AI_PATROL_ZONE:SetRefreshTimeInterval(Seconds)
self:F2()
if Seconds then
self.DetectInterval=Seconds
else
self.DetectInterval=30
end
end
function AI_PATROL_ZONE:SetDetectionZone(DetectionZone)
self:F2()
if DetectionZone then
self.DetectZone=DetectionZone
else
self.DetectZone=nil
end
end
function AI_PATROL_ZONE:GetDetectedUnits()
self:F2()
return self.DetectedUnits
end
function AI_PATROL_ZONE:ClearDetectedUnits()
self:F2()
self.DetectedUnits={}
end
function AI_PATROL_ZONE:ManageFuel(PatrolFuelThresholdPercentage,PatrolOutOfFuelOrbitTime)
self.PatrolFuelThresholdPercentage=PatrolFuelThresholdPercentage
self.PatrolOutOfFuelOrbitTime=PatrolOutOfFuelOrbitTime
return self
end
function AI_PATROL_ZONE:ManageDamage(PatrolDamageThreshold)
self.PatrolManageDamage=true
self.PatrolDamageThreshold=PatrolDamageThreshold
return self
end
function AI_PATROL_ZONE:onafterStart(Controllable,From,Event,To)
self:F2()
self:__Route(1)
self:__Status(60)
self:SetDetectionActivated()
self:HandleEvent(EVENTS.PilotDead,self.OnPilotDead)
self:HandleEvent(EVENTS.Crash,self.OnCrash)
self:HandleEvent(EVENTS.Ejection,self.OnEjection)
Controllable:OptionROEHoldFire()
Controllable:OptionROTVertical()
self.Controllable:OnReSpawn(
function(PatrolGroup)
self:T("ReSpawn")
self:__Reset(1)
self:__Route(5)
end
)
self:SetDetectionOn()
end
function AI_PATROL_ZONE:onbeforeDetect(Controllable,From,Event,To)
return self.DetectOn and self.DetectActivated
end
function AI_PATROL_ZONE:onafterDetect(Controllable,From,Event,To)
local Detected=false
local DetectedTargets=Controllable:GetDetectedTargets()
for TargetID,Target in pairs(DetectedTargets or{})do
local TargetObject=Target.object
if TargetObject and TargetObject:isExist()and TargetObject.id_<50000000 then
local TargetUnit=UNIT:Find(TargetObject)
if TargetUnit and TargetUnit:IsAlive()then
local TargetUnitName=TargetUnit:GetName()
if self.DetectionZone then
if TargetUnit:IsInZone(self.DetectionZone)then
self:T({"Detected ",TargetUnit})
if self.DetectedUnits[TargetUnit]==nil then
self.DetectedUnits[TargetUnit]=true
end
Detected=true
end
else
if self.DetectedUnits[TargetUnit]==nil then
self.DetectedUnits[TargetUnit]=true
end
Detected=true
end
end
end
end
self:__Detect(-self.DetectInterval)
if Detected==true then
self:__Detected(1.5)
end
end
function AI_PATROL_ZONE:_NewPatrolRoute(AIControllable)
local PatrolZone=AIControllable:GetState(AIControllable,"PatrolZone")
PatrolZone:__Route(1)
end
function AI_PATROL_ZONE:onafterRoute(Controllable,From,Event,To)
self:F2()
if From=="RTB"then
return
end
local life=self.Controllable:GetLife()or 0
if self.Controllable:IsAlive()and life>1 then
local PatrolRoute={}
if self.Controllable:InAir()==false then
self:T("Not in the air, finding route path within PatrolZone")
local CurrentVec2=self.Controllable:GetVec2()
if not CurrentVec2 then return end
local CurrentAltitude=self.Controllable:GetAltitude()
local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
local ToPatrolZoneSpeed=self.PatrolMaxSpeed
local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TakeOffParking,
POINT_VEC3.RoutePointAction.FromParkingArea,
ToPatrolZoneSpeed,
true
)
PatrolRoute[#PatrolRoute+1]=CurrentRoutePoint
else
self:T("In the air, finding route path within PatrolZone")
local CurrentVec2=self.Controllable:GetVec2()
if not CurrentVec2 then return end
local CurrentAltitude=self.Controllable:GetAltitude()
local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
local ToPatrolZoneSpeed=self.PatrolMaxSpeed
local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToPatrolZoneSpeed,
true
)
PatrolRoute[#PatrolRoute+1]=CurrentRoutePoint
end
local ToTargetVec2=self.PatrolZone:GetRandomVec2()
self:T2(ToTargetVec2)
local ToTargetAltitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude)
local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
self:T2({self.PatrolMinSpeed,self.PatrolMaxSpeed,ToTargetSpeed})
local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y)
local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
true
)
PatrolRoute[#PatrolRoute+1]=ToTargetRoutePoint
self.Controllable:WayPointInitialize(PatrolRoute)
self.Controllable:SetState(self.Controllable,"PatrolZone",self)
self.Controllable:WayPointFunction(#PatrolRoute,1,"AI_PATROL_ZONE:_NewPatrolRoute")
self.Controllable:WayPointExecute(1,2)
end
end
function AI_PATROL_ZONE:onbeforeStatus()
return self.CheckStatus
end
function AI_PATROL_ZONE:onafterStatus()
self:F2()
if self.Controllable and self.Controllable:IsAlive()then
local RTB=false
local Fuel=self.Controllable:GetFuelMin()
if Fuel<self.PatrolFuelThresholdPercentage then
self:I(self.Controllable:GetName().." is out of fuel:"..Fuel..", RTB!")
local OldAIControllable=self.Controllable
local OrbitTask=OldAIControllable:TaskOrbitCircle(math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude),self.PatrolMinSpeed)
local TimedOrbitTask=OldAIControllable:TaskControlled(OrbitTask,OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil))
OldAIControllable:SetTask(TimedOrbitTask,10)
RTB=true
else
end
local Damage=self.Controllable:GetLife()
if Damage<=self.PatrolDamageThreshold then
self:I(self.Controllable:GetName().." is damaged:"..Damage..", RTB!")
RTB=true
end
if RTB==true then
self:RTB()
else
self:__Status(60)
end
end
end
function AI_PATROL_ZONE:onafterRTB()
self:F2()
if self.Controllable and self.Controllable:IsAlive()then
self:SetDetectionOff()
self.CheckStatus=false
local PatrolRoute={}
local CurrentVec2=self.Controllable:GetVec2()
if not CurrentVec2 then return end
local CurrentAltitude=self.Controllable:GetAltitude()
local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
local ToPatrolZoneSpeed=self.PatrolMaxSpeed
local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToPatrolZoneSpeed,
true
)
PatrolRoute[#PatrolRoute+1]=CurrentRoutePoint
self.Controllable:WayPointInitialize(PatrolRoute)
self.Controllable:WayPointExecute(1,1)
end
end
function AI_PATROL_ZONE:onafterDead()
self:SetDetectionOff()
self:SetStatusOff()
end
function AI_PATROL_ZONE:OnCrash(EventData)
if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then
if#self.Controllable:GetUnits()==1 then
self:__Crash(1,EventData)
end
end
end
function AI_PATROL_ZONE:OnEjection(EventData)
if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then
self:__Eject(1,EventData)
end
end
function AI_PATROL_ZONE:OnPilotDead(EventData)
if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then
self:__PilotDead(1,EventData)
end
end
AI_CAP_ZONE={
ClassName="AI_CAP_ZONE",
}
function AI_CAP_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
local self=BASE:Inherit(self,AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType))
self.Accomplished=false
self.Engaging=false
self:AddTransition({"Patrolling","Engaging"},"Engage","Engaging")
self:AddTransition("Engaging","Fired","Engaging")
self:AddTransition("*","Destroy","*")
self:AddTransition("Engaging","Abort","Patrolling")
self:AddTransition("Engaging","Accomplish","Patrolling")
return self
end
function AI_CAP_ZONE:SetEngageZone(EngageZone)
self:F2()
if EngageZone then
self.EngageZone=EngageZone
else
self.EngageZone=nil
end
end
function AI_CAP_ZONE:SetEngageRange(EngageRange)
self:F2()
if EngageRange then
self.EngageRange=EngageRange
else
self.EngageRange=nil
end
end
function AI_CAP_ZONE:onafterStart(Controllable,From,Event,To)
self:GetParent(self).onafterStart(self,Controllable,From,Event,To)
self:HandleEvent(EVENTS.Dead)
end
function AI_CAP_ZONE.EngageRoute(EngageGroup,Fsm)
EngageGroup:F({"AI_CAP_ZONE.EngageRoute:",EngageGroup:GetName()})
if EngageGroup:IsAlive()then
Fsm:__Engage(1)
end
end
function AI_CAP_ZONE:onbeforeEngage(Controllable,From,Event,To)
if self.Accomplished==true then
return false
end
end
function AI_CAP_ZONE:onafterDetected(Controllable,From,Event,To)
if From~="Engaging"then
local Engage=false
for DetectedUnit,Detected in pairs(self.DetectedUnits)do
local DetectedUnit=DetectedUnit
self:T(DetectedUnit)
if DetectedUnit:IsAlive()and DetectedUnit:IsAir()then
Engage=true
break
end
end
if Engage==true then
self:F('Detected -> Engaging')
self:__Engage(1)
end
end
end
function AI_CAP_ZONE:onafterAbort(Controllable,From,Event,To)
Controllable:ClearTasks()
self:__Route(1)
end
function AI_CAP_ZONE:onafterEngage(Controllable,From,Event,To)
if Controllable and Controllable:IsAlive()then
local EngageRoute={}
local CurrentVec2=self.Controllable:GetVec2()
if not CurrentVec2 then return self end
local CurrentAltitude=self.Controllable:GetAltitude()
local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
local ToEngageZoneSpeed=self.PatrolMaxSpeed
local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToEngageZoneSpeed,
true
)
EngageRoute[#EngageRoute+1]=CurrentRoutePoint
local ToTargetVec2=self.PatrolZone:GetRandomVec2()
self:T2(ToTargetVec2)
local ToTargetAltitude=math.random(self.EngageFloorAltitude,self.EngageCeilingAltitude)
local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
self:T2({self.PatrolMinSpeed,self.PatrolMaxSpeed,ToTargetSpeed})
local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y)
local ToPatrolRoutePoint=ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
true
)
EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint
Controllable:OptionROEOpenFire()
Controllable:OptionROTEvadeFire()
local AttackTasks={}
for DetectedUnit,Detected in pairs(self.DetectedUnits)do
local DetectedUnit=DetectedUnit
self:T({DetectedUnit,DetectedUnit:IsAlive(),DetectedUnit:IsAir()})
if DetectedUnit:IsAlive()and DetectedUnit:IsAir()then
if self.EngageZone then
if DetectedUnit:IsInZone(self.EngageZone)then
self:F({"Within Zone and Engaging ",DetectedUnit})
AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit)
end
else
if self.EngageRange then
if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3())<=self.EngageRange then
self:F({"Within Range and Engaging",DetectedUnit})
AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit)
end
else
AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit)
end
end
else
self.DetectedUnits[DetectedUnit]=nil
end
end
if#AttackTasks==0 then
self:F("No targets found -> Going back to Patrolling")
self:__Abort(1)
self:__Route(1)
self:SetDetectionActivated()
else
AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAP_ZONE.EngageRoute",self)
EngageRoute[1].task=Controllable:TaskCombo(AttackTasks)
self:SetDetectionDeactivated()
end
Controllable:Route(EngageRoute,0.5)
end
end
function AI_CAP_ZONE:onafterAccomplish(Controllable,From,Event,To)
self.Accomplished=true
self:SetDetectionOff()
end
function AI_CAP_ZONE:onafterDestroy(Controllable,From,Event,To,EventData)
if EventData.IniUnit then
self.DetectedUnits[EventData.IniUnit]=nil
end
end
function AI_CAP_ZONE:OnEventDead(EventData)
self:F({"EventDead",EventData})
if EventData.IniDCSUnit then
if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then
self:__Destroy(1,EventData)
end
end
end
AI_CAS_ZONE={
ClassName="AI_CAS_ZONE",
}
function AI_CAS_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageZone,PatrolAltType)
local self=BASE:Inherit(self,AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType))
self.EngageZone=EngageZone
self.Accomplished=false
self:SetDetectionZone(self.EngageZone)
self:AddTransition({"Patrolling","Engaging"},"Engage","Engaging")
self:AddTransition("Engaging","Target","Engaging")
self:AddTransition("Engaging","Fired","Engaging")
self:AddTransition("*","Destroy","*")
self:AddTransition("Engaging","Abort","Patrolling")
self:AddTransition("Engaging","Accomplish","Patrolling")
return self
end
function AI_CAS_ZONE:SetEngageZone(EngageZone)
self:F2()
if EngageZone then
self.EngageZone=EngageZone
else
self.EngageZone=nil
end
end
function AI_CAS_ZONE:onafterStart(Controllable,From,Event,To)
self:GetParent(self).onafterStart(self,Controllable,From,Event,To)
self:HandleEvent(EVENTS.Dead)
self:SetDetectionDeactivated()
end
function AI_CAS_ZONE.EngageRoute(EngageGroup,Fsm)
EngageGroup:F({"AI_CAS_ZONE.EngageRoute:",EngageGroup:GetName()})
if EngageGroup:IsAlive()then
Fsm:__Engage(1,Fsm.EngageSpeed,Fsm.EngageAltitude,Fsm.EngageWeaponExpend,Fsm.EngageAttackQty,Fsm.EngageDirection)
end
end
function AI_CAS_ZONE:onbeforeEngage(Controllable,From,Event,To)
if self.Accomplished==true then
return false
end
end
function AI_CAS_ZONE:onafterTarget(Controllable,From,Event,To)
if Controllable:IsAlive()then
local AttackTasks={}
for DetectedUnit,Detected in pairs(self.DetectedUnits)do
local DetectedUnit=DetectedUnit
if DetectedUnit:IsAlive()then
if DetectedUnit:IsInZone(self.EngageZone)then
if Detected==true then
self:F({"Target: ",DetectedUnit})
self.DetectedUnits[DetectedUnit]=false
local AttackTask=Controllable:TaskAttackUnit(DetectedUnit,false,self.EngageWeaponExpend,self.EngageAttackQty,self.EngageDirection,self.EngageAltitude,nil)
self.Controllable:PushTask(AttackTask,1)
end
end
else
self.DetectedUnits[DetectedUnit]=nil
end
end
self:__Target(-10)
end
end
function AI_CAS_ZONE:onafterAbort(Controllable,From,Event,To)
Controllable:ClearTasks()
self:__Route(1)
end
function AI_CAS_ZONE:onafterEngage(Controllable,From,Event,To,
EngageSpeed,
EngageAltitude,
EngageWeaponExpend,
EngageAttackQty,
EngageDirection)
self:F("onafterEngage")
self.EngageSpeed=EngageSpeed or 400
self.EngageAltitude=EngageAltitude or 2000
self.EngageWeaponExpend=EngageWeaponExpend
self.EngageAttackQty=EngageAttackQty
self.EngageDirection=EngageDirection
if Controllable:IsAlive()then
Controllable:OptionROEOpenFire()
Controllable:OptionROTVertical()
local EngageRoute={}
local CurrentVec2=self.Controllable:GetVec2()
local CurrentAltitude=self.Controllable:GetAltitude()
local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
local ToEngageZoneSpeed=self.PatrolMaxSpeed
local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
self.EngageSpeed,
true
)
EngageRoute[#EngageRoute+1]=CurrentRoutePoint
local AttackTasks={}
for DetectedUnit,Detected in pairs(self.DetectedUnits)do
local DetectedUnit=DetectedUnit
self:T(DetectedUnit)
if DetectedUnit:IsAlive()then
if DetectedUnit:IsInZone(self.EngageZone)then
self:F({"Engaging ",DetectedUnit})
AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit,
true,
EngageWeaponExpend,
EngageAttackQty,
EngageDirection
)
end
else
self.DetectedUnits[DetectedUnit]=nil
end
end
AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAS_ZONE.EngageRoute",self)
EngageRoute[#EngageRoute].task=Controllable:TaskCombo(AttackTasks)
local ToTargetVec2=self.EngageZone:GetRandomVec2()
self:T2(ToTargetVec2)
local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y)
local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
self.EngageSpeed,
true
)
EngageRoute[#EngageRoute+1]=ToTargetRoutePoint
Controllable:Route(EngageRoute,0.5)
self:SetRefreshTimeInterval(2)
self:SetDetectionActivated()
self:__Target(-2)
end
end
function AI_CAS_ZONE:onafterAccomplish(Controllable,From,Event,To)
self.Accomplished=true
self:SetDetectionDeactivated()
end
function AI_CAS_ZONE:onafterDestroy(Controllable,From,Event,To,EventData)
if EventData.IniUnit then
self.DetectedUnits[EventData.IniUnit]=nil
end
end
function AI_CAS_ZONE:OnEventDead(EventData)
self:F({"EventDead",EventData})
if EventData.IniDCSUnit then
if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then
self:__Destroy(1,EventData)
end
end
end
AI_BAI_ZONE={
ClassName="AI_BAI_ZONE",
}
function AI_BAI_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageZone,PatrolAltType)
local self=BASE:Inherit(self,AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType))
self.EngageZone=EngageZone
self.Accomplished=false
self:SetDetectionZone(self.EngageZone)
self:SearchOn()
self:AddTransition({"Patrolling","Engaging"},"Engage","Engaging")
self:AddTransition("Engaging","Target","Engaging")
self:AddTransition("Engaging","Fired","Engaging")
self:AddTransition("*","Destroy","*")
self:AddTransition("Engaging","Abort","Patrolling")
self:AddTransition("Engaging","Accomplish","Patrolling")
return self
end
function AI_BAI_ZONE:SetEngageZone(EngageZone)
self:F2()
if EngageZone then
self.EngageZone=EngageZone
else
self.EngageZone=nil
end
end
function AI_BAI_ZONE:SearchOnOff(Search)
self.Search=Search
return self
end
function AI_BAI_ZONE:SearchOff()
self:SearchOnOff(false)
return self
end
function AI_BAI_ZONE:SearchOn()
self:SearchOnOff(true)
return self
end
function AI_BAI_ZONE:onafterStart(Controllable,From,Event,To)
self:GetParent(self).onafterStart(self,Controllable,From,Event,To)
self:HandleEvent(EVENTS.Dead)
self:SetDetectionDeactivated()
end
function _NewEngageRoute(AIControllable)
AIControllable:T("NewEngageRoute")
local EngageZone=AIControllable:GetState(AIControllable,"EngageZone")
EngageZone:__Engage(1,EngageZone.EngageSpeed,EngageZone.EngageAltitude,EngageZone.EngageWeaponExpend,EngageZone.EngageAttackQty,EngageZone.EngageDirection)
end
function AI_BAI_ZONE:onbeforeEngage(Controllable,From,Event,To)
if self.Accomplished==true then
return false
end
end
function AI_BAI_ZONE:onafterTarget(Controllable,From,Event,To)
self:F({"onafterTarget",self.Search,Controllable:IsAlive()})
if Controllable:IsAlive()then
local AttackTasks={}
if self.Search==true then
for DetectedUnit,Detected in pairs(self.DetectedUnits)do
local DetectedUnit=DetectedUnit
if DetectedUnit:IsAlive()then
if DetectedUnit:IsInZone(self.EngageZone)then
if Detected==true then
self:F({"Target: ",DetectedUnit})
self.DetectedUnits[DetectedUnit]=false
local AttackTask=Controllable:TaskAttackUnit(DetectedUnit,false,self.EngageWeaponExpend,self.EngageAttackQty,self.EngageDirection,self.EngageAltitude,nil)
self.Controllable:PushTask(AttackTask,1)
end
end
else
self.DetectedUnits[DetectedUnit]=nil
end
end
else
self:F("Attack zone")
local AttackTask=Controllable:TaskAttackMapObject(
self.EngageZone:GetPointVec2():GetVec2(),
true,
self.EngageWeaponExpend,
self.EngageAttackQty,
self.EngageDirection,
self.EngageAltitude
)
self.Controllable:PushTask(AttackTask,1)
end
self:__Target(-10)
end
end
function AI_BAI_ZONE:onafterAbort(Controllable,From,Event,To)
Controllable:ClearTasks()
self:__Route(1)
end
function AI_BAI_ZONE:onafterEngage(Controllable,From,Event,To,
EngageSpeed,
EngageAltitude,
EngageWeaponExpend,
EngageAttackQty,
EngageDirection)
self:F("onafterEngage")
self.EngageSpeed=EngageSpeed or 400
self.EngageAltitude=EngageAltitude or 2000
self.EngageWeaponExpend=EngageWeaponExpend
self.EngageAttackQty=EngageAttackQty
self.EngageDirection=EngageDirection
if Controllable:IsAlive()then
local EngageRoute={}
local CurrentVec2=self.Controllable:GetVec2()
local CurrentAltitude=self.Controllable:GetAltitude()
local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y)
local ToEngageZoneSpeed=self.PatrolMaxSpeed
local CurrentRoutePoint=CurrentPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
self.EngageSpeed,
true
)
EngageRoute[#EngageRoute+1]=CurrentRoutePoint
local AttackTasks={}
if self.Search==true then
for DetectedUnitID,DetectedUnitData in pairs(self.DetectedUnits)do
local DetectedUnit=DetectedUnitData
self:T(DetectedUnit)
if DetectedUnit:IsAlive()then
if DetectedUnit:IsInZone(self.EngageZone)then
self:F({"Engaging ",DetectedUnit})
AttackTasks[#AttackTasks+1]=Controllable:TaskBombing(
DetectedUnit:GetPointVec2():GetVec2(),
true,
EngageWeaponExpend,
EngageAttackQty,
EngageDirection,
EngageAltitude
)
end
else
self.DetectedUnits[DetectedUnit]=nil
end
end
else
self:F("Attack zone")
AttackTasks[#AttackTasks+1]=Controllable:TaskAttackMapObject(
self.EngageZone:GetPointVec2():GetVec2(),
true,
EngageWeaponExpend,
EngageAttackQty,
EngageDirection,
EngageAltitude
)
end
EngageRoute[#EngageRoute].task=Controllable:TaskCombo(AttackTasks)
local ToTargetVec2=self.EngageZone:GetRandomVec2()
self:T2(ToTargetVec2)
local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y)
local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
self.EngageSpeed,
true
)
EngageRoute[#EngageRoute+1]=ToTargetRoutePoint
Controllable:OptionROEOpenFire()
Controllable:OptionROTVertical()
Controllable:WayPointInitialize(EngageRoute)
Controllable:SetState(Controllable,"EngageZone",self)
Controllable:WayPointFunction(#EngageRoute,1,"_NewEngageRoute")
Controllable:WayPointExecute(1)
self:SetRefreshTimeInterval(2)
self:SetDetectionActivated()
self:__Target(-2)
end
end
function AI_BAI_ZONE:onafterAccomplish(Controllable,From,Event,To)
self.Accomplished=true
self:SetDetectionDeactivated()
end
function AI_BAI_ZONE:onafterDestroy(Controllable,From,Event,To,EventData)
if EventData.IniUnit then
self.DetectedUnits[EventData.IniUnit]=nil
end
end
function AI_BAI_ZONE:OnEventDead(EventData)
self:F({"EventDead",EventData})
if EventData.IniDCSUnit then
if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then
self:__Destroy(1,EventData)
end
end
end
AI_FORMATION={
ClassName="AI_FORMATION",
FollowName=nil,
FollowUnit=nil,
FollowGroupSet=nil,
FollowMode=1,
MODE={
FOLLOW=1,
MISSION=2,
},
FollowScheduler=nil,
OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE,
OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION,
dtFollow=0.5,
}
AI_FORMATION.__Enum={}
AI_FORMATION.__Enum.Formation={
None=0,
Mission=1,
Line=2,
Trail=3,
Stack=4,
LeftLine=5,
RightLine=6,
LeftWing=7,
RightWing=8,
Vic=9,
Box=10,
}
AI_FORMATION.__Enum.Mode={
Mission="M",
Formation="F",
Attack="A",
Reconnaissance="R",
}
AI_FORMATION.__Enum.ReportType={
Airborne="*",
Airborne="A",
GroundRadar="R",
Ground="G",
}
function AI_FORMATION:New(FollowUnit,FollowGroupSet,FollowName,FollowBriefing)
local self=BASE:Inherit(self,FSM_SET:New(FollowGroupSet))
self:F({FollowUnit,FollowGroupSet,FollowName})
self.FollowUnit=FollowUnit
self.FollowGroupSet=FollowGroupSet
self.FollowGroupSet:ForEachGroup(
function(FollowGroup)
FollowGroup:SetState(self,"Mode",self.__Enum.Mode.Formation)
end
)
self:SetFlightModeFormation()
self:SetFlightRandomization(2)
self:SetStartState("None")
self:AddTransition("*","Stop","Stopped")
self:AddTransition({"None","Stopped"},"Start","Following")
self:AddTransition("*","FormationLine","*")
self:AddTransition("*","FormationTrail","*")
self:AddTransition("*","FormationStack","*")
self:AddTransition("*","FormationLeftLine","*")
self:AddTransition("*","FormationRightLine","*")
self:AddTransition("*","FormationLeftWing","*")
self:AddTransition("*","FormationRightWing","*")
self:AddTransition("*","FormationCenterWing","*")
self:AddTransition("*","FormationVic","*")
self:AddTransition("*","FormationBox","*")
self:AddTransition("*","Follow","Following")
self:FormationLeftLine(500,0,250,250)
self.FollowName=FollowName
self.FollowBriefing=FollowBriefing
self.CT1=0
self.GT1=0
self.FollowMode=AI_FORMATION.MODE.MISSION
return self
end
function AI_FORMATION:SetFollowTimeInterval(dt)
self.dtFollow=dt or 0.5
return self
end
function AI_FORMATION:TestSmokeDirectionVector(SmokeDirection)
self.SmokeDirectionVector=(SmokeDirection==true)and true or false
return self
end
function AI_FORMATION:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,Formation)
self:F({FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,Formation})
XStart=XStart or self.XStart
XSpace=XSpace or self.XSpace
YStart=YStart or self.YStart
YSpace=YSpace or self.YSpace
ZStart=ZStart or self.ZStart
ZSpace=ZSpace or self.ZSpace
FollowGroupSet:Flush(self)
local FollowSet=FollowGroupSet:GetSet()
local i=1
for FollowID,FollowGroup in pairs(FollowSet)do
local PointVec3=POINT_VEC3:New()
PointVec3:SetX(XStart+i*XSpace)
PointVec3:SetY(YStart+i*YSpace)
PointVec3:SetZ(ZStart+i*ZSpace)
local Vec3=PointVec3:GetVec3()
FollowGroup:SetState(self,"FormationVec3",Vec3)
i=i+1
FollowGroup:SetState(FollowGroup,"Formation",Formation)
end
return self
end
function AI_FORMATION:onafterFormationTrail(FollowGroupSet,From,Event,To,XStart,XSpace,YStart)
self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,0,0,self.__Enum.Formation.Trail)
return self
end
function AI_FORMATION:onafterFormationStack(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace)
self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,0,0,self.__Enum.Formation.Stack)
return self
end
function AI_FORMATION:onafterFormationLeftLine(FollowGroupSet,From,Event,To,XStart,YStart,ZStart,ZSpace)
self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,-ZStart,-ZSpace,self.__Enum.Formation.LeftLine)
return self
end
function AI_FORMATION:onafterFormationRightLine(FollowGroupSet,From,Event,To,XStart,YStart,ZStart,ZSpace)
self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightLine)
return self
end
function AI_FORMATION:onafterFormationLeftWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,ZStart,ZSpace)
self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,-ZStart,-ZSpace,self.__Enum.Formation.LeftWing)
return self
end
function AI_FORMATION:onafterFormationRightWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,ZStart,ZSpace)
self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightWing)
return self
end
function AI_FORMATION:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
local FollowSet=FollowGroupSet:GetSet()
local i=0
for FollowID,FollowGroup in pairs(FollowSet)do
local PointVec3=POINT_VEC3:New()
local Side=(i%2==0)and 1 or-1
local Row=i/2+1
PointVec3:SetX(XStart+Row*XSpace)
PointVec3:SetY(YStart)
PointVec3:SetZ(Side*(ZStart+i*ZSpace))
local Vec3=PointVec3:GetVec3()
FollowGroup:SetState(self,"FormationVec3",Vec3)
i=i+1
FollowGroup:SetState(FollowGroup,"Formation",self.__Enum.Formation.Vic)
end
return self
end
function AI_FORMATION:onafterFormationVic(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
self:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
return self
end
function AI_FORMATION:onafterFormationBox(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels)
local FollowSet=FollowGroupSet:GetSet()
local i=0
for FollowID,FollowGroup in pairs(FollowSet)do
local PointVec3=POINT_VEC3:New()
local ZIndex=i%ZLevels
local XIndex=math.floor(i/ZLevels)
local YIndex=math.floor(i/ZLevels)
PointVec3:SetX(XStart+XIndex*XSpace)
PointVec3:SetY(YStart+YIndex*YSpace)
PointVec3:SetZ(-ZStart-(ZSpace*ZLevels/2)+ZSpace*ZIndex)
local Vec3=PointVec3:GetVec3()
FollowGroup:SetState(self,"FormationVec3",Vec3)
i=i+1
FollowGroup:SetState(FollowGroup,"Formation",self.__Enum.Formation.Box)
end
return self
end
function AI_FORMATION:SetFlightRandomization(FlightRandomization)
self.FlightRandomization=FlightRandomization
return self
end
function AI_FORMATION:GetFlightMode(FollowGroup)
if FollowGroup then
FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode"))
FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Mission)
end
return FollowGroup:GetState(FollowGroup,"Mode")
end
function AI_FORMATION:SetFlightModeMission(FollowGroup)
if FollowGroup then
FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode"))
FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Mission)
else
self.FollowGroupSet:ForSomeGroupAlive(
function(FollowGroup)
FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode"))
FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Mission)
end
)
end
return self
end
function AI_FORMATION:SetFlightModeAttack(FollowGroup)
if FollowGroup then
FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode"))
FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Attack)
else
self.FollowGroupSet:ForSomeGroupAlive(
function(FollowGroup)
FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode"))
FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Attack)
end
)
end
return self
end
function AI_FORMATION:SetFlightModeFormation(FollowGroup)
if FollowGroup then
FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode"))
FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Formation)
else
self.FollowGroupSet:ForSomeGroupAlive(
function(FollowGroup)
FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode"))
FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Formation)
end
)
end
return self
end
function AI_FORMATION:onafterStop(FollowGroupSet,From,Event,To)
self:E("Stopping formation.")
end
function AI_FORMATION:onbeforeFollow(FollowGroupSet,From,Event,To)
if From=="Stopped"then
return false
end
return true
end
function AI_FORMATION:onenterFollowing(FollowGroupSet)
if self.FollowUnit:IsAlive()then
local ClientUnit=self.FollowUnit
local CT1,CT2,CV1,CV2
CT1=ClientUnit:GetState(self,"CT1")
local CuVec3=ClientUnit:GetVec3()
if CT1==nil or CT1==0 then
ClientUnit:SetState(self,"CV1",CuVec3)
ClientUnit:SetState(self,"CT1",timer.getTime())
else
CT1=ClientUnit:GetState(self,"CT1")
CT2=timer.getTime()
CV1=ClientUnit:GetState(self,"CV1")
CV2=CuVec3
ClientUnit:SetState(self,"CT1",CT2)
ClientUnit:SetState(self,"CV1",CV2)
end
for _,_group in pairs(FollowGroupSet:GetSet())do
local group=_group
if group and group:IsAlive()then
self:FollowMe(group,ClientUnit,CT1,CV1,CT2,CV2)
end
end
self:__Follow(-self.dtFollow)
end
end
function AI_FORMATION:FollowMe(FollowGroup,ClientUnit,CT1,CV1,CT2,CV2)
if FollowGroup:GetState(FollowGroup,"Mode")==self.__Enum.Mode.Formation and not self:Is("Stopped")then
self:T({Mode=FollowGroup:GetState(FollowGroup,"Mode")})
FollowGroup:OptionROTEvadeFire()
FollowGroup:OptionROEReturnFire()
local GroupUnit=FollowGroup:GetUnit(1)
local GuVec3=GroupUnit:GetVec3()
local FollowFormation=FollowGroup:GetState(self,"FormationVec3")
if FollowFormation then
local FollowDistance=FollowFormation.x
local GT1=GroupUnit:GetState(self,"GT1")
if CT1==nil or CT1==0 or GT1==nil or GT1==0 then
GroupUnit:SetState(self,"GV1",GuVec3)
GroupUnit:SetState(self,"GT1",timer.getTime())
else
local CD=((CV2.x-CV1.x)^2+(CV2.y-CV1.y)^2+(CV2.z-CV1.z)^2)^0.5
local CT=CT2-CT1
local CS=(3600/CT)*(CD/1000)/3.6
local CDv={x=CV2.x-CV1.x,y=CV2.y-CV1.y,z=CV2.z-CV1.z}
local Ca=math.atan2(CDv.x,CDv.z)
local GT1=GroupUnit:GetState(self,"GT1")
local GT2=timer.getTime()
local GV1=GroupUnit:GetState(self,"GV1")
local GV2=GuVec3
GV2.x=GV2.x+math.random(-self.FlightRandomization/2,self.FlightRandomization/2)
GV2.y=GV2.y+math.random(-self.FlightRandomization/2,self.FlightRandomization/2)
GV2.z=GV2.z+math.random(-self.FlightRandomization/2,self.FlightRandomization/2)
GroupUnit:SetState(self,"GT1",GT2)
GroupUnit:SetState(self,"GV1",GV2)
local GD=((GV2.x-GV1.x)^2+(GV2.y-GV1.y)^2+(GV2.z-GV1.z)^2)^0.5
local GT=GT2-GT1
local GDv={x=GV2.x-CV1.x,y=GV2.y-CV1.y,z=GV2.z-CV1.z}
local Alpha_T=math.atan2(GDv.x,GDv.z)-math.atan2(CDv.x,CDv.z)
local Alpha_R=(Alpha_T<0)and Alpha_T+2*math.pi or Alpha_T
local Position=math.cos(Alpha_R)
local GD=((GDv.x)^2+(GDv.z)^2)^0.5
local Distance=GD*Position+-CS*0.5
local GV={x=GV2.x-CV2.x,y=GV2.y-CV2.y,z=GV2.z-CV2.z}
local GH2={x=GV2.x,y=CV2.y+FollowFormation.y,z=GV2.z}
local alpha=math.atan2(GV.x,GV.z)
local GVx=FollowFormation.z*math.cos(Ca)+FollowFormation.x*math.sin(Ca)
local GVz=FollowFormation.x*math.cos(Ca)-FollowFormation.z*math.sin(Ca)
local Inclination=(Distance+FollowFormation.x)/10
if Inclination<-30 then
Inclination=-30
end
local CVI={
x=CV2.x+CS*10*math.sin(Ca),
y=GH2.y+Inclination,
y=GH2.y,
z=CV2.z+CS*10*math.cos(Ca),
}
local DV={x=CV2.x-CVI.x,y=CV2.y-CVI.y,z=CV2.z-CVI.z}
local DVu={x=DV.x/FollowDistance,y=DV.y,z=DV.z/FollowDistance}
local GDV={x=CVI.x,y=CVI.y,z=CVI.z}
local ADDx=FollowFormation.x*math.cos(alpha)-FollowFormation.z*math.sin(alpha)
local ADDz=FollowFormation.z*math.cos(alpha)+FollowFormation.x*math.sin(alpha)
local GDV_Formation={
x=GDV.x-GVx,
y=GDV.y,
z=GDV.z-GVz
}
if self.SmokeDirectionVector==true then
trigger.action.smoke(GDV,trigger.smokeColor.Green)
trigger.action.smoke(GDV_Formation,trigger.smokeColor.White)
end
local Time=120
local Speed=-(Distance+FollowFormation.x)/Time
if Distance>-10000 then
Speed=-(Distance+FollowFormation.x)/60
end
if Distance>-2500 then
Speed=-(Distance+FollowFormation.x)/20
end
local GS=Speed+CS
FollowGroup:RouteToVec3(GDV_Formation,GS)
end
end
end
end
AI_ESCORT={
ClassName="AI_ESCORT",
EscortName=nil,
EscortUnit=nil,
EscortGroup=nil,
EscortMode=1,
Targets={},
FollowScheduler=nil,
ReportTargets=true,
OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE,
OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION,
SmokeDirectionVector=false,
TaskPoints={}
}
AI_ESCORT.Detection=nil
function AI_ESCORT:New(EscortUnit,EscortGroupSet,EscortName,EscortBriefing)
local self=BASE:Inherit(self,AI_FORMATION:New(EscortUnit,EscortGroupSet,EscortName,EscortBriefing))
self:F({EscortUnit,EscortGroupSet})
self.PlayerUnit=self.FollowUnit
self.PlayerGroup=self.FollowUnit:GetGroup()
self.EscortName=EscortName
self.EscortGroupSet=EscortGroupSet
self.EscortGroupSet:SetSomeIteratorLimit(8)
self.EscortBriefing=EscortBriefing
self.Menu={}
self.FollowDistance=100
self.CT1=0
self.GT1=0
EscortGroupSet:ForEachGroup(
function(EscortGroup)
if not self.PlayerUnit._EscortGroups then
self.PlayerUnit._EscortGroups={}
end
if not self.PlayerUnit._EscortGroups[EscortGroup:GetName()]then
self.PlayerUnit._EscortGroups[EscortGroup:GetName()]={}
self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortGroup=EscortGroup
self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortName=self.EscortName
self.PlayerUnit._EscortGroups[EscortGroup:GetName()].Detection=self.Detection
end
end
)
self:SetFlightReportType(self.__Enum.ReportType.All)
return self
end
function AI_ESCORT:_InitFlightMenus()
self:SetFlightMenuJoinUp()
self:SetFlightMenuFormation("Trail")
self:SetFlightMenuFormation("Stack")
self:SetFlightMenuFormation("LeftLine")
self:SetFlightMenuFormation("RightLine")
self:SetFlightMenuFormation("LeftWing")
self:SetFlightMenuFormation("RightWing")
self:SetFlightMenuFormation("Vic")
self:SetFlightMenuFormation("Box")
self:SetFlightMenuHoldAtEscortPosition()
self:SetFlightMenuHoldAtLeaderPosition()
self:SetFlightMenuFlare()
self:SetFlightMenuSmoke()
self:SetFlightMenuROE()
self:SetFlightMenuROT()
self:SetFlightMenuTargets()
self:SetFlightMenuReportType()
end
function AI_ESCORT:_InitEscortMenus(EscortGroup)
EscortGroup.EscortMenu=MENU_GROUP:New(self.PlayerGroup,EscortGroup:GetCallsign(),self.MainMenu)
self:SetEscortMenuJoinUp(EscortGroup)
self:SetEscortMenuResumeMission(EscortGroup)
self:SetEscortMenuHoldAtEscortPosition(EscortGroup)
self:SetEscortMenuHoldAtLeaderPosition(EscortGroup)
self:SetEscortMenuFlare(EscortGroup)
self:SetEscortMenuSmoke(EscortGroup)
self:SetEscortMenuROE(EscortGroup)
self:SetEscortMenuROT(EscortGroup)
self:SetEscortMenuTargets(EscortGroup)
end
function AI_ESCORT:_InitEscortRoute(EscortGroup)
EscortGroup.MissionRoute=EscortGroup:GetTaskRoute()
end
function AI_ESCORT:onafterStart(EscortGroupSet)
self:F()
EscortGroupSet:ForEachGroup(
function(EscortGroup)
EscortGroup:WayPointInitialize()
EscortGroup:OptionROTVertical()
EscortGroup:OptionROEOpenFire()
end
)
local LeaderEscort=EscortGroupSet:GetFirst()
if LeaderEscort then
local Report=REPORT:New("Escort reporting:")
Report:Add("Joining Up "..EscortGroupSet:GetUnitTypeNames():Text(", ").." from "..LeaderEscort:GetCoordinate():ToString(self.PlayerUnit))
LeaderEscort:MessageTypeToGroup(Report:Text(),MESSAGE.Type.Information,self.PlayerUnit)
end
self.Detection=DETECTION_AREAS:New(EscortGroupSet,5000)
self.Detection:InitDetectVisual(true)
self.Detection:InitDetectIRST(true)
self.Detection:InitDetectOptical(true)
self.Detection:InitDetectRadar(true)
self.Detection:InitDetectRWR(true)
self.Detection:SetAcceptRange(100000)
self.Detection:__Start(30)
self.MainMenu=MENU_GROUP:New(self.PlayerGroup,self.EscortName)
self.FlightMenu=MENU_GROUP:New(self.PlayerGroup,"Flight",self.MainMenu)
self:_InitFlightMenus()
self.EscortGroupSet:ForSomeGroupAlive(
function(EscortGroup)
self:_InitEscortMenus(EscortGroup)
self:_InitEscortRoute(EscortGroup)
self:SetFlightModeFormation(EscortGroup)
function EscortGroup:OnEventDeadOrCrash(EventData)
self:F({"EventDead",EventData})
self.EscortMenu:Remove()
end
EscortGroup:HandleEvent(EVENTS.Dead,EscortGroup.OnEventDeadOrCrash)
EscortGroup:HandleEvent(EVENTS.Crash,EscortGroup.OnEventDeadOrCrash)
end
)
end
function AI_ESCORT:onafterStop(EscortGroupSet)
self:F()
EscortGroupSet:ForEachGroup(
function(EscortGroup)
EscortGroup:WayPointInitialize()
EscortGroup:OptionROTVertical()
EscortGroup:OptionROEOpenFire()
end
)
self.Detection:Stop()
self.MainMenu:Remove()
end
function AI_ESCORT:SetDetection(Detection)
self.Detection=Detection
self.EscortGroup.Detection=self.Detection
self.PlayerUnit._EscortGroups[self.EscortGroup:GetName()].Detection=self.EscortGroup.Detection
Detection:__Start(1)
end
function AI_ESCORT:TestSmokeDirectionVector(SmokeDirection)
self.SmokeDirectionVector=(SmokeDirection==true)and true or false
end
function AI_ESCORT:MenusHelicopters(XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels)
self:F()
self.XStart=XStart or 50
self.XSpace=XSpace or 50
self.YStart=YStart or 50
self.YSpace=YSpace or 50
self.ZStart=ZStart or 50
self.ZSpace=ZSpace or 50
self.ZLevels=ZLevels or 10
self:MenuJoinUp()
self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart)
self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace)
self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace)
self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace)
self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace)
self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace)
self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace)
self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels)
self:MenuHoldAtEscortPosition(30)
self:MenuHoldAtEscortPosition(100)
self:MenuHoldAtEscortPosition(500)
self:MenuHoldAtLeaderPosition(30,500)
self:MenuFlare()
self:MenuSmoke()
self:MenuTargets(60)
self:MenuAssistedAttack()
self:MenuROE()
self:MenuROT()
return self
end
function AI_ESCORT:MenusAirplanes(XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels)
self:F()
self.XStart=XStart or 50
self.XSpace=XSpace or 50
self.YStart=YStart or 50
self.YSpace=YSpace or 50
self.ZStart=ZStart or 50
self.ZSpace=ZSpace or 50
self.ZLevels=ZLevels or 10
self:MenuJoinUp()
self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart)
self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace)
self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace)
self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace)
self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace)
self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace)
self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace)
self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels)
self:MenuHoldAtEscortPosition(1000,500)
self:MenuHoldAtLeaderPosition(1000,500)
self:MenuFlare()
self:MenuSmoke()
self:MenuTargets(60)
self:MenuAssistedAttack()
self:MenuROE()
self:MenuROT()
return self
end
function AI_ESCORT:SetFlightMenuFormation(Formation)
local FormationID="Formation"..Formation
local MenuFormation=self.Menu[FormationID]
if MenuFormation then
local Arguments=MenuFormation.Arguments
local FlightMenuFormation=MENU_GROUP:New(self.PlayerGroup,"Formation",self.MainMenu)
local MenuFlightFormationID=MENU_GROUP_COMMAND:New(self.PlayerGroup,Formation,FlightMenuFormation,
function(self,Formation,...)
self.EscortGroupSet:ForSomeGroupAlive(
function(EscortGroup,self,Formation,Arguments)
if EscortGroup:IsAir()then
self:E({FormationID=FormationID})
self[FormationID](self,unpack(Arguments))
end
end,self,Formation,Arguments
)
end,self,Formation,Arguments
)
end
return self
end
function AI_ESCORT:MenuFormation(Formation,...)
local FormationID="Formation"..Formation
self.Menu[FormationID]=self.Menu[FormationID]or{}
self.Menu[FormationID].Arguments=arg
end
function AI_ESCORT:MenuFormationTrail(XStart,XSpace,YStart)
self:MenuFormation("Trail",XStart,XSpace,YStart)
return self
end
function AI_ESCORT:MenuFormationStack(XStart,XSpace,YStart,YSpace)
self:MenuFormation("Stack",XStart,XSpace,YStart,YSpace)
return self
end
function AI_ESCORT:MenuFormationLeftLine(XStart,YStart,ZStart,ZSpace)
self:MenuFormation("LeftLine",XStart,YStart,ZStart,ZSpace)
return self
end
function AI_ESCORT:MenuFormationRightLine(XStart,YStart,ZStart,ZSpace)
self:MenuFormation("RightLine",XStart,YStart,ZStart,ZSpace)
return self
end
function AI_ESCORT:MenuFormationLeftWing(XStart,XSpace,YStart,ZStart,ZSpace)
self:MenuFormation("LeftWing",XStart,XSpace,YStart,ZStart,ZSpace)
return self
end
function AI_ESCORT:MenuFormationRightWing(XStart,XSpace,YStart,ZStart,ZSpace)
self:MenuFormation("RightWing",XStart,XSpace,YStart,ZStart,ZSpace)
return self
end
function AI_ESCORT:MenuFormationCenterWing(XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
self:MenuFormation("CenterWing",XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
return self
end
function AI_ESCORT:MenuFormationVic(XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
self:MenuFormation("Vic",XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
return self
end
function AI_ESCORT:MenuFormationBox(XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels)
self:MenuFormation("Box",XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels)
return self
end
function AI_ESCORT:SetFlightMenuJoinUp()
if self.Menu.JoinUp==true then
local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu)
local FlightMenuJoinUp=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Join Up",FlightMenuReportNavigation,AI_ESCORT._FlightJoinUp,self)
end
end
function AI_ESCORT:SetEscortMenuJoinUp(EscortGroup)
if self.Menu.JoinUp==true then
if EscortGroup:IsAir()then
local EscortGroupName=EscortGroup:GetName()
local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu)
local EscortMenuJoinUp=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Join Up",EscortMenuReportNavigation,AI_ESCORT._JoinUp,self,EscortGroup)
end
end
end
function AI_ESCORT:MenuJoinUp()
self.Menu.JoinUp=true
return self
end
function AI_ESCORT:SetFlightMenuHoldAtEscortPosition()
for _,MenuHoldAtEscortPosition in pairs(self.Menu.HoldAtEscortPosition)do
local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu)
local FlightMenuHoldPosition=MENU_GROUP_COMMAND
:New(
self.PlayerGroup,
MenuHoldAtEscortPosition.MenuText,
FlightMenuReportNavigation,
AI_ESCORT._FlightHoldPosition,
self,
nil,
MenuHoldAtEscortPosition.Height,
MenuHoldAtEscortPosition.Speed
)
end
return self
end
function AI_ESCORT:SetEscortMenuHoldAtEscortPosition(EscortGroup)
for _,HoldAtEscortPosition in pairs(self.Menu.HoldAtEscortPosition)do
if EscortGroup:IsAir()then
local EscortGroupName=EscortGroup:GetName()
local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu)
local EscortMenuHoldPosition=MENU_GROUP_COMMAND
:New(
self.PlayerGroup,
HoldAtEscortPosition.MenuText,
EscortMenuReportNavigation,
AI_ESCORT._HoldPosition,
self,
EscortGroup,
EscortGroup,
HoldAtEscortPosition.Height,
HoldAtEscortPosition.Speed
)
end
end
return self
end
function AI_ESCORT:MenuHoldAtEscortPosition(Height,Speed,MenuTextFormat)
self:F({Height,Speed,MenuTextFormat})
if not Height then
Height=30
end
if not Speed then
Speed=0
end
local MenuText=""
if not MenuTextFormat then
if Speed==0 then
MenuText=string.format("Hold at %d meter",Height)
else
MenuText=string.format("Hold at %d meter at %d",Height,Speed)
end
else
if Speed==0 then
MenuText=string.format(MenuTextFormat,Height)
else
MenuText=string.format(MenuTextFormat,Height,Speed)
end
end
self.Menu.HoldAtEscortPosition=self.Menu.HoldAtEscortPosition or{}
self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition+1]={}
self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Height=Height
self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Speed=Speed
self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].MenuText=MenuText
return self
end
function AI_ESCORT:SetFlightMenuHoldAtLeaderPosition()
for _,MenuHoldAtLeaderPosition in pairs(self.Menu.HoldAtLeaderPosition)do
local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu)
local FlightMenuHoldAtLeaderPosition=MENU_GROUP_COMMAND
:New(
self.PlayerGroup,
MenuHoldAtLeaderPosition.MenuText,
FlightMenuReportNavigation,
AI_ESCORT._FlightHoldPosition,
self,
self.PlayerGroup,
MenuHoldAtLeaderPosition.Height,
MenuHoldAtLeaderPosition.Speed
)
end
return self
end
function AI_ESCORT:SetEscortMenuHoldAtLeaderPosition(EscortGroup)
for _,HoldAtLeaderPosition in pairs(self.Menu.HoldAtLeaderPosition)do
if EscortGroup:IsAir()then
local EscortGroupName=EscortGroup:GetName()
local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu)
local EscortMenuHoldAtLeaderPosition=MENU_GROUP_COMMAND
:New(
self.PlayerGroup,
HoldAtLeaderPosition.MenuText,
EscortMenuReportNavigation,
AI_ESCORT._HoldPosition,
self,
self.PlayerGroup,
EscortGroup,
HoldAtLeaderPosition.Height,
HoldAtLeaderPosition.Speed
)
end
end
return self
end
function AI_ESCORT:MenuHoldAtLeaderPosition(Height,Speed,MenuTextFormat)
self:F({Height,Speed,MenuTextFormat})
if not Height then
Height=30
end
if not Speed then
Speed=0
end
local MenuText=""
if not MenuTextFormat then
if Speed==0 then
MenuText=string.format("Rejoin and hold at %d meter",Height)
else
MenuText=string.format("Rejoin and hold at %d meter at %d",Height,Speed)
end
else
if Speed==0 then
MenuText=string.format(MenuTextFormat,Height)
else
MenuText=string.format(MenuTextFormat,Height,Speed)
end
end
self.Menu.HoldAtLeaderPosition=self.Menu.HoldAtLeaderPosition or{}
self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition+1]={}
self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Height=Height
self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Speed=Speed
self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].MenuText=MenuText
return self
end
function AI_ESCORT:MenuScanForTargets(Height,Seconds,MenuTextFormat)
self:F({Height,Seconds,MenuTextFormat})
if self.EscortGroup:IsAir()then
if not self.EscortMenuScan then
self.EscortMenuScan=MENU_GROUP:New(self.PlayerGroup,"Scan for targets",self.EscortMenu)
end
if not Height then
Height=100
end
if not Seconds then
Seconds=30
end
local MenuText=""
if not MenuTextFormat then
if Seconds==0 then
MenuText=string.format("At %d meter",Height)
else
MenuText=string.format("At %d meter for %d seconds",Height,Seconds)
end
else
if Seconds==0 then
MenuText=string.format(MenuTextFormat,Height)
else
MenuText=string.format(MenuTextFormat,Height,Seconds)
end
end
if not self.EscortMenuScanForTargets then
self.EscortMenuScanForTargets={}
end
self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1]=MENU_GROUP_COMMAND
:New(
self.PlayerGroup,
MenuText,
self.EscortMenuScan,
AI_ESCORT._ScanTargets,
self,
30
)
end
return self
end
function AI_ESCORT:SetFlightMenuFlare()
for _,MenuFlare in pairs(self.Menu.Flare)do
local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu)
local FlightMenuFlare=MENU_GROUP:New(self.PlayerGroup,MenuFlare.MenuText,FlightMenuReportNavigation)
local FlightMenuFlareGreenFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.Green,"Released a green flare!")
local FlightMenuFlareRedFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.Red,"Released a red flare!")
local FlightMenuFlareWhiteFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.White,"Released a white flare!")
local FlightMenuFlareYellowFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release yellow flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.Yellow,"Released a yellow flare!")
end
return self
end
function AI_ESCORT:SetEscortMenuFlare(EscortGroup)
for _,MenuFlare in pairs(self.Menu.Flare)do
if EscortGroup:IsAir()then
local EscortGroupName=EscortGroup:GetName()
local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu)
local EscortMenuFlare=MENU_GROUP:New(self.PlayerGroup,MenuFlare.MenuText,EscortMenuReportNavigation)
local EscortMenuFlareGreen=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.Green,"Released a green flare!")
local EscortMenuFlareRed=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.Red,"Released a red flare!")
local EscortMenuFlareWhite=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.White,"Released a white flare!")
local EscortMenuFlareYellow=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release yellow flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.Yellow,"Released a yellow flare!")
end
end
return self
end
function AI_ESCORT:MenuFlare(MenuTextFormat)
self:F()
local MenuText=""
if not MenuTextFormat then
MenuText="Flare"
else
MenuText=MenuTextFormat
end
self.Menu.Flare=self.Menu.Flare or{}
self.Menu.Flare[#self.Menu.Flare+1]={}
self.Menu.Flare[#self.Menu.Flare].MenuText=MenuText
return self
end
function AI_ESCORT:SetFlightMenuSmoke()
for _,MenuSmoke in pairs(self.Menu.Smoke)do
local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu)
local FlightMenuSmoke=MENU_GROUP:New(self.PlayerGroup,MenuSmoke.MenuText,FlightMenuReportNavigation)
local FlightMenuSmokeGreenFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Green,"Releasing green smoke!")
local FlightMenuSmokeRedFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Red,"Releasing red smoke!")
local FlightMenuSmokeWhiteFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.White,"Releasing white smoke!")
local FlightMenuSmokeOrangeFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release orange smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Orange,"Releasing orange smoke!")
local FlightMenuSmokeBlueFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release blue smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Blue,"Releasing blue smoke!")
end
return self
end
function AI_ESCORT:SetEscortMenuSmoke(EscortGroup)
for _,MenuSmoke in pairs(self.Menu.Smoke)do
if EscortGroup:IsAir()then
local EscortGroupName=EscortGroup:GetName()
local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu)
local EscortMenuSmoke=MENU_GROUP:New(self.PlayerGroup,MenuSmoke.MenuText,EscortMenuReportNavigation)
local EscortMenuSmokeGreen=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Green,"Releasing green smoke!")
local EscortMenuSmokeRed=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Red,"Releasing red smoke!")
local EscortMenuSmokeWhite=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.White,"Releasing white smoke!")
local EscortMenuSmokeOrange=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release orange smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Orange,"Releasing orange smoke!")
local EscortMenuSmokeBlue=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release blue smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Blue,"Releasing blue smoke!")
end
end
return self
end
function AI_ESCORT:MenuSmoke(MenuTextFormat)
self:F()
local MenuText=""
if not MenuTextFormat then
MenuText="Smoke"
else
MenuText=MenuTextFormat
end
self.Menu.Smoke=self.Menu.Smoke or{}
self.Menu.Smoke[#self.Menu.Smoke+1]={}
self.Menu.Smoke[#self.Menu.Smoke].MenuText=MenuText
return self
end
function AI_ESCORT:SetFlightMenuReportType()
local FlightMenuReportTargets=MENU_GROUP:New(self.PlayerGroup,"Report targets",self.FlightMenu)
local MenuStamp=FlightMenuReportTargets:GetStamp()
local FlightReportType=self:GetFlightReportType()
if FlightReportType~=self.__Enum.ReportType.All then
local FlightMenuReportTargetsAll=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report all targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeAll,self)
:SetTag("ReportType")
:SetStamp(MenuStamp)
end
if FlightReportType==self.__Enum.ReportType.All or FlightReportType~=self.__Enum.ReportType.Airborne then
local FlightMenuReportTargetsAirborne=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report airborne targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeAirborne,self)
:SetTag("ReportType")
:SetStamp(MenuStamp)
end
if FlightReportType==self.__Enum.ReportType.All or FlightReportType~=self.__Enum.ReportType.GroundRadar then
local FlightMenuReportTargetsGroundRadar=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report gound radar targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeGroundRadar,self)
:SetTag("ReportType")
:SetStamp(MenuStamp)
end
if FlightReportType==self.__Enum.ReportType.All or FlightReportType~=self.__Enum.ReportType.Ground then
local FlightMenuReportTargetsGround=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report ground targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeGround,self)
:SetTag("ReportType")
:SetStamp(MenuStamp)
end
FlightMenuReportTargets:RemoveSubMenus(MenuStamp,"ReportType")
end
function AI_ESCORT:SetFlightMenuTargets()
local FlightMenuReportTargets=MENU_GROUP:New(self.PlayerGroup,"Report targets",self.FlightMenu)
local FlightMenuReportTargetsNow=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets now!",FlightMenuReportTargets,AI_ESCORT._FlightReportNearbyTargetsNow,self)
local FlightMenuReportTargetsOn=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets on",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportNearbyTargets,self,true)
local FlightMenuReportTargetsOff=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets off",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportNearbyTargets,self,false)
self.FlightMenuAttack=MENU_GROUP:New(self.PlayerGroup,"Attack targets",self.FlightMenu)
local FlightMenuAttackNearby=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Attack nearest targets",self.FlightMenuAttack,AI_ESCORT._FlightAttackNearestTarget,self):SetTag("Attack")
local FlightMenuAttackNearbyAir=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Attack nearest airborne targets",self.FlightMenuAttack,AI_ESCORT._FlightAttackNearestTarget,self,self.__Enum.ReportType.Air):SetTag("Attack")
local FlightMenuAttackNearbyGround=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Attack nearest ground targets",self.FlightMenuAttack,AI_ESCORT._FlightAttackNearestTarget,self,self.__Enum.ReportType.Ground):SetTag("Attack")
for _,MenuTargets in pairs(self.Menu.Targets)do
MenuTargets.FlightReportTargetsScheduler=SCHEDULER:New(self,self._FlightReportTargetsScheduler,{},MenuTargets.Interval,MenuTargets.Interval)
end
return self
end
function AI_ESCORT:SetEscortMenuTargets(EscortGroup)
for _,MenuTargets in pairs(self.Menu.Targets)do
if EscortGroup:IsAir()then
local EscortGroupName=EscortGroup:GetName()
EscortGroup.EscortMenuReportNearbyTargetsNow=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets",EscortGroup.EscortMenu,AI_ESCORT._ReportNearbyTargetsNow,self,EscortGroup,true)
EscortGroup.ReportTargetsScheduler=SCHEDULER:New(self,self._ReportTargetsScheduler,{EscortGroup},1,MenuTargets.Interval)
EscortGroup.ResumeScheduler=SCHEDULER:New(self,self._ResumeScheduler,{EscortGroup},1,60)
end
end
return self
end
function AI_ESCORT:MenuTargets(Seconds)
self:F({Seconds})
if not Seconds then
Seconds=30
end
self.Menu.Targets=self.Menu.Targets or{}
self.Menu.Targets[#self.Menu.Targets+1]={}
self.Menu.Targets[#self.Menu.Targets].Interval=Seconds
return self
end
function AI_ESCORT:MenuAssistedAttack()
self:F()
self.EscortGroupSet:ForSomeGroupAlive(
function(EscortGroup)
if not EscortGroup:IsAir()then
self.EscortMenuTargetAssistance=MENU_GROUP:New(self.PlayerGroup,"Request assistance from",EscortGroup.EscortMenu)
end
end
)
return self
end
function AI_ESCORT:SetFlightMenuROE()
for _,MenuROE in pairs(self.Menu.ROE)do
local FlightMenuROE=MENU_GROUP:New(self.PlayerGroup,"Rule Of Engagement",self.FlightMenu)
local FlightMenuROEHoldFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Hold fire",FlightMenuROE,AI_ESCORT._FlightROEHoldFire,self,"Holding weapons!")
local FlightMenuROEReturnFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Return fire",FlightMenuROE,AI_ESCORT._FlightROEReturnFire,self,"Returning fire!")
local FlightMenuROEOpenFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open Fire",FlightMenuROE,AI_ESCORT._FlightROEOpenFire,self,"Open fire at designated targets!")
local FlightMenuROEWeaponFree=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Engage all targets",FlightMenuROE,AI_ESCORT._FlightROEWeaponFree,self,"Engaging all targets!")
end
return self
end
function AI_ESCORT:SetEscortMenuROE(EscortGroup)
for _,MenuROE in pairs(self.Menu.ROE)do
if EscortGroup:IsAir()then
local EscortGroupName=EscortGroup:GetName()
local EscortMenuROE=MENU_GROUP:New(self.PlayerGroup,"Rule Of Engagement",EscortGroup.EscortMenu)
if EscortGroup:OptionROEHoldFirePossible()then
local EscortMenuROEHoldFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Hold fire",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEHoldFire,"Holding weapons!")
end
if EscortGroup:OptionROEReturnFirePossible()then
local EscortMenuROEReturnFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Return fire",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEReturnFire,"Returning fire!")
end
if EscortGroup:OptionROEOpenFirePossible()then
EscortGroup.EscortMenuROEOpenFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open Fire",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEOpenFire,"Opening fire on designated targets!!")
end
if EscortGroup:OptionROEWeaponFreePossible()then
EscortGroup.EscortMenuROEWeaponFree=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Engage all targets",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEWeaponFree,"Opening fire on targets of opportunity!")
end
end
end
return self
end
function AI_ESCORT:MenuROE()
self:F()
self.Menu.ROE=self.Menu.ROE or{}
self.Menu.ROE[#self.Menu.ROE+1]={}
return self
end
function AI_ESCORT:SetFlightMenuROT()
for _,MenuROT in pairs(self.Menu.ROT)do
local FlightMenuROT=MENU_GROUP:New(self.PlayerGroup,"Reaction On Threat",self.FlightMenu)
local FlightMenuROTNoReaction=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Fight until death",FlightMenuROT,AI_ESCORT._FlightROTNoReaction,self,"Fighting until death!")
local FlightMenuROTPassiveDefense=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Use flares, chaff and jammers",FlightMenuROT,AI_ESCORT._FlightROTPassiveDefense,self,"Defending using jammers, chaff and flares!")
local FlightMenuROTEvadeFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open fire",FlightMenuROT,AI_ESCORT._FlightROTEvadeFire,self,"Evading on enemy fire!")
local FlightMenuROTVertical=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Avoid radar and evade fire",FlightMenuROT,AI_ESCORT._FlightROTVertical,self,"Evading on enemy fire with vertical manoeuvres!")
end
return self
end
function AI_ESCORT:SetEscortMenuROT(EscortGroup)
for _,MenuROT in pairs(self.Menu.ROT)do
if EscortGroup:IsAir()then
local EscortGroupName=EscortGroup:GetName()
local EscortMenuROT=MENU_GROUP:New(self.PlayerGroup,"Reaction On Threat",EscortGroup.EscortMenu)
if not EscortGroup.EscortMenuEvasion then
if EscortGroup:OptionROTNoReactionPossible()then
local EscortMenuEvasionNoReaction=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Fight until death",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTNoReaction,"Fighting until death!")
end
if EscortGroup:OptionROTPassiveDefensePossible()then
local EscortMenuEvasionPassiveDefense=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Use flares, chaff and jammers",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTPassiveDefense,"Defending using jammers, chaff and flares!")
end
if EscortGroup:OptionROTEvadeFirePossible()then
local EscortMenuEvasionEvadeFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open fire",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTEvadeFire,"Evading on enemy fire!")
end
if EscortGroup:OptionROTVerticalPossible()then
local EscortMenuOptionEvasionVertical=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Avoid radar and evade fire",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTVertical,"Evading on enemy fire with vertical manoeuvres!")
end
end
end
end
return self
end
function AI_ESCORT:MenuROT(MenuTextFormat)
self:F(MenuTextFormat)
self.Menu.ROT=self.Menu.ROT or{}
self.Menu.ROT[#self.Menu.ROT+1]={}
return self
end
function AI_ESCORT:SetEscortMenuResumeMission(EscortGroup)
self:F()
if EscortGroup:IsAir()then
local EscortGroupName=EscortGroup:GetName()
EscortGroup.EscortMenuResumeMission=MENU_GROUP:New(self.PlayerGroup,"Resume from",EscortGroup.EscortMenu)
end
return self
end
function AI_ESCORT:_HoldPosition(OrbitGroup,EscortGroup,OrbitHeight,OrbitSeconds)
local EscortUnit=self.PlayerUnit
local OrbitUnit=OrbitGroup:GetUnit(1)
self:SetFlightModeMission(EscortGroup)
local PointFrom={}
local GroupVec3=EscortGroup:GetUnit(1):GetVec3()
PointFrom={}
PointFrom.x=GroupVec3.x
PointFrom.y=GroupVec3.z
PointFrom.speed=250
PointFrom.type=AI.Task.WaypointType.TURNING_POINT
PointFrom.alt=GroupVec3.y
PointFrom.alt_type=AI.Task.AltitudeType.BARO
local OrbitPoint=OrbitUnit:GetVec2()
local PointTo={}
PointTo.x=OrbitPoint.x
PointTo.y=OrbitPoint.y
PointTo.speed=250
PointTo.type=AI.Task.WaypointType.TURNING_POINT
PointTo.alt=OrbitHeight
PointTo.alt_type=AI.Task.AltitudeType.BARO
PointTo.task=EscortGroup:TaskOrbitCircleAtVec2(OrbitPoint,OrbitHeight,0)
local Points={PointFrom,PointTo}
EscortGroup:OptionROEHoldFire()
EscortGroup:OptionROTPassiveDefense()
EscortGroup:SetTask(EscortGroup:TaskRoute(Points),1)
EscortGroup:MessageTypeToGroup("Orbiting at current location.",MESSAGE.Type.Information,EscortUnit:GetGroup())
end
function AI_ESCORT:_FlightHoldPosition(OrbitGroup,OrbitHeight,OrbitSeconds)
local EscortUnit=self.PlayerUnit
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup,OrbitGroup)
if EscortGroup:IsAir()then
if OrbitGroup==nil then
OrbitGroup=EscortGroup
end
self:_HoldPosition(OrbitGroup,EscortGroup,OrbitHeight,OrbitSeconds)
end
end,OrbitGroup
)
end
function AI_ESCORT:_JoinUp(EscortGroup)
local EscortUnit=self.PlayerUnit
self:SetFlightModeFormation(EscortGroup)
EscortGroup:MessageTypeToGroup("Joining up!",MESSAGE.Type.Information,EscortUnit:GetGroup())
end
function AI_ESCORT:_FlightJoinUp()
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
if EscortGroup:IsAir()then
self:_JoinUp(EscortGroup)
end
end
)
end
function AI_ESCORT:_EscortFormationTrail(EscortGroup,XStart,XSpace,YStart)
self:FormationTrail(XStart,XSpace,YStart)
end
function AI_ESCORT:_FlightFormationTrail(XStart,XSpace,YStart)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
if EscortGroup:IsAir()then
self:_EscortFormationTrail(EscortGroup,XStart,XSpace,YStart)
end
end
)
end
function AI_ESCORT:_EscortFormationStack(EscortGroup,XStart,XSpace,YStart,YSpace)
self:FormationStack(XStart,XSpace,YStart,YSpace)
end
function AI_ESCORT:_FlightFormationStack(XStart,XSpace,YStart,YSpace)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
if EscortGroup:IsAir()then
self:_EscortFormationStack(EscortGroup,XStart,XSpace,YStart,YSpace)
end
end
)
end
function AI_ESCORT:_Flare(EscortGroup,Color,Message)
local EscortUnit=self.PlayerUnit
EscortGroup:GetUnit(1):Flare(Color)
EscortGroup:MessageTypeToGroup(Message,MESSAGE.Type.Information,EscortUnit:GetGroup())
end
function AI_ESCORT:_FlightFlare(Color,Message)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
if EscortGroup:IsAir()then
self:_Flare(EscortGroup,Color,Message)
end
end
)
end
function AI_ESCORT:_Smoke(EscortGroup,Color,Message)
local EscortUnit=self.PlayerUnit
EscortGroup:GetUnit(1):Smoke(Color)
EscortGroup:MessageTypeToGroup(Message,MESSAGE.Type.Information,EscortUnit:GetGroup())
end
function AI_ESCORT:_FlightSmoke(Color,Message)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
if EscortGroup:IsAir()then
self:_Smoke(EscortGroup,Color,Message)
end
end
)
end
function AI_ESCORT:_ReportNearbyTargetsNow(EscortGroup)
local EscortUnit=self.PlayerUnit
self:_ReportTargetsScheduler(EscortGroup)
end
function AI_ESCORT:_FlightReportNearbyTargetsNow()
self:_FlightReportTargetsScheduler()
end
function AI_ESCORT:_FlightSwitchReportNearbyTargets(ReportTargets)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
if EscortGroup:IsAir()then
self:_EscortSwitchReportNearbyTargets(EscortGroup,ReportTargets)
end
end
)
end
function AI_ESCORT:SetFlightReportType(ReportType)
self.FlightReportType=ReportType
end
function AI_ESCORT:GetFlightReportType()
return self.FlightReportType
end
function AI_ESCORT:_FlightSwitchReportTypeAll()
self:SetFlightReportType(self.__Enum.ReportType.All)
self:SetFlightMenuReportType()
local EscortGroup=self.EscortGroupSet:GetFirst()
EscortGroup:MessageTypeToGroup("Reporting all targets.",MESSAGE.Type.Information,self.PlayerGroup)
end
function AI_ESCORT:_FlightSwitchReportTypeAirborne()
self:SetFlightReportType(self.__Enum.ReportType.Airborne)
self:SetFlightMenuReportType()
local EscortGroup=self.EscortGroupSet:GetFirst()
EscortGroup:MessageTypeToGroup("Reporting airborne targets.",MESSAGE.Type.Information,self.PlayerGroup)
end
function AI_ESCORT:_FlightSwitchReportTypeGroundRadar()
self:SetFlightReportType(self.__Enum.ReportType.Ground)
self:SetFlightMenuReportType()
local EscortGroup=self.EscortGroupSet:GetFirst()
EscortGroup:MessageTypeToGroup("Reporting ground radar targets.",MESSAGE.Type.Information,self.PlayerGroup)
end
function AI_ESCORT:_FlightSwitchReportTypeGround()
self:SetFlightReportType(self.__Enum.ReportType.Ground)
self:SetFlightMenuReportType()
local EscortGroup=self.EscortGroupSet:GetFirst()
EscortGroup:MessageTypeToGroup("Reporting ground targets.",MESSAGE.Type.Information,self.PlayerGroup)
end
function AI_ESCORT:_ScanTargets(ScanDuration)
local EscortGroup=self.EscortGroup
local EscortUnit=self.PlayerUnit
self.FollowScheduler:Stop(self.FollowSchedule)
if EscortGroup:IsHelicopter()then
EscortGroup:PushTask(
EscortGroup:TaskControlled(
EscortGroup:TaskOrbitCircle(200,20),
EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil)
),1)
elseif EscortGroup:IsAirPlane()then
EscortGroup:PushTask(
EscortGroup:TaskControlled(
EscortGroup:TaskOrbitCircle(1000,500),
EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil)
),1)
end
EscortGroup:MessageToClient("Scanning targets for "..ScanDuration.." seconds.",ScanDuration,EscortUnit)
if self.EscortMode==AI_ESCORT.MODE.FOLLOW then
self.FollowScheduler:Start(self.FollowSchedule)
end
end
function AI_ESCORT.___Resume(EscortGroup,self)
self:F({self=self})
local PlayerGroup=self.PlayerGroup
EscortGroup:OptionROEHoldFire()
EscortGroup:OptionROTVertical()
EscortGroup:SetState(EscortGroup,"Mode",EscortGroup:GetState(EscortGroup,"PreviousMode"))
if EscortGroup:GetState(EscortGroup,"Mode")==self.__Enum.Mode.Mission then
EscortGroup:MessageTypeToGroup("Resuming route.",MESSAGE.Type.Information,PlayerGroup)
else
EscortGroup:MessageTypeToGroup("Rejoining formation.",MESSAGE.Type.Information,PlayerGroup)
end
end
function AI_ESCORT:_ResumeMission(EscortGroup,WayPoint)
self:SetFlightModeMission(EscortGroup)
local WayPoints=EscortGroup.MissionRoute
self:T(WayPoint,WayPoints)
for WayPointIgnore=1,WayPoint do
table.remove(WayPoints,1)
end
EscortGroup:SetTask(EscortGroup:TaskRoute(WayPoints),1)
EscortGroup:MessageTypeToGroup("Resuming mission from waypoint ",MESSAGE.Type.Information,self.PlayerGroup)
end
function AI_ESCORT:_AttackTarget(EscortGroup,DetectedItem)
self:F(EscortGroup)
self:SetFlightModeAttack(EscortGroup)
if EscortGroup:IsAir()then
EscortGroup:OptionROEOpenFire()
EscortGroup:OptionROTVertical()
EscortGroup:SetState(EscortGroup,"Escort",self)
local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem)
local Tasks={}
local AttackUnitTasks={}
DetectedSet:ForEachUnit(
function(DetectedUnit,Tasks)
if DetectedUnit:IsAlive()then
AttackUnitTasks[#AttackUnitTasks+1]=EscortGroup:TaskAttackUnit(DetectedUnit)
end
end,Tasks
)
Tasks[#Tasks+1]=EscortGroup:TaskCombo(AttackUnitTasks)
Tasks[#Tasks+1]=EscortGroup:TaskFunction("AI_ESCORT.___Resume",self)
EscortGroup:PushTask(
EscortGroup:TaskCombo(
Tasks
),1
)
else
local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem)
local Tasks={}
DetectedSet:ForEachUnit(
function(DetectedUnit,Tasks)
if DetectedUnit:IsAlive()then
Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50)
end
end,Tasks
)
EscortGroup:PushTask(
EscortGroup:TaskCombo(
Tasks
),1
)
end
local DetectedTargetsReport=REPORT:New("Engaging target:\n")
local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,self.PlayerGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName()))
local ReportSummary=DetectedItemReportSummary:Text(", ")
DetectedTargetsReport:AddIndent(ReportSummary,"-")
EscortGroup:MessageTypeToGroup(DetectedTargetsReport:Text(),MESSAGE.Type.Information,self.PlayerGroup)
end
function AI_ESCORT:_FlightAttackTarget(DetectedItem)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup,DetectedItem)
if EscortGroup:IsAir()then
self:_AttackTarget(EscortGroup,DetectedItem)
end
end,DetectedItem
)
end
function AI_ESCORT:_FlightAttackNearestTarget(TargetType)
self.Detection:Detect()
self:_FlightReportTargetsScheduler()
local EscortGroup=self.EscortGroupSet:GetFirst()
local AttackDetectedItem=nil
local DetectedItems=self.Detection:GetDetectedItems()
for DetectedItemIndex,DetectedItem in UTILS.spairs(DetectedItems,function(t,a,b)return self:Distance(self.PlayerUnit,t[a])<self:Distance(self.PlayerUnit,t[b])end)do
local DetectedItemSet=self.Detection:GetDetectedItemSet(DetectedItem)
local HasGround=DetectedItemSet:HasGroundUnits()>0
local HasAir=DetectedItemSet:HasAirUnits()>0
local FlightReportType=self:GetFlightReportType()
if(TargetType and TargetType==self.__Enum.ReportType.Ground and HasGround)or
(TargetType and TargetType==self.__Enum.ReportType.Air and HasAir)or
(TargetType==nil)then
AttackDetectedItem=DetectedItem
break
end
end
if AttackDetectedItem then
self:_FlightAttackTarget(AttackDetectedItem)
else
EscortGroup:MessageTypeToGroup("Nothing to attack!",MESSAGE.Type.Information,self.PlayerGroup)
end
end
function AI_ESCORT:_AssistTarget(EscortGroup,DetectedItem)
local EscortUnit=self.PlayerUnit
local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem)
local Tasks={}
DetectedSet:ForEachUnit(
function(DetectedUnit,Tasks)
if DetectedUnit:IsAlive()then
Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50)
end
end,Tasks
)
EscortGroup:SetTask(
EscortGroup:TaskCombo(
Tasks
),1
)
EscortGroup:MessageTypeToGroup("Assisting attack!",MESSAGE.Type.Information,EscortUnit:GetGroup())
end
function AI_ESCORT:_ROE(EscortGroup,EscortROEFunction,EscortROEMessage)
pcall(function()EscortROEFunction(EscortGroup)end)
EscortGroup:MessageTypeToGroup(EscortROEMessage,MESSAGE.Type.Information,self.PlayerGroup)
end
function AI_ESCORT:_FlightROEHoldFire(EscortROEMessage)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
self:_ROE(EscortGroup,EscortGroup.OptionROEHoldFire,EscortROEMessage)
end
)
end
function AI_ESCORT:_FlightROEOpenFire(EscortROEMessage)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
self:_ROE(EscortGroup,EscortGroup.OptionROEOpenFire,EscortROEMessage)
end
)
end
function AI_ESCORT:_FlightROEReturnFire(EscortROEMessage)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
self:_ROE(EscortGroup,EscortGroup.OptionROEReturnFire,EscortROEMessage)
end
)
end
function AI_ESCORT:_FlightROEWeaponFree(EscortROEMessage)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
self:_ROE(EscortGroup,EscortGroup.OptionROEWeaponFree,EscortROEMessage)
end
)
end
function AI_ESCORT:_ROT(EscortGroup,EscortROTFunction,EscortROTMessage)
pcall(function()EscortROTFunction(EscortGroup)end)
EscortGroup:MessageTypeToGroup(EscortROTMessage,MESSAGE.Type.Information,self.PlayerGroup)
end
function AI_ESCORT:_FlightROTNoReaction(EscortROTMessage)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
self:_ROT(EscortGroup,EscortGroup.OptionROTNoReaction,EscortROTMessage)
end
)
end
function AI_ESCORT:_FlightROTPassiveDefense(EscortROTMessage)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
self:_ROT(EscortGroup,EscortGroup.OptionROTPassiveDefense,EscortROTMessage)
end
)
end
function AI_ESCORT:_FlightROTEvadeFire(EscortROTMessage)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
self:_ROT(EscortGroup,EscortGroup.OptionROTEvadeFire,EscortROTMessage)
end
)
end
function AI_ESCORT:_FlightROTVertical(EscortROTMessage)
self.EscortGroupSet:ForEachGroupAlive(
function(EscortGroup)
self:_ROT(EscortGroup,EscortGroup.OptionROTVertical,EscortROTMessage)
end
)
end
function AI_ESCORT:RegisterRoute()
self:F()
local EscortGroup=self.EscortGroup
local TaskPoints=EscortGroup:GetTaskRoute()
self:T(TaskPoints)
return TaskPoints
end
function AI_ESCORT:_ResumeScheduler(EscortGroup)
self:F(EscortGroup:GetName())
if EscortGroup:IsAlive()and self.PlayerUnit:IsAlive()then
local EscortGroupName=EscortGroup:GetCallsign()
if EscortGroup.EscortMenuResumeMission then
EscortGroup.EscortMenuResumeMission:RemoveSubMenus()
local TaskPoints=EscortGroup.MissionRoute
for WayPointID,WayPoint in pairs(TaskPoints)do
local EscortVec3=EscortGroup:GetVec3()
local Distance=((WayPoint.x-EscortVec3.x)^2+
(WayPoint.y-EscortVec3.z)^2
)^0.5/1000
MENU_GROUP_COMMAND:New(self.PlayerGroup,"Waypoint "..WayPointID.." at "..string.format("%.2f",Distance).."km",EscortGroup.EscortMenuResumeMission,AI_ESCORT._ResumeMission,self,EscortGroup,WayPointID)
end
end
end
end
function AI_ESCORT:Distance(PlayerUnit,DetectedItem)
local DetectedCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem)
local PlayerCoordinate=PlayerUnit:GetCoordinate()
return DetectedCoordinate:Get3DDistance(PlayerCoordinate)
end
function AI_ESCORT:_ReportTargetsScheduler(EscortGroup,Report)
self:F(EscortGroup:GetName())
if EscortGroup:IsAlive()and self.PlayerUnit:IsAlive()then
local EscortGroupName=EscortGroup:GetCallsign()
local DetectedTargetsReport=REPORT:New("Reporting targets:\n")
if EscortGroup.EscortMenuTargetAssistance then
EscortGroup.EscortMenuTargetAssistance:RemoveSubMenus()
end
local DetectedItems=self.Detection:GetDetectedItems()
local ClientEscortTargets=self.Detection
local TimeUpdate=timer.getTime()
local EscortMenuAttackTargets=MENU_GROUP:New(self.PlayerGroup,"Attack targets",EscortGroup.EscortMenu)
local DetectedTargets=false
for DetectedItemIndex,DetectedItem in UTILS.spairs(DetectedItems,function(t,a,b)return self:Distance(self.PlayerUnit,t[a])<self:Distance(self.PlayerUnit,t[b])end)do
local DetectedItemSet=self.Detection:GetDetectedItemSet(DetectedItem)
local HasGround=DetectedItemSet:HasGroundUnits()>0
local HasGroundRadar=HasGround and DetectedItemSet:HasRadar()>0
local HasAir=DetectedItemSet:HasAirUnits()>0
local FlightReportType=self:GetFlightReportType()
if(FlightReportType==self.__Enum.ReportType.All)or
(FlightReportType==self.__Enum.ReportType.Airborne and HasAir)or
(FlightReportType==self.__Enum.ReportType.Ground and HasGround)or
(FlightReportType==self.__Enum.ReportType.GroundRadar and HasGroundRadar)then
DetectedTargets=true
local DetectedMenu=self.Detection:DetectedItemReportMenu(DetectedItem,EscortGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())):Text("\n")
local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,EscortGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName()))
local ReportSummary=DetectedItemReportSummary:Text(", ")
DetectedTargetsReport:AddIndent(ReportSummary,"-")
if EscortGroup:IsAir()then
MENU_GROUP_COMMAND:New(self.PlayerGroup,
DetectedMenu,
EscortMenuAttackTargets,
AI_ESCORT._AttackTarget,
self,
EscortGroup,
DetectedItem
):SetTag("Escort"):SetTime(TimeUpdate)
else
if self.EscortMenuTargetAssistance then
local MenuTargetAssistance=MENU_GROUP:New(self.PlayerGroup,EscortGroupName,EscortGroup.EscortMenuTargetAssistance)
MENU_GROUP_COMMAND:New(self.PlayerGroup,
DetectedMenu,
MenuTargetAssistance,
AI_ESCORT._AssistTarget,
self,
EscortGroup,
DetectedItem
)
end
end
end
end
EscortMenuAttackTargets:RemoveSubMenus(TimeUpdate,"Escort")
if Report then
if DetectedTargets then
EscortGroup:MessageTypeToGroup(DetectedTargetsReport:Text("\n"),MESSAGE.Type.Information,self.PlayerGroup)
else
EscortGroup:MessageTypeToGroup("No targets detected.",MESSAGE.Type.Information,self.PlayerGroup)
end
end
return true
end
return false
end
function AI_ESCORT:_FlightReportTargetsScheduler()
self:F("FlightReportTargetScheduler")
local EscortGroup=self.EscortGroupSet:GetFirst()
local DetectedTargetsReport=REPORT:New("Reporting your targets:\n")
if EscortGroup and(self.PlayerUnit:IsAlive()and EscortGroup:IsAlive())then
local TimeUpdate=timer.getTime()
local DetectedItems=self.Detection:GetDetectedItems()
local DetectedTargets=false
local ClientEscortTargets=self.Detection
for DetectedItemIndex,DetectedItem in UTILS.spairs(DetectedItems,function(t,a,b)return self:Distance(self.PlayerUnit,t[a])<self:Distance(self.PlayerUnit,t[b])end)do
self:F("FlightReportTargetScheduler Targets")
local DetectedItemSet=self.Detection:GetDetectedItemSet(DetectedItem)
local HasGround=DetectedItemSet:HasGroundUnits()>0
local HasGroundRadar=HasGround and DetectedItemSet:HasRadar()>0
local HasAir=DetectedItemSet:HasAirUnits()>0
local FlightReportType=self:GetFlightReportType()
if(FlightReportType==self.__Enum.ReportType.All)or
(FlightReportType==self.__Enum.ReportType.Airborne and HasAir)or
(FlightReportType==self.__Enum.ReportType.Ground and HasGround)or
(FlightReportType==self.__Enum.ReportType.GroundRadar and HasGroundRadar)then
DetectedTargets=true
local DetectedItemReportMenu=self.Detection:DetectedItemReportMenu(DetectedItem,self.PlayerGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName()))
local ReportMenuText=DetectedItemReportMenu:Text(", ")
MENU_GROUP_COMMAND:New(self.PlayerGroup,
ReportMenuText,
self.FlightMenuAttack,
AI_ESCORT._FlightAttackTarget,
self,
DetectedItem
):SetTag("Flight"):SetTime(TimeUpdate)
local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,self.PlayerGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName()))
local ReportSummary=DetectedItemReportSummary:Text(", ")
DetectedTargetsReport:AddIndent(ReportSummary,"-")
end
end
self.FlightMenuAttack:RemoveSubMenus(TimeUpdate,"Flight")
if DetectedTargets then
EscortGroup:MessageTypeToGroup(DetectedTargetsReport:Text("\n"),MESSAGE.Type.Information,self.PlayerGroup)
end
return true
end
return false
end
AI_ESCORT_REQUEST={
ClassName="AI_ESCORT_REQUEST",
}
function AI_ESCORT_REQUEST:New(EscortUnit,EscortSpawn,EscortAirbase,EscortName,EscortBriefing)
local EscortGroupSet=SET_GROUP:New():FilterDeads():FilterCrashes()
local self=BASE:Inherit(self,AI_ESCORT:New(EscortUnit,EscortGroupSet,EscortName,EscortBriefing))
self.EscortGroupSet=EscortGroupSet
self.EscortSpawn=EscortSpawn
self.EscortAirbase=EscortAirbase
self.LeaderGroup=self.PlayerUnit:GetGroup()
self.Detection=DETECTION_AREAS:New(self.EscortGroupSet,5000)
self.Detection:__Start(30)
self.SpawnMode=self.__Enum.Mode.Mission
return self
end
function AI_ESCORT_REQUEST:SpawnEscort()
local EscortGroup=self.EscortSpawn:SpawnAtAirbase(self.EscortAirbase,SPAWN.Takeoff.Hot)
self:ScheduleOnce(0.1,
function(EscortGroup)
EscortGroup:OptionROTVertical()
EscortGroup:OptionROEHoldFire()
self.EscortGroupSet:AddGroup(EscortGroup)
local LeaderEscort=self.EscortGroupSet:GetFirst()
local Report=REPORT:New()
Report:Add("Joining Up "..self.EscortGroupSet:GetUnitTypeNames():Text(", ").." from "..LeaderEscort:GetCoordinate():ToString(self.EscortUnit))
LeaderEscort:MessageTypeToGroup(Report:Text(),MESSAGE.Type.Information,self.PlayerUnit)
self:SetFlightModeFormation(EscortGroup)
self:FormationTrail()
self:_InitFlightMenus()
self:_InitEscortMenus(EscortGroup)
self:_InitEscortRoute(EscortGroup)
function EscortGroup:OnEventDeadOrCrash(EventData)
self:F({"EventDead",EventData})
self.EscortMenu:Remove()
end
EscortGroup:HandleEvent(EVENTS.Dead,EscortGroup.OnEventDeadOrCrash)
EscortGroup:HandleEvent(EVENTS.Crash,EscortGroup.OnEventDeadOrCrash)
end,EscortGroup
)
end
function AI_ESCORT_REQUEST:onafterStart(EscortGroupSet)
self:F()
if not self.MenuRequestEscort then
self.MainMenu=MENU_GROUP:New(self.PlayerGroup,self.EscortName)
self.MenuRequestEscort=MENU_GROUP_COMMAND:New(self.LeaderGroup,"Request new escort ",self.MainMenu,
function()
self:SpawnEscort()
end
)
end
self:GetParent(self).onafterStart(self,EscortGroupSet)
self:HandleEvent(EVENTS.Dead,self.OnEventDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self.OnEventDeadOrCrash)
end
function AI_ESCORT_REQUEST:onafterStop(EscortGroupSet)
self:F()
EscortGroupSet:ForEachGroup(
function(EscortGroup)
EscortGroup:WayPointInitialize()
EscortGroup:OptionROTVertical()
EscortGroup:OptionROEOpenFire()
end
)
self.Detection:Stop()
self.MainMenu:Remove()
end
function AI_ESCORT_REQUEST:SetEscortSpawnMission()
self.SpawnMode=self.__Enum.Mode.Mission
end
AI_ESCORT_DISPATCHER={
ClassName="AI_ESCORT_DISPATCHER",
}
AI_ESCORT_DISPATCHER.AI_Escorts={}
function AI_ESCORT_DISPATCHER:New(CarrierSet,EscortSpawn,EscortAirbase,EscortName,EscortBriefing)
local self=BASE:Inherit(self,FSM:New())
self.CarrierSet=CarrierSet
self.EscortSpawn=EscortSpawn
self.EscortAirbase=EscortAirbase
self.EscortName=EscortName
self.EscortBriefing=EscortBriefing
self:SetStartState("Idle")
self:AddTransition("Monitoring","Monitor","Monitoring")
self:AddTransition("Idle","Start","Monitoring")
self:AddTransition("Monitoring","Stop","Idle")
function self.CarrierSet.OnAfterRemoved(CarrierSet,From,Event,To,CarrierName,Carrier)
self:F({Carrier=Carrier:GetName()})
end
return self
end
function AI_ESCORT_DISPATCHER:onafterStart(From,Event,To)
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventExit)
self:HandleEvent(EVENTS.Crash,self.OnEventExit)
self:HandleEvent(EVENTS.Dead,self.OnEventExit)
end
function AI_ESCORT_DISPATCHER:OnEventExit(EventData)
local PlayerGroupName=EventData.IniGroupName
local PlayerGroup=EventData.IniGroup
local PlayerUnit=EventData.IniUnit
self:I({EscortAirbase=self.EscortAirbase})
self:I({PlayerGroupName=PlayerGroupName})
self:I({PlayerGroup=PlayerGroup})
self:I({FirstGroup=self.CarrierSet:GetFirst()})
self:I({FindGroup=self.CarrierSet:FindGroup(PlayerGroupName)})
if self.CarrierSet:FindGroup(PlayerGroupName)then
if self.AI_Escorts[PlayerGroupName]then
self.AI_Escorts[PlayerGroupName]:Stop()
self.AI_Escorts[PlayerGroupName]=nil
end
end
end
function AI_ESCORT_DISPATCHER:OnEventBirth(EventData)
local PlayerGroupName=EventData.IniGroupName
local PlayerGroup=EventData.IniGroup
local PlayerUnit=EventData.IniUnit
self:I({EscortAirbase=self.EscortAirbase})
self:I({PlayerGroupName=PlayerGroupName})
self:I({PlayerGroup=PlayerGroup})
self:I({FirstGroup=self.CarrierSet:GetFirst()})
self:I({FindGroup=self.CarrierSet:FindGroup(PlayerGroupName)})
if self.CarrierSet:FindGroup(PlayerGroupName)then
if not self.AI_Escorts[PlayerGroupName]then
local LeaderUnit=PlayerUnit
local EscortGroup=self.EscortSpawn:SpawnAtAirbase(self.EscortAirbase,SPAWN.Takeoff.Hot)
self:I({EscortGroup=EscortGroup})
self:ScheduleOnce(1,
function(EscortGroup)
local EscortSet=SET_GROUP:New()
EscortSet:AddGroup(EscortGroup)
self.AI_Escorts[PlayerGroupName]=AI_ESCORT:New(LeaderUnit,EscortSet,self.EscortName,self.EscortBriefing)
self.AI_Escorts[PlayerGroupName]:FormationTrail(0,100,0)
if EscortGroup:IsHelicopter()then
self.AI_Escorts[PlayerGroupName]:MenusHelicopters()
else
self.AI_Escorts[PlayerGroupName]:MenusAirplanes()
end
self.AI_Escorts[PlayerGroupName]:__Start(0.1)
end,EscortGroup
)
end
end
end
AI_ESCORT_DISPATCHER_REQUEST={
ClassName="AI_ESCORT_DISPATCHER_REQUEST",
}
AI_ESCORT_DISPATCHER_REQUEST.AI_Escorts={}
function AI_ESCORT_DISPATCHER_REQUEST:New(CarrierSet,EscortSpawn,EscortAirbase,EscortName,EscortBriefing)
local self=BASE:Inherit(self,FSM:New())
self.CarrierSet=CarrierSet
self.EscortSpawn=EscortSpawn
self.EscortAirbase=EscortAirbase
self.EscortName=EscortName
self.EscortBriefing=EscortBriefing
self:SetStartState("Idle")
self:AddTransition("Monitoring","Monitor","Monitoring")
self:AddTransition("Idle","Start","Monitoring")
self:AddTransition("Monitoring","Stop","Idle")
function self.CarrierSet.OnAfterRemoved(CarrierSet,From,Event,To,CarrierName,Carrier)
self:F({Carrier=Carrier:GetName()})
end
return self
end
function AI_ESCORT_DISPATCHER_REQUEST:onafterStart(From,Event,To)
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventExit)
self:HandleEvent(EVENTS.Crash,self.OnEventExit)
self:HandleEvent(EVENTS.Dead,self.OnEventExit)
end
function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit(EventData)
local PlayerGroupName=EventData.IniGroupName
local PlayerGroup=EventData.IniGroup
local PlayerUnit=EventData.IniUnit
if self.CarrierSet:FindGroup(PlayerGroupName)then
if self.AI_Escorts[PlayerGroupName]then
self.AI_Escorts[PlayerGroupName]:Stop()
self.AI_Escorts[PlayerGroupName]=nil
end
end
end
function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth(EventData)
local PlayerGroupName=EventData.IniGroupName
local PlayerGroup=EventData.IniGroup
local PlayerUnit=EventData.IniUnit
if self.CarrierSet:FindGroup(PlayerGroupName)then
if not self.AI_Escorts[PlayerGroupName]then
local LeaderUnit=PlayerUnit
self:ScheduleOnce(0.1,
function()
self.AI_Escorts[PlayerGroupName]=AI_ESCORT_REQUEST:New(LeaderUnit,self.EscortSpawn,self.EscortAirbase,self.EscortName,self.EscortBriefing)
self.AI_Escorts[PlayerGroupName]:FormationTrail(0,100,0)
if PlayerGroup:IsHelicopter()then
self.AI_Escorts[PlayerGroupName]:MenusHelicopters()
else
self.AI_Escorts[PlayerGroupName]:MenusAirplanes()
end
self.AI_Escorts[PlayerGroupName]:__Start(0.1)
end
)
end
end
end
AI_CARGO={
ClassName="AI_CARGO",
Coordinate=nil,
Carrier_Cargo={},
}
function AI_CARGO:New(Carrier,CargoSet)
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New(Carrier))
self.CargoSet=CargoSet
self.CargoCarrier=Carrier
self:SetStartState("Unloaded")
self:AddTransition("Unloaded","Pickup","Unloaded")
self:AddTransition("*","Load","*")
self:AddTransition("*","Reload","*")
self:AddTransition("*","Board","*")
self:AddTransition("*","Loaded","Loaded")
self:AddTransition("Loaded","PickedUp","Loaded")
self:AddTransition("Loaded","Deploy","*")
self:AddTransition("*","Unload","*")
self:AddTransition("*","Unboard","*")
self:AddTransition("*","Unloaded","Unloaded")
self:AddTransition("Unloaded","Deployed","Unloaded")
for _,CarrierUnit in pairs(Carrier:GetUnits())do
local CarrierUnit=CarrierUnit
CarrierUnit:SetCargoBayWeightLimit()
end
self.Transporting=false
self.Relocating=false
return self
end
function AI_CARGO:IsTransporting()
return self.Transporting==true
end
function AI_CARGO:IsRelocating()
return self.Relocating==true
end
function AI_CARGO:onafterPickup(APC,From,Event,To,Coordinate,Speed,Height,PickupZone)
self.Transporting=false
self.Relocating=true
end
function AI_CARGO:onafterDeploy(APC,From,Event,To,Coordinate,Speed,Height,DeployZone)
self.Relocating=false
self.Transporting=true
end
function AI_CARGO:onbeforeLoad(Carrier,From,Event,To,PickupZone)
self:F({Carrier,From,Event,To})
local Boarding=false
local LoadInterval=2
local LoadDelay=1
local Carrier_List={}
local Carrier_Weight={}
if Carrier and Carrier:IsAlive()then
self.Carrier_Cargo={}
for _,CarrierUnit in pairs(Carrier:GetUnits())do
local CarrierUnit=CarrierUnit
local CargoBayFreeWeight=CarrierUnit:GetCargoBayFreeWeight()
self:F({CargoBayFreeWeight=CargoBayFreeWeight})
Carrier_List[#Carrier_List+1]=CarrierUnit
Carrier_Weight[CarrierUnit]=CargoBayFreeWeight
end
local Carrier_Count=#Carrier_List
local Carrier_Index=1
local Loaded=false
for _,Cargo in UTILS.spairs(self.CargoSet:GetSet(),function(t,a,b)return t[a]:GetWeight()>t[b]:GetWeight()end)do
local Cargo=Cargo
self:F({IsUnLoaded=Cargo:IsUnLoaded(),IsDeployed=Cargo:IsDeployed(),Cargo:GetName(),Carrier:GetName()})
for Carrier_Loop=1,#Carrier_List do
local CarrierUnit=Carrier_List[Carrier_Index]
Carrier_Index=Carrier_Index+1
if Carrier_Index>Carrier_Count then
Carrier_Index=1
end
if Cargo:IsUnLoaded()and not Cargo:IsDeployed()then
if Cargo:IsInLoadRadius(CarrierUnit:GetCoordinate())then
self:F({"In radius",CarrierUnit:GetName()})
local CargoWeight=Cargo:GetWeight()
local CarrierSpace=Carrier_Weight[CarrierUnit]
if CarrierSpace>CargoWeight then
Carrier:RouteStop()
Cargo:__Board(-LoadDelay,CarrierUnit)
self:__Board(LoadDelay,Cargo,CarrierUnit,PickupZone)
LoadDelay=LoadDelay+Cargo:GetCount()*LoadInterval
self.Carrier_Cargo[Cargo]=CarrierUnit
Boarding=true
Carrier_Weight[CarrierUnit]=Carrier_Weight[CarrierUnit]-CargoWeight
Loaded=true
break
else
self:T(string.format("WARNING: Cargo too heavy for carrier %s. Cargo=%.1f > %.1f free space",tostring(CarrierUnit:GetName()),CargoWeight,CarrierSpace))
end
end
end
end
end
if not Loaded==true then
self.Relocating=false
end
end
return Boarding
end
function AI_CARGO:onbeforeReload(Carrier,From,Event,To)
self:F({Carrier,From,Event,To})
local Boarding=false
local LoadInterval=2
local LoadDelay=1
local Carrier_List={}
local Carrier_Weight={}
if Carrier and Carrier:IsAlive()then
for _,CarrierUnit in pairs(Carrier:GetUnits())do
local CarrierUnit=CarrierUnit
Carrier_List[#Carrier_List+1]=CarrierUnit
end
local Carrier_Count=#Carrier_List
local Carrier_Index=1
local Loaded=false
for Cargo,CarrierUnit in pairs(self.Carrier_Cargo)do
local Cargo=Cargo
self:F({IsUnLoaded=Cargo:IsUnLoaded(),IsDeployed=Cargo:IsDeployed(),Cargo:GetName(),Carrier:GetName()})
for Carrier_Loop=1,#Carrier_List do
local CarrierUnit=Carrier_List[Carrier_Index]
Carrier_Index=Carrier_Index+1
if Carrier_Index>Carrier_Count then
Carrier_Index=1
end
if Cargo:IsUnLoaded()and not Cargo:IsDeployed()then
Carrier:RouteStop()
Cargo:__Board(-LoadDelay,CarrierUnit)
self:__Board(LoadDelay,Cargo,CarrierUnit)
LoadDelay=LoadDelay+Cargo:GetCount()*LoadInterval
self.Carrier_Cargo[Cargo]=CarrierUnit
Boarding=true
Loaded=true
end
end
end
if not Loaded==true then
self.Relocating=false
end
end
return Boarding
end
function AI_CARGO:onafterBoard(Carrier,From,Event,To,Cargo,CarrierUnit,PickupZone)
self:F({Carrier,From,Event,To,Cargo,CarrierUnit:GetName()})
if Carrier and Carrier:IsAlive()then
self:F({IsLoaded=Cargo:IsLoaded(),Cargo:GetName(),Carrier:GetName()})
if not Cargo:IsLoaded()and not Cargo:IsDestroyed()then
self:__Board(-10,Cargo,CarrierUnit,PickupZone)
return
end
end
self:__Loaded(0.1,Cargo,CarrierUnit,PickupZone)
end
function AI_CARGO:onafterLoaded(Carrier,From,Event,To,Cargo,PickupZone)
self:F({Carrier,From,Event,To})
local Loaded=true
if Carrier and Carrier:IsAlive()then
for Cargo,CarrierUnit in pairs(self.Carrier_Cargo)do
local Cargo=Cargo
self:F({IsLoaded=Cargo:IsLoaded(),IsDestroyed=Cargo:IsDestroyed(),Cargo:GetName(),Carrier:GetName()})
if not Cargo:IsLoaded()and not Cargo:IsDestroyed()then
Loaded=false
end
end
end
if Loaded then
self:__PickedUp(0.1,PickupZone)
end
end
function AI_CARGO:onafterPickedUp(Carrier,From,Event,To,PickupZone)
self:F({Carrier,From,Event,To})
Carrier:RouteResume()
local HasCargo=false
if Carrier and Carrier:IsAlive()then
for Cargo,CarrierUnit in pairs(self.Carrier_Cargo)do
HasCargo=true
break
end
end
self.Relocating=false
if HasCargo then
self:F("Transporting")
self.Transporting=true
end
end
function AI_CARGO:onafterUnload(Carrier,From,Event,To,DeployZone,Defend)
self:F({Carrier,From,Event,To,DeployZone,Defend=Defend})
local UnboardInterval=5
local UnboardDelay=5
if Carrier and Carrier:IsAlive()then
for _,CarrierUnit in pairs(Carrier:GetUnits())do
local CarrierUnit=CarrierUnit
Carrier:RouteStop()
for _,Cargo in pairs(CarrierUnit:GetCargo())do
self:F({Cargo=Cargo:GetName(),Isloaded=Cargo:IsLoaded()})
if Cargo:IsLoaded()then
Cargo:__UnBoard(UnboardDelay)
UnboardDelay=UnboardDelay+Cargo:GetCount()*UnboardInterval
self:__Unboard(UnboardDelay,Cargo,CarrierUnit,DeployZone,Defend)
if not Defend==true then
Cargo:SetDeployed(true)
end
end
end
end
end
end
function AI_CARGO:onafterUnboard(Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend)
self:F({Carrier,From,Event,To,Cargo:GetName(),DeployZone=DeployZone,Defend=Defend})
if Carrier and Carrier:IsAlive()then
if not Cargo:IsUnLoaded()then
self:__Unboard(10,Cargo,CarrierUnit,DeployZone,Defend)
return
end
end
self:Unloaded(Cargo,CarrierUnit,DeployZone,Defend)
end
function AI_CARGO:onafterUnloaded(Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend)
self:F({Carrier,From,Event,To,Cargo:GetName(),DeployZone=DeployZone,Defend=Defend})
local AllUnloaded=true
if Carrier and Carrier:IsAlive()then
for _,CarrierUnit in pairs(Carrier:GetUnits())do
local CarrierUnit=CarrierUnit
local IsEmpty=CarrierUnit:IsCargoEmpty()
self:I({IsEmpty=IsEmpty})
if not IsEmpty then
AllUnloaded=false
break
end
end
if AllUnloaded==true then
if DeployZone==true then
self.Carrier_Cargo={}
end
self.CargoCarrier=Carrier
end
end
if AllUnloaded==true then
self:__Deployed(5,DeployZone,Defend)
end
end
function AI_CARGO:onafterDeployed(Carrier,From,Event,To,DeployZone,Defend)
self:F({Carrier,From,Event,To,DeployZone=DeployZone,Defend=Defend})
if not Defend==true then
self.Transporting=false
else
self:F("Defending")
end
end
AI_CARGO_APC={
ClassName="AI_CARGO_APC",
Coordinate=nil,
}
function AI_CARGO_APC:New(APC,CargoSet,CombatRadius)
local self=BASE:Inherit(self,AI_CARGO:New(APC,CargoSet))
self:AddTransition("*","Monitor","*")
self:AddTransition("*","Follow","Following")
self:AddTransition("*","Guard","Unloaded")
self:AddTransition("*","Home","*")
self:AddTransition("*","Reload","Boarding")
self:AddTransition("*","Deployed","*")
self:AddTransition("*","PickedUp","*")
self:AddTransition("*","Destroyed","Destroyed")
self:SetCombatRadius(CombatRadius)
self:SetCarrier(APC)
return self
end
function AI_CARGO_APC:SetCarrier(CargoCarrier)
self.CargoCarrier=CargoCarrier
self.CargoCarrier:SetState(self.CargoCarrier,"AI_CARGO_APC",self)
CargoCarrier:HandleEvent(EVENTS.Dead)
function CargoCarrier:OnEventDead(EventData)
self:F({"dead"})
local AICargoTroops=self:GetState(self,"AI_CARGO_APC")
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is("Loaded")then
AICargoTroops:Destroyed()
end
end
end
self.Zone=ZONE_UNIT:New(self.CargoCarrier:GetName().."-Zone",self.CargoCarrier,self.CombatRadius)
self.Coalition=self.CargoCarrier:GetCoalition()
self:SetControllable(CargoCarrier)
self:Guard()
return self
end
function AI_CARGO_APC:SetOffRoad(Offroad,Formation)
self:SetPickupOffRoad(Offroad,Formation)
self:SetDeployOffRoad(Offroad,Formation)
return self
end
function AI_CARGO_APC:SetPickupOffRoad(Offroad,Formation)
self.pickupOffroad=Offroad
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
return self
end
function AI_CARGO_APC:SetDeployOffRoad(Offroad,Formation)
self.deployOffroad=Offroad
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
return self
end
function AI_CARGO_APC:FindCarrier(Coordinate,Radius)
local CoordinateZone=ZONE_RADIUS:New("Zone",Coordinate:GetVec2(),Radius)
CoordinateZone:Scan({Object.Category.UNIT})
for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do
local NearUnit=UNIT:Find(DCSUnit)
self:F({NearUnit=NearUnit})
if not NearUnit:GetState(NearUnit,"AI_CARGO_APC")then
local Attributes=NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttribute("Trucks")then
return NearUnit:GetGroup()
end
end
end
return nil
end
function AI_CARGO_APC:SetCombatRadius(CombatRadius)
self.CombatRadius=CombatRadius or 0
if self.CombatRadius>0 then
self:__Monitor(-5)
end
return self
end
function AI_CARGO_APC:FollowToCarrier(Me,APCUnit,CargoGroup)
local InfantryGroup=CargoGroup:GetGroup()
self:F({self=self:GetClassNameAndID(),InfantryGroup=InfantryGroup:GetName()})
if APCUnit:IsAlive()then
if InfantryGroup:IsPartlyInZone(ZONE_UNIT:New("Radius",APCUnit,25))then
Me:Guard()
else
self:F({InfantryGroup=InfantryGroup:GetName()})
if InfantryGroup:IsAlive()then
self:F({InfantryGroup=InfantryGroup:GetName()})
local Waypoints={}
local FromCoord=InfantryGroup:GetCoordinate()
local FromGround=FromCoord:WaypointGround(10,"Diamond")
self:F({FromGround=FromGround})
table.insert(Waypoints,FromGround)
local ToCoord=APCUnit:GetCoordinate():GetRandomCoordinateInRadius(10,5)
local ToGround=ToCoord:WaypointGround(10,"Diamond")
self:F({ToGround=ToGround})
table.insert(Waypoints,ToGround)
local TaskRoute=InfantryGroup:TaskFunction("AI_CARGO_APC.FollowToCarrier",Me,APCUnit,CargoGroup)
self:F({Waypoints=Waypoints})
local Waypoint=Waypoints[#Waypoints]
InfantryGroup:SetTaskWaypoint(Waypoint,TaskRoute)
InfantryGroup:Route(Waypoints,1)
end
end
end
end
function AI_CARGO_APC:onafterMonitor(APC,From,Event,To)
self:F({APC,From,Event,To,IsTransporting=self:IsTransporting()})
if self.CombatRadius>0 then
if APC and APC:IsAlive()then
if self.CarrierCoordinate then
if self:IsTransporting()==true then
local Coordinate=APC:GetCoordinate()
if self:Is("Unloaded")or self:Is("Loaded")then
self.Zone:Scan({Object.Category.UNIT})
if self.Zone:IsAllInZoneOfCoalition(self.Coalition)then
if self:Is("Unloaded")then
self:Reload()
end
else
if self:Is("Loaded")then
self:__Unload(1,nil,true)
else
if self:Is("Unloaded")then
end
self:F("I am here"..self:GetCurrentState())
if self:Is("Following")then
for Cargo,APCUnit in pairs(self.Carrier_Cargo)do
local Cargo=Cargo
local APCUnit=APCUnit
if Cargo:IsAlive()then
if not Cargo:IsNear(APCUnit,40)then
APCUnit:RouteStop()
self.CarrierStopped=true
else
if self.CarrierStopped then
if Cargo:IsNear(APCUnit,25)then
APCUnit:RouteResume()
self.CarrierStopped=nil
end
end
end
end
end
end
end
end
end
end
end
self.CarrierCoordinate=APC:GetCoordinate()
end
self:__Monitor(-5)
end
end
function AI_CARGO_APC:onafterFollow(APC,From,Event,To)
self:F({APC,From,Event,To})
self:F("Follow")
if APC and APC:IsAlive()then
for Cargo,APCUnit in pairs(self.Carrier_Cargo)do
local Cargo=Cargo
if Cargo:IsUnLoaded()then
self:FollowToCarrier(self,APCUnit,Cargo)
APCUnit:RouteResume()
end
end
end
end
function AI_CARGO_APC._Pickup(APC,self,Coordinate,Speed,PickupZone)
APC:F({"AI_CARGO_APC._Pickup:",APC:GetName()})
if APC:IsAlive()then
self:Load(PickupZone)
end
end
function AI_CARGO_APC._Deploy(APC,self,Coordinate,DeployZone)
APC:F({"AI_CARGO_APC._Deploy:",APC})
if APC:IsAlive()then
self:Unload(DeployZone)
end
end
function AI_CARGO_APC:onafterPickup(APC,From,Event,To,Coordinate,Speed,Height,PickupZone)
if APC and APC:IsAlive()then
if Coordinate then
self.RoutePickup=true
local _speed=Speed or APC:GetSpeedMax()*0.5
local Waypoints={}
if self.pickupOffroad then
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed,self.pickupFormation)
Waypoints[2]=Coordinate:WaypointGround(_speed,self.pickupFormation,DCSTasks)
else
Waypoints=APC:TaskGroundOnRoad(Coordinate,_speed,ENUMS.Formation.Vehicle.OffRoad,true)
end
local TaskFunction=APC:TaskFunction("AI_CARGO_APC._Pickup",self,Coordinate,Speed,PickupZone)
local Waypoint=Waypoints[#Waypoints]
APC:SetTaskWaypoint(Waypoint,TaskFunction)
APC:Route(Waypoints,1)
else
AI_CARGO_APC._Pickup(APC,self,Coordinate,Speed,PickupZone)
end
self:GetParent(self,AI_CARGO_APC).onafterPickup(self,APC,From,Event,To,Coordinate,Speed,Height,PickupZone)
end
end
function AI_CARGO_APC:onafterDeploy(APC,From,Event,To,Coordinate,Speed,Height,DeployZone)
if APC and APC:IsAlive()then
self.RouteDeploy=true
local speedmax=APC:GetSpeedMax()
local _speed=Speed or speedmax*0.5
_speed=math.min(_speed,speedmax)
local Waypoints={}
if self.deployOffroad then
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed,self.deployFormation)
Waypoints[2]=Coordinate:WaypointGround(_speed,self.deployFormation,DCSTasks)
else
Waypoints=APC:TaskGroundOnRoad(Coordinate,_speed,ENUMS.Formation.Vehicle.OffRoad,true)
end
local TaskFunction=APC:TaskFunction("AI_CARGO_APC._Deploy",self,Coordinate,DeployZone)
local Waypoint=Waypoints[#Waypoints]
APC:SetTaskWaypoint(Waypoint,TaskFunction)
APC:Route(Waypoints,1)
self:GetParent(self,AI_CARGO_APC).onafterDeploy(self,APC,From,Event,To,Coordinate,Speed,Height,DeployZone)
end
end
function AI_CARGO_APC:onafterUnloaded(Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend)
self:F({Carrier,From,Event,To,DeployZone=DeployZone,Defend=Defend})
self:GetParent(self,AI_CARGO_APC).onafterUnloaded(self,Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend)
if Defend==true then
self.Zone:Scan({Object.Category.UNIT})
if not self.Zone:IsAllInZoneOfCoalition(self.Coalition)then
local AttackUnits=self.Zone:GetScannedUnits()
local Move={}
local CargoGroup=Cargo.CargoObject
Move[#Move+1]=CargoGroup:GetCoordinate():WaypointGround(70,"Custom")
for UnitId,AttackUnit in pairs(AttackUnits)do
local MooseUnit=UNIT:Find(AttackUnit)
if MooseUnit:GetCoalition()~=CargoGroup:GetCoalition()then
Move[#Move+1]=MooseUnit:GetCoordinate():WaypointGround(70,"Line abreast")
self:F({MooseUnit=MooseUnit:GetName(),CargoGroup=CargoGroup:GetName()})
end
end
CargoGroup:RoutePush(Move,0.1)
end
end
end
function AI_CARGO_APC:onafterDeployed(APC,From,Event,To,DeployZone,Defend)
self:F({APC,From,Event,To,DeployZone=DeployZone,Defend=Defend})
self:__Guard(0.1)
self:GetParent(self,AI_CARGO_APC).onafterDeployed(self,APC,From,Event,To,DeployZone,Defend)
end
function AI_CARGO_APC:onafterHome(APC,From,Event,To,Coordinate,Speed,Height,HomeZone)
if APC and APC:IsAlive()~=nil then
self.RouteHome=true
Speed=Speed or APC:GetSpeedMax()*0.5
local Waypoints=APC:TaskGroundOnRoad(Coordinate,Speed,"Line abreast",true)
self:F({Waypoints=Waypoints})
local Waypoint=Waypoints[#Waypoints]
APC:Route(Waypoints,1)
end
end
AI_CARGO_HELICOPTER={
ClassName="AI_CARGO_HELICOPTER",
Coordinate=nil,
}
AI_CARGO_QUEUE={}
function AI_CARGO_HELICOPTER:New(Helicopter,CargoSet)
local self=BASE:Inherit(self,AI_CARGO:New(Helicopter,CargoSet))
self.Zone=ZONE_GROUP:New(Helicopter:GetName(),Helicopter,300)
self:SetStartState("Unloaded")
self:AddTransition("Unloaded","Pickup","Unloaded")
self:AddTransition("*","Landed","*")
self:AddTransition("*","Load","*")
self:AddTransition("*","Loaded","Loaded")
self:AddTransition("Loaded","PickedUp","Loaded")
self:AddTransition("Loaded","Deploy","*")
self:AddTransition("*","Queue","*")
self:AddTransition("*","Orbit","*")
self:AddTransition("*","Destroyed","*")
self:AddTransition("*","Unload","*")
self:AddTransition("*","Unloaded","Unloaded")
self:AddTransition("Unloaded","Deployed","Unloaded")
self:AddTransition("*","Home","*")
Helicopter:HandleEvent(EVENTS.Crash,
function(Helicopter,EventData)
AI_CARGO_QUEUE[Helicopter]=nil
end
)
Helicopter:HandleEvent(EVENTS.Land,
function(Helicopter,EventData)
self:ScheduleOnce(60,
function(Helicopter)
AI_CARGO_QUEUE[Helicopter]=nil
end,Helicopter
)
end
)
self:SetCarrier(Helicopter)
self.landingspeed=15
self.landingheight=5.5
return self
end
function AI_CARGO_HELICOPTER:SetCarrier(Helicopter)
local AICargo=self
self.Helicopter=Helicopter
self.Helicopter:SetState(self.Helicopter,"AI_CARGO_HELICOPTER",self)
self.RoutePickup=false
self.RouteDeploy=false
Helicopter:HandleEvent(EVENTS.Dead)
Helicopter:HandleEvent(EVENTS.Hit)
Helicopter:HandleEvent(EVENTS.Land)
function Helicopter:OnEventDead(EventData)
local AICargoTroops=self:GetState(self,"AI_CARGO_HELICOPTER")
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is("Loaded")then
AICargoTroops:Destroyed()
end
end
end
function Helicopter:OnEventLand(EventData)
AICargo:Landed()
end
self.Coalition=self.Helicopter:GetCoalition()
self:SetControllable(Helicopter)
return self
end
function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed,height)
local _speed=speed or 15
local _height=height or 5.5
self.landingheight=_height
self.landingspeed=_speed
return self
end
function AI_CARGO_HELICOPTER:onafterLanded(Helicopter,From,Event,To)
self:F({From,Event,To})
Helicopter:F({Name=Helicopter:GetName()})
if Helicopter and Helicopter:IsAlive()then
self:T({Helicopter:GetName(),Height=Helicopter:GetHeight(true),Velocity=Helicopter:GetVelocityKMH()})
if self.RoutePickup==true then
if Helicopter:GetHeight(true)<=self.landingheight then
self:Load(self.PickupZone)
self.RoutePickup=false
end
end
if self.RouteDeploy==true then
if Helicopter:GetHeight(true)<=self.landingheight then
self:Unload(self.DeployZone)
self.RouteDeploy=false
end
end
end
end
function AI_CARGO_HELICOPTER:onafterQueue(Helicopter,From,Event,To,Coordinate,Speed,DeployZone)
self:F({From,Event,To,Coordinate,Speed,DeployZone})
local HelicopterInZone=false
if Helicopter and Helicopter:IsAlive()==true then
local Distance=Coordinate:DistanceFromPointVec2(Helicopter:GetCoordinate())
if Distance>2000 then
self:__Queue(-10,Coordinate,Speed,DeployZone)
else
local ZoneFree=true
for Helicopter,ZoneQueue in pairs(AI_CARGO_QUEUE)do
local ZoneQueue=ZoneQueue
if ZoneQueue:IsCoordinateInZone(Coordinate)then
ZoneFree=false
end
end
self:F({ZoneFree=ZoneFree})
if ZoneFree==true then
local ZoneQueue=ZONE_RADIUS:New(Helicopter:GetName(),Coordinate:GetVec2(),100)
AI_CARGO_QUEUE[Helicopter]=ZoneQueue
local Route={}
local CoordinateTo=Coordinate
local landheight=CoordinateTo:GetLandHeight()
CoordinateTo.y=landheight+50
local WaypointTo=CoordinateTo:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
50,
true
)
Route[#Route+1]=WaypointTo
local Tasks={}
Tasks[#Tasks+1]=Helicopter:TaskLandAtVec2(CoordinateTo:GetVec2())
Route[#Route].task=Helicopter:TaskCombo(Tasks)
Route[#Route+1]=WaypointTo
Helicopter:Route(Route,0)
self.DeployZone=DeployZone
else
self:__Queue(-10,Coordinate,Speed,DeployZone)
end
end
else
AI_CARGO_QUEUE[Helicopter]=nil
end
end
function AI_CARGO_HELICOPTER:onafterOrbit(Helicopter,From,Event,To,Coordinate)
self:F({From,Event,To,Coordinate})
if Helicopter and Helicopter:IsAlive()then
local Route={}
local CoordinateTo=Coordinate
local landheight=CoordinateTo:GetLandHeight()
CoordinateTo.y=landheight+50
local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,50,true)
Route[#Route+1]=WaypointTo
local Tasks={}
Tasks[#Tasks+1]=Helicopter:TaskOrbitCircle(math.random(30,80),150,CoordinateTo:GetRandomCoordinateInRadius(800,500))
Route[#Route].task=Helicopter:TaskCombo(Tasks)
Route[#Route+1]=WaypointTo
Helicopter:Route(Route,0)
end
end
function AI_CARGO_HELICOPTER:onafterDeployed(Helicopter,From,Event,To,DeployZone)
self:F({From,Event,To,DeployZone=DeployZone})
self:Orbit(Helicopter:GetCoordinate(),50)
self:ScheduleOnce(30,
function(Helicopter)
AI_CARGO_QUEUE[Helicopter]=nil
end,Helicopter
)
self:GetParent(self,AI_CARGO_HELICOPTER).onafterDeployed(self,Helicopter,From,Event,To,DeployZone)
end
function AI_CARGO_HELICOPTER:onafterPickup(Helicopter,From,Event,To,Coordinate,Speed,Height,PickupZone)
self:F({Coordinate,Speed,Height,PickupZone})
if Helicopter and Helicopter:IsAlive()~=nil then
Helicopter:Activate()
self.RoutePickup=true
Coordinate.y=Height
local _speed=Speed or Helicopter:GetSpeedMax()*0.5
local Route={}
local CoordinateFrom=Helicopter:GetCoordinate()
local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true)
local CoordinateTo=Coordinate
local landheight=CoordinateTo:GetLandHeight()
CoordinateTo.y=landheight+50
local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true)
Route[#Route+1]=WaypointFrom
Route[#Route+1]=WaypointTo
Helicopter:WayPointInitialize(Route)
local Tasks={}
Tasks[#Tasks+1]=Helicopter:TaskLandAtVec2(CoordinateTo:GetVec2())
Route[#Route].task=Helicopter:TaskCombo(Tasks)
Route[#Route+1]=WaypointTo
Helicopter:Route(Route,1)
self.PickupZone=PickupZone
self:GetParent(self,AI_CARGO_HELICOPTER).onafterPickup(self,Helicopter,From,Event,To,Coordinate,Speed,Height,PickupZone)
end
end
function AI_CARGO_HELICOPTER:_Deploy(AICargoHelicopter,Coordinate,DeployZone)
AICargoHelicopter:__Queue(-10,Coordinate,100,DeployZone)
end
function AI_CARGO_HELICOPTER:onafterDeploy(Helicopter,From,Event,To,Coordinate,Speed,Height,DeployZone)
self:F({From,Event,To,Coordinate,Speed,Height,DeployZone})
if Helicopter and Helicopter:IsAlive()~=nil then
self.RouteDeploy=true
local Route={}
Coordinate.y=Height
local _speed=Speed or Helicopter:GetSpeedMax()*0.5
local CoordinateFrom=Helicopter:GetCoordinate()
local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true)
Route[#Route+1]=WaypointFrom
Route[#Route+1]=WaypointFrom
local CoordinateTo=Coordinate
local landheight=CoordinateTo:GetLandHeight()
CoordinateTo.y=landheight+50
local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true)
Route[#Route+1]=WaypointTo
Route[#Route+1]=WaypointTo
Helicopter:WayPointInitialize(Route)
local Tasks={}
Tasks[#Tasks+1]=Helicopter:TaskFunction("AI_CARGO_HELICOPTER._Deploy",self,Coordinate,DeployZone)
Tasks[#Tasks+1]=Helicopter:TaskOrbitCircle(math.random(30,100),_speed,CoordinateTo:GetRandomCoordinateInRadius(800,500))
Route[#Route].task=Helicopter:TaskCombo(Tasks)
Route[#Route+1]=WaypointTo
Helicopter:Route(Route,0)
self:GetParent(self,AI_CARGO_HELICOPTER).onafterDeploy(self,Helicopter,From,Event,To,Coordinate,Speed,Height,DeployZone)
end
end
function AI_CARGO_HELICOPTER:onafterHome(Helicopter,From,Event,To,Coordinate,Speed,Height,HomeZone)
self:F({From,Event,To,Coordinate,Speed,Height})
if Helicopter and Helicopter:IsAlive()~=nil then
self.RouteHome=true
local Route={}
Height=Height or 50
Speed=Speed or Helicopter:GetSpeedMax()*0.5
local CoordinateFrom=Helicopter:GetCoordinate()
local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,Speed,true)
Route[#Route+1]=WaypointFrom
local CoordinateTo=Coordinate
local landheight=CoordinateTo:GetLandHeight()
CoordinateTo.y=landheight+Height
local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,Speed,true)
Route[#Route+1]=WaypointTo
Helicopter:WayPointInitialize(Route)
local Tasks={}
Tasks[#Tasks+1]=Helicopter:TaskLandAtVec2(CoordinateTo:GetVec2())
Route[#Route].task=Helicopter:TaskCombo(Tasks)
Route[#Route+1]=WaypointTo
Helicopter:Route(Route,0)
end
end
AI_CARGO_AIRPLANE={
ClassName="AI_CARGO_AIRPLANE",
Coordinate=nil,
}
function AI_CARGO_AIRPLANE:New(Airplane,CargoSet)
local self=BASE:Inherit(self,AI_CARGO:New(Airplane,CargoSet))
self:AddTransition("*","Landed","*")
self:AddTransition("*","Home","*")
self:AddTransition("*","Destroyed","Destroyed")
self:SetCarrier(Airplane)
return self
end
function AI_CARGO_AIRPLANE:SetCarrier(Airplane)
local AICargo=self
self.Airplane=Airplane
self.Airplane:SetState(self.Airplane,"AI_CARGO_AIRPLANE",self)
self.RoutePickup=false
self.RouteDeploy=false
Airplane:HandleEvent(EVENTS.Dead)
Airplane:HandleEvent(EVENTS.Hit)
Airplane:HandleEvent(EVENTS.EngineShutdown)
function Airplane:OnEventDead(EventData)
local AICargoTroops=self:GetState(self,"AI_CARGO_AIRPLANE")
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is("Loaded")then
AICargoTroops:Destroyed()
end
end
end
function Airplane:OnEventHit(EventData)
local AICargoTroops=self:GetState(self,"AI_CARGO_AIRPLANE")
if AICargoTroops then
self:F({OnHitLoaded=AICargoTroops:Is("Loaded")})
if AICargoTroops:Is("Loaded")or AICargoTroops:Is("Boarding")then
AICargoTroops:Unload()
end
end
end
function Airplane:OnEventEngineShutdown(EventData)
AICargo.Relocating=false
AICargo:Landed(self.Airplane)
end
self.Coalition=self.Airplane:GetCoalition()
self:SetControllable(Airplane)
return self
end
function AI_CARGO_AIRPLANE:FindCarrier(Coordinate,Radius)
local CoordinateZone=ZONE_RADIUS:New("Zone",Coordinate:GetVec2(),Radius)
CoordinateZone:Scan({Object.Category.UNIT})
for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do
local NearUnit=UNIT:Find(DCSUnit)
self:F({NearUnit=NearUnit})
if not NearUnit:GetState(NearUnit,"AI_CARGO_AIRPLANE")then
local Attributes=NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttribute("Trucks")then
self:SetCarrier(NearUnit)
break
end
end
end
end
function AI_CARGO_AIRPLANE:onafterLanded(Airplane,From,Event,To)
self:F({Airplane,From,Event,To})
if Airplane and Airplane:IsAlive()~=nil then
if self.RoutePickup==true then
self:Load(self.PickupZone)
end
if self.RouteDeploy==true then
self:Unload()
self.RouteDeploy=false
end
end
end
function AI_CARGO_AIRPLANE:onafterPickup(Airplane,From,Event,To,Coordinate,Speed,Height,PickupZone)
if Airplane and Airplane:IsAlive()then
local airbasepickup=Coordinate:GetClosestAirbase()
self.PickupZone=PickupZone or ZONE_AIRBASE:New(airbasepickup:GetName())
local ClosestAirbase,DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase()
if Airplane:InAir()then
self.Airbase=nil
else
self.Airbase=ClosestAirbase
end
local Airbase=self.PickupZone:GetAirbase()
local Dist=Airbase:GetCoordinate():Get2DDistance(ClosestAirbase:GetCoordinate())
if Airplane:InAir()or Dist>500 then
self:Route(Airplane,Airbase,Speed,Height)
self.Airbase=Airbase
self.RoutePickup=true
else
self.RoutePickup=true
self:Landed()
end
self:GetParent(self,AI_CARGO_AIRPLANE).onafterPickup(self,Airplane,From,Event,To,Coordinate,Speed,Height,self.PickupZone)
end
end
function AI_CARGO_AIRPLANE:onafterDeploy(Airplane,From,Event,To,Coordinate,Speed,Height,DeployZone)
if Airplane and Airplane:IsAlive()~=nil then
local Airbase=Coordinate:GetClosestAirbase()
if DeployZone then
Airbase=DeployZone:GetAirbase()
end
if Airplane:IsAlive()==false then
Airplane:SetCommand({id='Start',params={}})
end
self:Route(Airplane,Airbase,Speed,Height)
self.RouteDeploy=true
self.Airbase=Airbase
self:GetParent(self,AI_CARGO_AIRPLANE).onafterDeploy(self,Airplane,From,Event,To,Coordinate,Speed,Height,DeployZone)
end
end
function AI_CARGO_AIRPLANE:onafterUnload(Airplane,From,Event,To,DeployZone)
local UnboardInterval=10
local UnboardDelay=10
if Airplane and Airplane:IsAlive()then
for _,AirplaneUnit in pairs(Airplane:GetUnits())do
local Cargos=AirplaneUnit:GetCargo()
for CargoID,Cargo in pairs(Cargos)do
local Angle=180
local CargoCarrierHeading=Airplane:GetHeading()
local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle)
self:T({CargoCarrierHeading,CargoDeployHeading})
local CargoDeployCoordinate=Airplane:GetPointVec2():Translate(150,CargoDeployHeading)
Cargo:__UnBoard(UnboardDelay,CargoDeployCoordinate)
UnboardDelay=UnboardDelay+UnboardInterval
Cargo:SetDeployed(true)
self:__Unboard(UnboardDelay,Cargo,AirplaneUnit,DeployZone)
end
end
end
end
function AI_CARGO_AIRPLANE:Route(Airplane,Airbase,Speed,Height,Uncontrolled)
if Airplane and Airplane:IsAlive()then
local Takeoff=SPAWN.Takeoff.Cold
local Template=Airplane:GetTemplate()
if Template==nil then
return
end
local Points={}
local AirbasePointVec2=Airbase:GetPointVec2()
local ToWaypoint=AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO,"Land","Landing",Speed or Airplane:GetSpeedMax()*0.8,true,Airbase)
if self.Airbase then
Template.route.points[2]=ToWaypoint
Airplane:RespawnAtCurrentAirbase(Template,Takeoff,Uncontrolled)
else
local GroupPoint=Airplane:GetVec2()
local FromWaypoint={}
FromWaypoint.x=GroupPoint.x
FromWaypoint.y=GroupPoint.y
FromWaypoint.type="Turning Point"
FromWaypoint.action="Turning Point"
FromWaypoint.speed=Airplane:GetSpeedMax()*0.8
Points[1]=FromWaypoint
Points[2]=ToWaypoint
local PointVec3=Airplane:GetPointVec3()
Template.x=PointVec3.x
Template.y=PointVec3.z
Template.route.points=Points
local GroupSpawned=Airplane:Respawn(Template)
end
end
end
function AI_CARGO_AIRPLANE:onafterHome(Airplane,From,Event,To,Coordinate,Speed,Height,HomeZone)
if Airplane and Airplane:IsAlive()then
self.RouteHome=true
local HomeBase=HomeZone:GetAirbase()
self.Airbase=HomeBase
self:Route(Airplane,HomeBase,Speed,Height)
end
end
AI_CARGO_SHIP={
ClassName="AI_CARGO_SHIP",
Coordinate=nil
}
function AI_CARGO_SHIP:New(Ship,CargoSet,CombatRadius,ShippingLane)
local self=BASE:Inherit(self,AI_CARGO:New(Ship,CargoSet))
self:AddTransition("*","Monitor","*")
self:AddTransition("*","Destroyed","Destroyed")
self:AddTransition("*","Home","*")
self:SetCombatRadius(0)
self:SetShippingLane(ShippingLane)
self:SetCarrier(Ship)
return self
end
function AI_CARGO_SHIP:SetCarrier(CargoCarrier)
self.CargoCarrier=CargoCarrier
self.CargoCarrier:SetState(self.CargoCarrier,"AI_CARGO_SHIP",self)
CargoCarrier:HandleEvent(EVENTS.Dead)
function CargoCarrier:OnEventDead(EventData)
self:F({"dead"})
local AICargoTroops=self:GetState(self,"AI_CARGO_SHIP")
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is("Loaded")then
AICargoTroops:Destroyed()
end
end
end
self.Zone=ZONE_UNIT:New(self.CargoCarrier:GetName().."-Zone",self.CargoCarrier,self.CombatRadius)
self.Coalition=self.CargoCarrier:GetCoalition()
self:SetControllable(CargoCarrier)
return self
end
function AI_CARGO_SHIP:FindCarrier(Coordinate,Radius)
local CoordinateZone=ZONE_RADIUS:New("Zone",Coordinate:GetVec2(),Radius)
CoordinateZone:Scan({Object.Category.UNIT})
for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do
local NearUnit=UNIT:Find(DCSUnit)
self:F({NearUnit=NearUnit})
if not NearUnit:GetState(NearUnit,"AI_CARGO_SHIP")then
local Attributes=NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttributes("Trucks")then
return NearUnit:GetGroup()
end
end
end
return nil
end
function AI_CARGO_SHIP:SetShippingLane(ShippingLane)
self.ShippingLane=ShippingLane
return self
end
function AI_CARGO_SHIP:SetCombatRadius(CombatRadius)
self.CombatRadius=CombatRadius or 0
return self
end
function AI_CARGO_SHIP:FollowToCarrier(Me,ShipUnit,CargoGroup)
local InfantryGroup=CargoGroup:GetGroup()
self:F({self=self:GetClassNameAndID(),InfantryGroup=InfantryGroup:GetName()})
if ShipUnit:IsAlive()then
if InfantryGroup:IsPartlyInZone(ZONE_UNIT:New("Radius",ShipUnit,1000))then
Me:Guard()
else
self:F({InfantryGroup=InfantryGroup:GetName()})
if InfantryGroup:IsAlive()then
self:F({InfantryGroup=InfantryGroup:GetName()})
local Waypoints={}
local FromCoord=InfantryGroup:GetCoordinate()
local FromGround=FromCoord:WaypointGround(10,"Diamond")
self:F({FromGround=FromGround})
table.insert(Waypoints,FromGround)
local ToCoord=ShipUnit:GetCoordinate():GetRandomCoordinateInRadius(10,5)
local ToGround=ToCoord:WaypointGround(10,"Diamond")
self:F({ToGround=ToGround})
table.insert(Waypoints,ToGround)
local TaskRoute=InfantryGroup:TaskFunction("AI_CARGO_SHIP.FollowToCarrier",Me,ShipUnit,CargoGroup)
self:F({Waypoints=Waypoints})
local Waypoint=Waypoints[#Waypoints]
InfantryGroup:SetTaskWaypoint(Waypoint,TaskRoute)
InfantryGroup:Route(Waypoints,1)
end
end
end
end
function AI_CARGO_SHIP:onafterMonitor(Ship,From,Event,To)
self:F({Ship,From,Event,To,IsTransporting=self:IsTransporting()})
if self.CombatRadius>0 then
if Ship and Ship:IsAlive()then
if self.CarrierCoordinate then
if self:IsTransporting()==true then
local Coordinate=Ship:GetCoordinate()
if self:Is("Unloaded")or self:Is("Loaded")then
self.Zone:Scan({Object.Category.UNIT})
if self.Zone:IsAllInZoneOfCoalition(self.Coalition)then
if self:Is("Unloaded")then
self:Reload()
end
else
if self:Is("Loaded")then
self:__Unload(1,nil,true)
else
if self:Is("Unloaded")then
end
self:F("I am here"..self:GetCurrentState())
if self:Is("Following")then
for Cargo,ShipUnit in pairs(self.Carrier_Cargo)do
local Cargo=Cargo
local ShipUnit=ShipUnit
if Cargo:IsAlive()then
if not Cargo:IsNear(ShipUnit,40)then
ShipUnit:RouteStop()
self.CarrierStopped=true
else
if self.CarrierStopped then
if Cargo:IsNear(ShipUnit,25)then
ShipUnit:RouteResume()
self.CarrierStopped=nil
end
end
end
end
end
end
end
end
end
end
end
self.CarrierCoordinate=Ship:GetCoordinate()
end
self:__Monitor(-5)
end
end
function AI_CARGO_SHIP._Pickup(Ship,self,Coordinate,Speed,PickupZone)
Ship:F({"AI_CARGO_Ship._Pickup:",Ship:GetName()})
if Ship:IsAlive()then
self:Load(PickupZone)
end
end
function AI_CARGO_SHIP._Deploy(Ship,self,Coordinate,DeployZone)
Ship:F({"AI_CARGO_Ship._Deploy:",Ship})
if Ship:IsAlive()then
self:Unload(DeployZone)
end
end
function AI_CARGO_SHIP:onafterPickup(Ship,From,Event,To,Coordinate,Speed,Height,PickupZone)
if Ship and Ship:IsAlive()then
AI_CARGO_SHIP._Pickup(Ship,self,Coordinate,Speed,PickupZone)
self:GetParent(self,AI_CARGO_SHIP).onafterPickup(self,Ship,From,Event,To,Coordinate,Speed,Height,PickupZone)
end
end
function AI_CARGO_SHIP:onafterDeploy(Ship,From,Event,To,Coordinate,Speed,Height,DeployZone)
if Ship and Ship:IsAlive()then
Speed=Speed or Ship:GetSpeedMax()*0.8
local lane=self.ShippingLane
if lane then
local Waypoints={}
for i=1,#lane do
local coord=lane[i]
local Waypoint=coord:WaypointGround(_speed)
table.insert(Waypoints,Waypoint)
end
local TaskFunction=Ship:TaskFunction("AI_CARGO_SHIP._Deploy",self,Coordinate,DeployZone)
local Waypoint=Waypoints[#Waypoints]
Ship:SetTaskWaypoint(Waypoint,TaskFunction)
Ship:Route(Waypoints,1)
self:GetParent(self,AI_CARGO_SHIP).onafterDeploy(self,Ship,From,Event,To,Coordinate,Speed,Height,DeployZone)
else
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
end
end
end
function AI_CARGO_SHIP:onafterUnload(Ship,From,Event,To,DeployZone,Defend)
self:F({Ship,From,Event,To,DeployZone,Defend=Defend})
local UnboardInterval=5
local UnboardDelay=5
if Ship and Ship:IsAlive()then
for _,ShipUnit in pairs(Ship:GetUnits())do
local ShipUnit=ShipUnit
Ship:RouteStop()
for _,Cargo in pairs(ShipUnit:GetCargo())do
self:F({Cargo=Cargo:GetName(),Isloaded=Cargo:IsLoaded()})
if Cargo:IsLoaded()then
local unboardCoord=DeployZone:GetRandomPointVec2()
Cargo:__UnBoard(UnboardDelay,unboardCoord,1000)
UnboardDelay=UnboardDelay+Cargo:GetCount()*UnboardInterval
self:__Unboard(UnboardDelay,Cargo,ShipUnit,DeployZone,Defend)
if not Defend==true then
Cargo:SetDeployed(true)
end
end
end
end
end
end
function AI_CARGO_SHIP:onafterHome(Ship,From,Event,To,Coordinate,Speed,Height,HomeZone)
if Ship and Ship:IsAlive()then
self.RouteHome=true
Speed=Speed or Ship:GetSpeedMax()*0.8
local lane=self.ShippingLane
if lane then
local Waypoints={}
for i=#lane,1,-1 do
local coord=lane[i]
local Waypoint=coord:WaypointGround(_speed)
table.insert(Waypoints,Waypoint)
end
local Waypoint=Waypoints[#Waypoints]
Ship:Route(Waypoints,1)
else
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
end
end
end
AI_CARGO_DISPATCHER={
ClassName="AI_CARGO_DISPATCHER",
AI_Cargo={},
PickupCargo={}
}
AI_CARGO_DISPATCHER.AI_Cargo={}
AI_CARGO_DISPATCHER.PickupCargo={}
function AI_CARGO_DISPATCHER:New(CarrierSet,CargoSet,PickupZoneSet,DeployZoneSet)
local self=BASE:Inherit(self,FSM:New())
self.SetCarrier=CarrierSet
self.SetCargo=CargoSet
self.PickupZoneSet=PickupZoneSet
self.DeployZoneSet=DeployZoneSet
self:SetStartState("Idle")
self:AddTransition("Monitoring","Monitor","Monitoring")
self:AddTransition("Idle","Start","Monitoring")
self:AddTransition("Monitoring","Stop","Idle")
self:AddTransition("Monitoring","Pickup","Monitoring")
self:AddTransition("Monitoring","Load","Monitoring")
self:AddTransition("Monitoring","Loading","Monitoring")
self:AddTransition("Monitoring","Loaded","Monitoring")
self:AddTransition("Monitoring","PickedUp","Monitoring")
self:AddTransition("Monitoring","Transport","Monitoring")
self:AddTransition("Monitoring","Deploy","Monitoring")
self:AddTransition("Monitoring","Unload","Monitoring")
self:AddTransition("Monitoring","Unloading","Monitoring")
self:AddTransition("Monitoring","Unloaded","Monitoring")
self:AddTransition("Monitoring","Deployed","Monitoring")
self:AddTransition("Monitoring","Home","Monitoring")
self:SetMonitorTimeInterval(30)
self:SetDeployRadius(500,200)
self.PickupCargo={}
self.CarrierHome={}
function self.SetCarrier.OnAfterRemoved(SetCarrier,From,Event,To,CarrierName,Carrier)
self:F({Carrier=Carrier:GetName()})
self.PickupCargo[Carrier]=nil
self.CarrierHome[Carrier]=nil
end
return self
end
function AI_CARGO_DISPATCHER:SetMonitorTimeInterval(MonitorTimeInterval)
self.MonitorTimeInterval=MonitorTimeInterval
return self
end
function AI_CARGO_DISPATCHER:SetHomeZone(HomeZone)
self.HomeZone=HomeZone
return self
end
function AI_CARGO_DISPATCHER:SetPickupRadius(OuterRadius,InnerRadius)
OuterRadius=OuterRadius or 0
InnerRadius=InnerRadius or OuterRadius
self.PickupOuterRadius=OuterRadius
self.PickupInnerRadius=InnerRadius
return self
end
function AI_CARGO_DISPATCHER:SetPickupSpeed(MaxSpeed,MinSpeed)
MaxSpeed=MaxSpeed or 999
MinSpeed=MinSpeed or MaxSpeed
self.PickupMinSpeed=MinSpeed
self.PickupMaxSpeed=MaxSpeed
return self
end
function AI_CARGO_DISPATCHER:SetDeployRadius(OuterRadius,InnerRadius)
OuterRadius=OuterRadius or 0
InnerRadius=InnerRadius or OuterRadius
self.DeployOuterRadius=OuterRadius
self.DeployInnerRadius=InnerRadius
return self
end
function AI_CARGO_DISPATCHER:SetDeploySpeed(MaxSpeed,MinSpeed)
MaxSpeed=MaxSpeed or 999
MinSpeed=MinSpeed or MaxSpeed
self.DeployMinSpeed=MinSpeed
self.DeployMaxSpeed=MaxSpeed
return self
end
function AI_CARGO_DISPATCHER:SetPickupHeight(MaxHeight,MinHeight)
MaxHeight=MaxHeight or 200
MinHeight=MinHeight or MaxHeight
self.PickupMinHeight=MinHeight
self.PickupMaxHeight=MaxHeight
return self
end
function AI_CARGO_DISPATCHER:SetDeployHeight(MaxHeight,MinHeight)
MaxHeight=MaxHeight or 200
MinHeight=MinHeight or MaxHeight
self.DeployMinHeight=MinHeight
self.DeployMaxHeight=MaxHeight
return self
end
function AI_CARGO_DISPATCHER:onafterMonitor()
self:F("Carriers")
self.SetCarrier:Flush()
for CarrierGroupName,Carrier in pairs(self.SetCarrier:GetSet())do
local Carrier=Carrier
if Carrier:IsAlive()~=nil then
local AI_Cargo=self.AI_Cargo[Carrier]
if not AI_Cargo then
self.AI_Cargo[Carrier]=self:AICargo(Carrier,self.SetCargo,self.CombatRadius)
AI_Cargo=self.AI_Cargo[Carrier]
function AI_Cargo.OnAfterPickup(AI_Cargo,CarrierGroup,From,Event,To,Coordinate,Speed,Height,PickupZone)
self:Pickup(CarrierGroup,Coordinate,Speed,Height,PickupZone)
end
function AI_Cargo.OnAfterLoad(AI_Cargo,CarrierGroup,From,Event,To,PickupZone)
self:Load(CarrierGroup,PickupZone)
end
function AI_Cargo.OnAfterBoard(AI_Cargo,CarrierGroup,From,Event,To,Cargo,CarrierUnit,PickupZone)
self:Loading(CarrierGroup,Cargo,CarrierUnit,PickupZone)
end
function AI_Cargo.OnAfterLoaded(AI_Cargo,CarrierGroup,From,Event,To,Cargo,CarrierUnit,PickupZone)
self:Loaded(CarrierGroup,Cargo,CarrierUnit,PickupZone)
end
function AI_Cargo.OnAfterPickedUp(AI_Cargo,CarrierGroup,From,Event,To,PickupZone)
self:PickedUp(CarrierGroup,PickupZone)
self:Transport(CarrierGroup)
end
function AI_Cargo.OnAfterDeploy(AI_Cargo,CarrierGroup,From,Event,To,Coordinate,Speed,Height,DeployZone)
self:Deploy(CarrierGroup,Coordinate,Speed,Height,DeployZone)
end
function AI_Cargo.OnAfterUnload(AI_Cargo,Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone)
self:Unloading(Carrier,Cargo,CarrierUnit,DeployZone)
end
function AI_Cargo.OnAfterUnboard(AI_Cargo,CarrierGroup,From,Event,To,Cargo,CarrierUnit,DeployZone)
self:Unloading(CarrierGroup,Cargo,CarrierUnit,DeployZone)
end
function AI_Cargo.OnAfterUnloaded(AI_Cargo,Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone)
self:Unloaded(Carrier,Cargo,CarrierUnit,DeployZone)
end
function AI_Cargo.OnAfterDeployed(AI_Cargo,Carrier,From,Event,To,DeployZone)
self:Deployed(Carrier,DeployZone)
end
function AI_Cargo.OnAfterHome(AI_Cargo,Carrier,From,Event,To,Coordinate,Speed,Height,HomeZone)
self:Home(Carrier,Coordinate,Speed,Height,HomeZone)
end
end
self:T({Carrier=CarrierGroupName,IsRelocating=AI_Cargo:IsRelocating(),IsTransporting=AI_Cargo:IsTransporting()})
if AI_Cargo:IsRelocating()==false and AI_Cargo:IsTransporting()==false then
local PickupCargo=nil
local PickupZone=nil
self.SetCargo:Flush()
for CargoName,Cargo in UTILS.spairs(self.SetCargo:GetSet(),function(t,a,b)return t[a]:GetWeight()<t[b]:GetWeight()end)do
local Cargo=Cargo
self:F({Cargo=Cargo:GetName(),UnLoaded=Cargo:IsUnLoaded(),Deployed=Cargo:IsDeployed(),PickupCargo=self.PickupCargo[Carrier]~=nil})
if Cargo:IsUnLoaded()==true and Cargo:IsDeployed()==false then
local CargoCoordinate=Cargo:GetCoordinate()
local CoordinateFree=true
PickupZone=self.PickupZoneSet and self.PickupZoneSet:IsCoordinateInZone(CargoCoordinate)
if not self.PickupZoneSet or PickupZone then
for CarrierPickup,Coordinate in pairs(self.PickupCargo)do
if CarrierPickup:IsAlive()==true then
if CargoCoordinate:Get2DDistance(Coordinate)<=25 then
self:F({"Coordinate not free for ",Cargo=Cargo:GetName(),Carrier:GetName(),PickupCargo=self.PickupCargo[Carrier]~=nil})
CoordinateFree=false
break
end
else
self.PickupCargo[CarrierPickup]=nil
end
end
if CoordinateFree==true then
local LargestLoadCapacity=0
for _,Carrier in pairs(Carrier:GetUnits())do
local LoadCapacity=Carrier:GetCargoBayFreeWeight()
if LargestLoadCapacity<LoadCapacity then
LargestLoadCapacity=LoadCapacity
end
end
if LargestLoadCapacity>=Cargo:GetWeight()then
self.PickupCargo[Carrier]=CargoCoordinate
PickupCargo=Cargo
break
else
local text=string.format("WARNING: Cargo %s is too heavy to be loaded into transport. Cargo weight %.1f > %.1f load capacity of carrier %s.",
tostring(Cargo:GetName()),Cargo:GetWeight(),LargestLoadCapacity,tostring(Carrier:GetName()))
self:I(text)
end
end
end
end
end
if PickupCargo then
self.CarrierHome[Carrier]=nil
local PickupCoordinate=PickupCargo:GetCoordinate():GetRandomCoordinateInRadius(self.PickupOuterRadius,self.PickupInnerRadius)
AI_Cargo:Pickup(PickupCoordinate,math.random(self.PickupMinSpeed,self.PickupMaxSpeed),math.random(self.PickupMinHeight,self.PickupMaxHeight),PickupZone)
break
else
if self.HomeZone then
if not self.CarrierHome[Carrier]then
self.CarrierHome[Carrier]=true
AI_Cargo:Home(self.HomeZone:GetRandomPointVec2(),math.random(self.PickupMinSpeed,self.PickupMaxSpeed),math.random(self.PickupMinHeight,self.PickupMaxHeight),self.HomeZone)
end
end
end
end
end
end
self:__Monitor(self.MonitorTimeInterval)
end
function AI_CARGO_DISPATCHER:onafterStart(From,Event,To)
self:__Monitor(-1)
end
function AI_CARGO_DISPATCHER:onafterTransport(From,Event,To,Carrier,Cargo)
if self.DeployZoneSet then
if self.AI_Cargo[Carrier]:IsTransporting()==true then
local DeployZone=self.DeployZoneSet:GetRandomZone()
local DeployCoordinate=DeployZone:GetCoordinate():GetRandomCoordinateInRadius(self.DeployOuterRadius,self.DeployInnerRadius)
self.AI_Cargo[Carrier]:__Deploy(0.1,DeployCoordinate,math.random(self.DeployMinSpeed,self.DeployMaxSpeed),math.random(self.DeployMinHeight,self.DeployMaxHeight),DeployZone)
end
end
self:F({Carrier=Carrier:GetName(),PickupCargo=self.PickupCargo})
self.PickupCargo[Carrier]=nil
end
AI_CARGO_DISPATCHER_APC={
ClassName="AI_CARGO_DISPATCHER_APC",
}
function AI_CARGO_DISPATCHER_APC:New(APCSet,CargoSet,PickupZoneSet,DeployZoneSet,CombatRadius)
local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(APCSet,CargoSet,PickupZoneSet,DeployZoneSet))
self:SetDeploySpeed(120,70)
self:SetPickupSpeed(120,70)
self:SetPickupRadius(0,0)
self:SetDeployRadius(0,0)
self:SetPickupHeight()
self:SetDeployHeight()
self:SetCombatRadius(CombatRadius)
return self
end
function AI_CARGO_DISPATCHER_APC:AICargo(APC,CargoSet)
local aicargoapc=AI_CARGO_APC:New(APC,CargoSet,self.CombatRadius)
aicargoapc:SetDeployOffRoad(self.deployOffroad,self.deployFormation)
aicargoapc:SetPickupOffRoad(self.pickupOffroad,self.pickupFormation)
return aicargoapc
end
function AI_CARGO_DISPATCHER_APC:SetCombatRadius(CombatRadius)
self.CombatRadius=CombatRadius or 0
return self
end
function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad,Formation)
self:SetPickupOffRoad(Offroad,Formation)
self:SetDeployOffRoad(Offroad,Formation)
return self
end
function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad,Formation)
self.pickupOffroad=Offroad
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
return self
end
function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad,Formation)
self.deployOffroad=Offroad
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
return self
end
AI_CARGO_DISPATCHER_HELICOPTER={
ClassName="AI_CARGO_DISPATCHER_HELICOPTER",
}
function AI_CARGO_DISPATCHER_HELICOPTER:New(HelicopterSet,CargoSet,PickupZoneSet,DeployZoneSet)
local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(HelicopterSet,CargoSet,PickupZoneSet,DeployZoneSet))
self:SetPickupSpeed(350,150)
self:SetDeploySpeed(350,150)
self:SetPickupRadius(40,12)
self:SetDeployRadius(40,12)
self:SetPickupHeight(500,200)
self:SetDeployHeight(500,200)
return self
end
function AI_CARGO_DISPATCHER_HELICOPTER:AICargo(Helicopter,CargoSet)
local dispatcher=AI_CARGO_HELICOPTER:New(Helicopter,CargoSet)
dispatcher:SetLandingSpeedAndHeight(27,6)
return dispatcher
end
AI_CARGO_DISPATCHER_AIRPLANE={
ClassName="AI_CARGO_DISPATCHER_AIRPLANE",
}
function AI_CARGO_DISPATCHER_AIRPLANE:New(AirplaneSet,CargoSet,PickupZoneSet,DeployZoneSet)
local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(AirplaneSet,CargoSet,PickupZoneSet,DeployZoneSet))
self:SetPickupSpeed(1200,600)
self:SetDeploySpeed(1200,600)
self:SetPickupRadius(0,0)
self:SetDeployRadius(0,0)
self:SetPickupHeight(8000,6000)
self:SetDeployHeight(8000,6000)
self:SetMonitorTimeInterval(600)
return self
end
function AI_CARGO_DISPATCHER_AIRPLANE:AICargo(Airplane,CargoSet)
return AI_CARGO_AIRPLANE:New(Airplane,CargoSet)
end
AI_CARGO_DISPATCHER_SHIP={
ClassName="AI_CARGO_DISPATCHER_SHIP"
}
function AI_CARGO_DISPATCHER_SHIP:New(ShipSet,CargoSet,PickupZoneSet,DeployZoneSet,ShippingLane)
local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(ShipSet,CargoSet,PickupZoneSet,DeployZoneSet))
self:SetPickupSpeed(60,10)
self:SetDeploySpeed(60,10)
self:SetPickupRadius(500,6000)
self:SetDeployRadius(500,6000)
self:SetPickupHeight(0,0)
self:SetDeployHeight(0,0)
self:SetShippingLane(ShippingLane)
self:SetMonitorTimeInterval(600)
return self
end
function AI_CARGO_DISPATCHER_SHIP:SetShippingLane(ShippingLane)
self.ShippingLane=ShippingLane
return self
end
function AI_CARGO_DISPATCHER_SHIP:AICargo(Ship,CargoSet)
return AI_CARGO_SHIP:New(Ship,CargoSet,0,self.ShippingLane)
end
do
ACT_ASSIGN={
ClassName="ACT_ASSIGN",
}
function ACT_ASSIGN:New()
local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ASSIGN"))
self:AddTransition("UnAssigned","Start","Waiting")
self:AddTransition("Waiting","Assign","Assigned")
self:AddTransition("Waiting","Reject","Rejected")
self:AddTransition("*","Fail","Failed")
self:AddEndState("Assigned")
self:AddEndState("Rejected")
self:AddEndState("Failed")
self:SetStartState("UnAssigned")
return self
end
end
do
ACT_ASSIGN_ACCEPT={
ClassName="ACT_ASSIGN_ACCEPT",
}
function ACT_ASSIGN_ACCEPT:New(TaskBriefing)
local self=BASE:Inherit(self,ACT_ASSIGN:New())
self.TaskBriefing=TaskBriefing
return self
end
function ACT_ASSIGN_ACCEPT:Init(FsmAssign)
self.TaskBriefing=FsmAssign.TaskBriefing
end
function ACT_ASSIGN_ACCEPT:onafterStart(ProcessUnit,Task,From,Event,To)
self:__Assign(1)
end
function ACT_ASSIGN_ACCEPT:onenterAssigned(ProcessUnit,Task,From,Event,To,TaskGroup)
self.Task:Assign(ProcessUnit,ProcessUnit:GetPlayerName())
end
end
do
ACT_ASSIGN_MENU_ACCEPT={
ClassName="ACT_ASSIGN_MENU_ACCEPT",
}
function ACT_ASSIGN_MENU_ACCEPT:New(TaskBriefing)
local self=BASE:Inherit(self,ACT_ASSIGN:New())
self.TaskBriefing=TaskBriefing
return self
end
function ACT_ASSIGN_MENU_ACCEPT:Init(TaskBriefing)
self.TaskBriefing=TaskBriefing
return self
end
function ACT_ASSIGN_MENU_ACCEPT:onafterStart(ProcessUnit,Task,From,Event,To)
self:GetCommandCenter():MessageToGroup("Task "..self.Task:GetName().." has been assigned to you and your group!\nRead the briefing and use the Radio Menu (F10) / Task ... CONFIRMATION menu to accept or reject the task.\nYou have 2 minutes to accept, or the task assignment will be cancelled!",ProcessUnit:GetGroup(),120)
local TaskGroup=ProcessUnit:GetGroup()
self.Menu=MENU_GROUP:New(TaskGroup,"Task "..self.Task:GetName().." CONFIRMATION")
self.MenuAcceptTask=MENU_GROUP_COMMAND:New(TaskGroup,"Accept task "..self.Task:GetName(),self.Menu,self.MenuAssign,self,TaskGroup)
self.MenuRejectTask=MENU_GROUP_COMMAND:New(TaskGroup,"Reject task "..self.Task:GetName(),self.Menu,self.MenuReject,self,TaskGroup)
self:__Reject(120,TaskGroup)
end
function ACT_ASSIGN_MENU_ACCEPT:MenuAssign(TaskGroup)
self:__Assign(-1,TaskGroup)
end
function ACT_ASSIGN_MENU_ACCEPT:MenuReject(TaskGroup)
self:__Reject(-1,TaskGroup)
end
function ACT_ASSIGN_MENU_ACCEPT:onafterAssign(ProcessUnit,Task,From,Event,To,TaskGroup)
self.Menu:Remove()
end
function ACT_ASSIGN_MENU_ACCEPT:onafterReject(ProcessUnit,Task,From,Event,To,TaskGroup)
self:F({TaskGroup=TaskGroup})
self.Menu:Remove()
self.Task:RejectGroup(TaskGroup)
end
function ACT_ASSIGN_MENU_ACCEPT:onenterAssigned(ProcessUnit,Task,From,Event,To,TaskGroup)
self.Task:Assign(ProcessUnit,ProcessUnit:GetPlayerName())
end
end
do
ACT_ROUTE={
ClassName="ACT_ROUTE",
}
function ACT_ROUTE:New()
local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ROUTE"))
self:AddTransition("*","Reset","None")
self:AddTransition("None","Start","Routing")
self:AddTransition("*","Report","*")
self:AddTransition("Routing","Route","Routing")
self:AddTransition("Routing","Pause","Pausing")
self:AddTransition("Routing","Arrive","Arrived")
self:AddTransition("*","Cancel","Cancelled")
self:AddTransition("Arrived","Success","Success")
self:AddTransition("*","Fail","Failed")
self:AddTransition("","","")
self:AddTransition("","","")
self:AddEndState("Arrived")
self:AddEndState("Failed")
self:AddEndState("Cancelled")
self:SetStartState("None")
self:SetRouteMode("C")
return self
end
function ACT_ROUTE:SetMenuCancel(MenuGroup,MenuText,ParentMenu,MenuTime,MenuTag)
self.CancelMenuGroupCommand=MENU_GROUP_COMMAND:New(
MenuGroup,
MenuText,
ParentMenu,
self.MenuCancel,
self
):SetTime(MenuTime):SetTag(MenuTag)
ParentMenu:SetTime(MenuTime)
ParentMenu:Remove(MenuTime,MenuTag)
return self
end
function ACT_ROUTE:SetRouteMode(RouteMode)
self.RouteMode=RouteMode
return self
end
function ACT_ROUTE:GetRouteText(Controllable)
local RouteText=""
local Coordinate=nil
if self.Coordinate then
Coordinate=self.Coordinate
end
if self.Zone then
Coordinate=self.Zone:GetPointVec3(self.Altitude)
Coordinate:SetHeading(self.Heading)
end
local Task=self:GetTask()
local CC=self:GetTask():GetMission():GetCommandCenter()
if CC then
if CC:IsModeWWII()then
local ShortestDistance=0
local ShortestReferencePoint=nil
local ShortestReferenceName=""
self:F({CC.ReferencePoints})
for ZoneName,Zone in pairs(CC.ReferencePoints)do
self:F({ZoneName=ZoneName})
local Zone=Zone
local ZoneCoord=Zone:GetCoordinate()
local ZoneDistance=ZoneCoord:Get2DDistance(Coordinate)
self:F({ShortestDistance,ShortestReferenceName})
if ShortestDistance==0 or ZoneDistance<ShortestDistance then
ShortestDistance=ZoneDistance
ShortestReferencePoint=ZoneCoord
ShortestReferenceName=CC.ReferenceNames[ZoneName]
end
end
if ShortestReferencePoint then
RouteText=Coordinate:ToStringFromRP(ShortestReferencePoint,ShortestReferenceName,Controllable)
end
else
RouteText=Coordinate:ToString(Controllable,nil,Task)
end
end
return RouteText
end
function ACT_ROUTE:MenuCancel()
self:F("Cancelled")
self.CancelMenuGroupCommand:Remove()
self:__Cancel(1)
end
function ACT_ROUTE:onafterStart(ProcessUnit,From,Event,To)
self:__Route(1)
end
function ACT_ROUTE:onfuncHasArrived(ProcessUnit)
return false
end
function ACT_ROUTE:onbeforeRoute(ProcessUnit,From,Event,To)
if ProcessUnit:IsAlive()then
local HasArrived=self:onfuncHasArrived(ProcessUnit)
if self.DisplayCount>=self.DisplayInterval then
self:T({HasArrived=HasArrived})
if not HasArrived then
self:Report()
end
self.DisplayCount=1
else
self.DisplayCount=self.DisplayCount+1
end
if HasArrived then
self:__Arrive(1)
else
self:__Route(1)
end
return HasArrived
end
return false
end
end
do
ACT_ROUTE_POINT={
ClassName="ACT_ROUTE_POINT",
}
function ACT_ROUTE_POINT:New(Coordinate,Range)
local self=BASE:Inherit(self,ACT_ROUTE:New())
self.Coordinate=Coordinate
self.Range=Range or 0
self.DisplayInterval=30
self.DisplayCount=30
self.DisplayMessage=true
self.DisplayTime=10
return self
end
function ACT_ROUTE_POINT:Init(FsmRoute)
self.Coordinate=FsmRoute.Coordinate
self.Range=FsmRoute.Range or 0
self.DisplayInterval=30
self.DisplayCount=30
self.DisplayMessage=true
self.DisplayTime=10
self:SetStartState("None")
end
function ACT_ROUTE_POINT:SetCoordinate(Coordinate)
self:F2({Coordinate})
self.Coordinate=Coordinate
end
function ACT_ROUTE_POINT:GetCoordinate()
self:F2({self.Coordinate})
return self.Coordinate
end
function ACT_ROUTE_POINT:SetRange(Range)
self:F2({Range})
self.Range=Range or 10000
end
function ACT_ROUTE_POINT:GetRange()
self:F2({self.Range})
return self.Range
end
function ACT_ROUTE_POINT:onfuncHasArrived(ProcessUnit)
if ProcessUnit:IsAlive()then
local Distance=self.Coordinate:Get2DDistance(ProcessUnit:GetCoordinate())
if Distance<=self.Range then
local RouteText="Task \""..self:GetTask():GetName().."\", you have arrived."
self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Information)
return true
end
end
return false
end
function ACT_ROUTE_POINT:onafterReport(ProcessUnit,From,Event,To)
local RouteText="Task \""..self:GetTask():GetName().."\", "..self:GetRouteText(ProcessUnit)
self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Update)
end
end
do
ACT_ROUTE_ZONE={
ClassName="ACT_ROUTE_ZONE",
}
function ACT_ROUTE_ZONE:New(Zone)
local self=BASE:Inherit(self,ACT_ROUTE:New())
self.Zone=Zone
self.DisplayInterval=30
self.DisplayCount=30
self.DisplayMessage=true
self.DisplayTime=10
return self
end
function ACT_ROUTE_ZONE:Init(FsmRoute)
self.Zone=FsmRoute.Zone
self.DisplayInterval=30
self.DisplayCount=30
self.DisplayMessage=true
self.DisplayTime=10
end
function ACT_ROUTE_ZONE:SetZone(Zone,Altitude,Heading)
self.Zone=Zone
self.Altitude=Altitude
self.Heading=Heading
end
function ACT_ROUTE_ZONE:GetZone()
return self.Zone
end
function ACT_ROUTE_ZONE:onfuncHasArrived(ProcessUnit)
if ProcessUnit:IsInZone(self.Zone)then
local RouteText="Task \""..self:GetTask():GetName().."\", you have arrived within the zone."
self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Information)
end
return ProcessUnit:IsInZone(self.Zone)
end
function ACT_ROUTE_ZONE:onafterReport(ProcessUnit,From,Event,To)
self:F({ProcessUnit=ProcessUnit})
local RouteText="Task \""..self:GetTask():GetName().."\", "..self:GetRouteText(ProcessUnit)
self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Update)
end
end
do
ACT_ACCOUNT={
ClassName="ACT_ACCOUNT",
TargetSetUnit=nil,
}
function ACT_ACCOUNT:New()
local self=BASE:Inherit(self,FSM_PROCESS:New())
self:AddTransition("Assigned","Start","Waiting")
self:AddTransition("*","Wait","Waiting")
self:AddTransition("*","Report","Report")
self:AddTransition("*","Event","Account")
self:AddTransition("Account","Player","AccountForPlayer")
self:AddTransition("Account","Other","AccountForOther")
self:AddTransition({"Account","AccountForPlayer","AccountForOther"},"More","Wait")
self:AddTransition({"Account","AccountForPlayer","AccountForOther"},"NoMore","Accounted")
self:AddTransition("*","Fail","Failed")
self:AddEndState("Failed")
self:SetStartState("Assigned")
return self
end
function ACT_ACCOUNT:onafterStart(ProcessUnit,From,Event,To)
self:HandleEvent(EVENTS.Dead,self.onfuncEventDead)
self:HandleEvent(EVENTS.Crash,self.onfuncEventCrash)
self:HandleEvent(EVENTS.Hit)
self:__Wait(1)
end
function ACT_ACCOUNT:onenterWaiting(ProcessUnit,From,Event,To)
if self.DisplayCount>=self.DisplayInterval then
self:Report()
self.DisplayCount=1
else
self.DisplayCount=self.DisplayCount+1
end
return true
end
function ACT_ACCOUNT:onafterEvent(ProcessUnit,From,Event,To,Event)
self:__NoMore(1)
end
end
do
ACT_ACCOUNT_DEADS={
ClassName="ACT_ACCOUNT_DEADS",
}
function ACT_ACCOUNT_DEADS:New()
local self=BASE:Inherit(self,ACT_ACCOUNT:New())
self.DisplayInterval=30
self.DisplayCount=30
self.DisplayMessage=true
self.DisplayTime=10
self.DisplayCategory="HQ"
return self
end
function ACT_ACCOUNT_DEADS:Init(FsmAccount)
self.Task=self:GetTask()
self.TaskName=self.Task:GetName()
end
function ACT_ACCOUNT_DEADS:onenterReport(ProcessUnit,Task,From,Event,To)
local MessageText="Your group with assigned "..self.TaskName.." task has "..Task.TargetSetUnit:GetUnitTypesText().." targets left to be destroyed."
self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information)
end
function ACT_ACCOUNT_DEADS:onafterEvent(ProcessUnit,Task,From,Event,To,EventData)
self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData})
if Task.TargetSetUnit:FindUnit(EventData.IniUnitName)then
local PlayerName=ProcessUnit:GetPlayerName()
local PlayerHit=self.PlayerHits and self.PlayerHits[EventData.IniUnitName]
if PlayerHit==PlayerName then
self:Player(EventData)
else
self:Other(EventData)
end
end
end
function ACT_ACCOUNT_DEADS:onenterAccountForPlayer(ProcessUnit,Task,From,Event,To,EventData)
self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData})
local TaskGroup=ProcessUnit:GetGroup()
Task.TargetSetUnit:Remove(EventData.IniUnitName)
local MessageText="You have destroyed a target.\nYour group assigned with task "..self.TaskName.." has\n"..Task.TargetSetUnit:Count().." targets ( "..Task.TargetSetUnit:GetUnitTypesText().." ) left to be destroyed."
self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information)
local PlayerName=ProcessUnit:GetPlayerName()
Task:AddProgress(PlayerName,"Destroyed "..EventData.IniTypeName,timer.getTime(),1)
if Task.TargetSetUnit:Count()>0 then
self:__More(1)
else
self:__NoMore(1)
end
end
function ACT_ACCOUNT_DEADS:onenterAccountForOther(ProcessUnit,Task,From,Event,To,EventData)
self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData})
local TaskGroup=ProcessUnit:GetGroup()
Task.TargetSetUnit:Remove(EventData.IniUnitName)
local MessageText="One of the task targets has been destroyed.\nYour group assigned with task "..self.TaskName.." has\n"..Task.TargetSetUnit:Count().." targets ( "..Task.TargetSetUnit:GetUnitTypesText().." ) left to be destroyed."
self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information)
if Task.TargetSetUnit:Count()>0 then
self:__More(1)
else
self:__NoMore(1)
end
end
function ACT_ACCOUNT_DEADS:OnEventHit(EventData)
self:T({"EventDead",EventData})
if EventData.IniPlayerName and EventData.TgtDCSUnitName then
self.PlayerHits=self.PlayerHits or{}
self.PlayerHits[EventData.TgtDCSUnitName]=EventData.IniPlayerName
end
end
function ACT_ACCOUNT_DEADS:onfuncEventDead(EventData)
self:T({"EventDead",EventData})
if EventData.IniDCSUnit then
self:Event(EventData)
end
end
function ACT_ACCOUNT_DEADS:onfuncEventCrash(EventData)
self:T({"EventDead",EventData})
if EventData.IniDCSUnit then
self:Event(EventData)
end
end
end
do
ACT_ASSIST={
ClassName="ACT_ASSIST",
}
function ACT_ASSIST:New()
local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ASSIST"))
self:AddTransition("None","Start","AwaitSmoke")
self:AddTransition("AwaitSmoke","Next","Smoking")
self:AddTransition("Smoking","Next","AwaitSmoke")
self:AddTransition("*","Stop","Success")
self:AddTransition("*","Fail","Failed")
self:AddEndState("Failed")
self:AddEndState("Success")
self:SetStartState("None")
return self
end
function ACT_ASSIST:onafterStart(ProcessUnit,From,Event,To)
local ProcessGroup=ProcessUnit:GetGroup()
local MissionMenu=self:GetMission():GetMenu(ProcessGroup)
local function MenuSmoke(MenuParam)
local self=MenuParam.self
local SmokeColor=MenuParam.SmokeColor
self.SmokeColor=SmokeColor
self:__Next(1)
end
self.Menu=MENU_GROUP:New(ProcessGroup,"Target acquisition",MissionMenu)
self.MenuSmokeBlue=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop blue smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Blue})
self.MenuSmokeGreen=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop green smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Green})
self.MenuSmokeOrange=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop Orange smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Orange})
self.MenuSmokeRed=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop Red smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Red})
self.MenuSmokeWhite=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop White smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.White})
end
function ACT_ASSIST:onafterStop(ProcessUnit,From,Event,To)
self.Menu:Remove()
end
end
do
ACT_ASSIST_SMOKE_TARGETS_ZONE={
ClassName="ACT_ASSIST_SMOKE_TARGETS_ZONE",
}
function ACT_ASSIST_SMOKE_TARGETS_ZONE:New(TargetSetUnit,TargetZone)
local self=BASE:Inherit(self,ACT_ASSIST:New())
self.TargetSetUnit=TargetSetUnit
self.TargetZone=TargetZone
return self
end
function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init(FsmSmoke)
self.TargetSetUnit=FsmSmoke.TargetSetUnit
self.TargetZone=FsmSmoke.TargetZone
end
function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init(TargetSetUnit,TargetZone)
self.TargetSetUnit=TargetSetUnit
self.TargetZone=TargetZone
return self
end
function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking(ProcessUnit,From,Event,To)
self.TargetSetUnit:ForEachUnit(
function(SmokeUnit)
if math.random(1,(100*self.TargetSetUnit:Count())/4)<=100 then
SCHEDULER:New(self,
function()
if SmokeUnit:IsAlive()then
SmokeUnit:Smoke(self.SmokeColor,150)
end
end,{},math.random(10,60)
)
end
end
)
end
end
do
USERSOUND={
ClassName="USERSOUND",
}
function USERSOUND:New(UserSoundFileName)
local self=BASE:Inherit(self,BASE:New())
self.UserSoundFileName=UserSoundFileName
return self
end
function USERSOUND:SetFileName(UserSoundFileName)
self.UserSoundFileName=UserSoundFileName
return self
end
function USERSOUND:ToAll()
trigger.action.outSound(self.UserSoundFileName)
return self
end
function USERSOUND:ToCoalition(Coalition)
trigger.action.outSoundForCoalition(Coalition,self.UserSoundFileName)
return self
end
function USERSOUND:ToCountry(Country)
trigger.action.outSoundForCountry(Country,self.UserSoundFileName)
return self
end
function USERSOUND:ToGroup(Group,Delay)
Delay=Delay or 0
if Delay>0 then
SCHEDULER:New(nil,USERSOUND.ToGroup,{self,Group},Delay)
else
trigger.action.outSoundForGroup(Group:GetID(),self.UserSoundFileName)
end
return self
end
function USERSOUND:ToUnit(Unit,Delay)
Delay=Delay or 0
if Delay>0 then
SCHEDULER:New(nil,USERSOUND.ToUnit,{self,Unit},Delay)
else
trigger.action.outSoundForUnit(Unit:GetID(),self.UserSoundFileName)
end
return self
end
end
do
SOUNDBASE={
ClassName="SOUNDBASE",
}
function SOUNDBASE:New()
local self=BASE:Inherit(self,BASE:New())
return self
end
function SOUNDBASE:GetSpeechTime(length,speed,isGoogle)
local maxRateRatio=3
speed=speed or 1.0
isGoogle=isGoogle or false
local speedFactor=1.0
if isGoogle then
speedFactor=speed
else
if speed~=0 then
speedFactor=math.abs(speed)*(maxRateRatio-1)/10+1
end
if speed<0 then
speedFactor=1/speedFactor
end
end
local wpm=math.ceil(100*speedFactor)
local cps=math.floor((wpm*5)/60)
if type(length)=="string"then
length=string.len(length)
end
return math.ceil(length/cps)
end
end
do
SOUNDFILE={
ClassName="SOUNDFILE",
filename=nil,
path="l10n/DEFAULT/",
duration=3,
subtitle=nil,
subduration=0,
useSRS=false,
}
function SOUNDFILE:New(FileName,Path,Duration)
local self=BASE:Inherit(self,BASE:New())
self:SetFileName(FileName)
self:SetPath(Path)
self:SetDuration(Duration)
self:T(string.format("New SOUNDFILE: file name=%s, path=%s",self.filename,self.path))
return self
end
function SOUNDFILE:SetPath(Path)
self.path=Path or"l10n/DEFAULT/"
if not Path and self.useSRS then
self.path=os.getenv('TMP').."\\DCS\\Mission\\l10n\\DEFAULT"
end
local nmax=1000;local n=1
while(self.path:sub(-1)=="/"or self.path:sub(-1)==[[\]])and n<=nmax do
self.path=self.path:sub(1,#self.path-1)
n=n+1
end
self.path=self.path.."/"
return self
end
function SOUNDFILE:GetPath()
local path=self.path or"l10n/DEFAULT/"
return path
end
function SOUNDFILE:SetFileName(FileName)
self.filename=FileName or"Hello World.mp3"
return self
end
function SOUNDFILE:GetFileName()
return self.filename
end
function SOUNDFILE:SetDuration(Duration)
self.duration=Duration or 3
return self
end
function SOUNDFILE:GetDuration()
return self.duration or 3
end
function SOUNDFILE:GetName()
local path=self:GetPath()
local filename=self:GetFileName()
local name=string.format("%s%s",path,filename)
return name
end
function SOUNDFILE:SetPlayWithSRS(Switch)
if Switch==true or Switch==nil then
self.useSRS=true
else
self.useSRS=false
end
return self
end
end
do
SOUNDTEXT={
ClassName="SOUNDTEXT",
}
function SOUNDTEXT:New(Text,Duration)
local self=BASE:Inherit(self,BASE:New())
self:SetText(Text)
self:SetDuration(Duration or STTS.getSpeechTime(Text))
self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec",self.text,self.duration))
return self
end
function SOUNDTEXT:SetText(Text)
self.text=Text or"Hello World!"
return self
end
function SOUNDTEXT:SetDuration(Duration)
self.duration=Duration or 3
return self
end
function SOUNDTEXT:SetGender(Gender)
self.gender=Gender or"female"
return self
end
function SOUNDTEXT:SetCulture(Culture)
self.culture=Culture or"en-GB"
return self
end
function SOUNDTEXT:SetVoice(VoiceName)
self.voice=VoiceName
return self
end
end
RADIO={
ClassName="RADIO",
FileName="",
Frequency=0,
Modulation=radio.modulation.AM,
Subtitle="",
SubtitleDuration=0,
Power=100,
Loop=false,
alias=nil,
}
function RADIO:New(Positionable)
local self=BASE:Inherit(self,BASE:New())
self:F(Positionable)
if Positionable:GetPointVec2()then
self.Positionable=Positionable
return self
end
self:E({error="The passed positionable is invalid, no RADIO created!",positionable=Positionable})
return nil
end
function RADIO:SetAlias(alias)
self.alias=tostring(alias)
return self
end
function RADIO:GetAlias()
return tostring(self.alias)
end
function RADIO:SetFileName(FileName)
self:F2(FileName)
if type(FileName)=="string"then
if FileName:find(".ogg")or FileName:find(".wav")then
if not FileName:find("l10n/DEFAULT/")then
FileName="l10n/DEFAULT/"..FileName
end
self.FileName=FileName
return self
end
end
self:E({"File name invalid. Maybe something wrong with the extension?",FileName})
return self
end
function RADIO:SetFrequency(Frequency)
self:F2(Frequency)
if type(Frequency)=="number"then
self.Frequency=Frequency*1000000
if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then
local commandSetFrequency={
id="SetFrequency",
params={
frequency=self.Frequency,
modulation=self.Modulation,
}
}
self:T2(commandSetFrequency)
self.Positionable:SetCommand(commandSetFrequency)
end
return self
end
self:E({"Frequency is not a number. Frequency unchanged.",Frequency})
return self
end
function RADIO:SetModulation(Modulation)
self:F2(Modulation)
if type(Modulation)=="number"then
if Modulation==radio.modulation.AM or Modulation==radio.modulation.FM then
self.Modulation=Modulation
return self
end
end
self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.",self.Modulation})
return self
end
function RADIO:SetPower(Power)
self:F2(Power)
if type(Power)=="number"then
self.Power=math.floor(math.abs(Power))
else
self:E({"Power is invalid. Power unchanged.",self.Power})
end
return self
end
function RADIO:SetLoop(Loop)
self:F2(Loop)
if type(Loop)=="boolean"then
self.Loop=Loop
return self
end
self:E({"Loop is invalid. Loop unchanged.",self.Loop})
return self
end
function RADIO:SetSubtitle(Subtitle,SubtitleDuration)
self:F2({Subtitle,SubtitleDuration})
if type(Subtitle)=="string"then
self.Subtitle=Subtitle
else
self.Subtitle=""
self:E({"Subtitle is invalid. Subtitle reset.",self.Subtitle})
end
if type(SubtitleDuration)=="number"then
self.SubtitleDuration=SubtitleDuration
else
self.SubtitleDuration=0
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.",self.SubtitleDuration})
end
return self
end
function RADIO:NewGenericTransmission(FileName,Frequency,Modulation,Power,Loop)
self:F({FileName,Frequency,Modulation,Power})
self:SetFileName(FileName)
if Frequency then self:SetFrequency(Frequency)end
if Modulation then self:SetModulation(Modulation)end
if Power then self:SetPower(Power)end
if Loop then self:SetLoop(Loop)end
return self
end
function RADIO:NewUnitTransmission(FileName,Subtitle,SubtitleDuration,Frequency,Modulation,Loop)
self:F({FileName,Subtitle,SubtitleDuration,Frequency,Modulation,Loop})
self:SetFileName(FileName)
if Modulation then
self:SetModulation(Modulation)
end
if Frequency then
self:SetFrequency(Frequency)
end
if Subtitle then
self:SetSubtitle(Subtitle,SubtitleDuration or 0)
end
if Loop then
self:SetLoop(Loop)
end
return self
end
function RADIO:Broadcast(viatrigger)
self:F({viatrigger=viatrigger})
if(self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP")and(not viatrigger)then
self:T("Broadcasting from a UNIT or a GROUP")
local commandTransmitMessage={
id="TransmitMessage",
params={
file=self.FileName,
duration=self.SubtitleDuration,
subtitle=self.Subtitle,
loop=self.Loop,
}}
self:T3(commandTransmitMessage)
self.Positionable:SetCommand(commandTransmitMessage)
else
self:T("Broadcasting from a POSITIONABLE")
trigger.action.radioTransmission(self.FileName,self.Positionable:GetPositionVec3(),self.Modulation,self.Loop,self.Frequency,self.Power,tostring(self.ID))
end
return self
end
function RADIO:StopBroadcast()
self:F()
if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then
local commandStopTransmission={id="StopTransmission",params={}}
self.Positionable:SetCommand(commandStopTransmission)
else
trigger.action.stopRadioTransmission(tostring(self.ID))
end
return self
end
RADIOQUEUE={
ClassName="RADIOQUEUE",
Debugmode=nil,
lid=nil,
frequency=nil,
modulation=nil,
scheduler=nil,
RQid=nil,
queue={},
alias=nil,
dt=nil,
delay=nil,
Tlast=nil,
sendercoord=nil,
sendername=nil,
senderinit=nil,
power=nil,
numbers={},
checking=nil,
schedonce=false,
}
function RADIOQUEUE:New(frequency,modulation,alias)
local self=BASE:Inherit(self,BASE:New())
self.alias=alias or"My Radio"
self.lid=string.format("RADIOQUEUE %s | ",self.alias)
if frequency==nil then
self:E(self.lid.."ERROR: No frequency specified as first parameter!")
return nil
end
self.frequency=frequency*1000000
self.modulation=modulation or radio.modulation.AM
self:SetRadioPower()
self.scheduler=SCHEDULER:New()
self.scheduler:NoTrace()
return self
end
function RADIOQUEUE:Start(delay,dt)
self.delay=delay or 1
self.dt=dt or 0.01
self:I(self.lid..string.format("Starting RADIOQUEUE %s on Frequency %.2f MHz [modulation=%d] in %.1f seconds (dt=%.3f sec)",self.alias,self.frequency/1000000,self.modulation,self.delay,self.dt))
if self.schedonce then
self:_CheckRadioQueueDelayed(self.delay)
else
self.RQid=self.scheduler:Schedule(nil,RADIOQUEUE._CheckRadioQueue,{self},self.delay,self.dt)
end
return self
end
function RADIOQUEUE:Stop()
self:I(self.lid.."Stopping RADIOQUEUE.")
self.scheduler:Stop(self.RQid)
self.queue={}
return self
end
function RADIOQUEUE:SetSenderCoordinate(coordinate)
self.sendercoord=coordinate
return self
end
function RADIOQUEUE:SetSenderUnitName(name)
self.sendername=name
return self
end
function RADIOQUEUE:SetRadioPower(power)
self.power=power or 100
return self
end
function RADIOQUEUE:SetSRS(PathToSRS,Port)
self.msrs=MSRS:New(PathToSRS,self.frequency/1000000,self.modulation)
self.msrs:SetPort(Port)
return self
end
function RADIOQUEUE:SetDigit(digit,filename,duration,path,subtitle,subduration)
local transmission={}
transmission.filename=filename
transmission.duration=duration
transmission.path=path or"l10n/DEFAULT/"
transmission.subtitle=nil
transmission.subduration=nil
if type(digit)=="number"then
digit=tostring(digit)
end
self.numbers[digit]=transmission
return self
end
function RADIOQUEUE:AddTransmission(transmission)
self:F({transmission=transmission})
transmission.isplaying=false
transmission.Tstarted=nil
table.insert(self.queue,transmission)
if self.schedonce and not self.checking then
self:_CheckRadioQueueDelayed()
end
return self
end
function RADIOQUEUE:NewTransmission(filename,duration,path,tstart,interval,subtitle,subduration)
if not filename then
self:E(self.lid.."ERROR: No filename specified.")
return nil
end
if type(filename)~="string"then
self:E(self.lid.."ERROR: Filename specified is NOT a string.")
return nil
end
if not duration then
self:E(self.lid.."ERROR: No duration of transmission specified.")
return nil
end
if type(duration)~="number"then
self:E(self.lid.."ERROR: Duration specified is NOT a number.")
return nil
end
local transmission={}
transmission.filename=filename
transmission.duration=duration
transmission.path=path or"l10n/DEFAULT/"
transmission.Tplay=tstart or timer.getAbsTime()
transmission.subtitle=subtitle
transmission.interval=interval or 0
if transmission.subtitle then
transmission.subduration=subduration or 5
else
transmission.subduration=nil
end
self:AddTransmission(transmission)
return transmission
end
function RADIOQUEUE:AddSoundFile(soundfile,tstart,interval)
local transmission=self:NewTransmission(soundfile:GetFileName(),soundfile.duration,soundfile:GetPath(),tstart,interval,soundfile.subtitle,soundfile.subduration)
transmission.soundfile=soundfile
return self
end
function RADIOQUEUE:AddSoundText(soundtext,tstart,interval)
local transmission=self:NewTransmission("SoundText.ogg",soundtext.duration,nil,tstart,interval,soundtext.subtitle,soundtext.subduration)
transmission.soundtext=soundtext
return self
end
function RADIOQUEUE:Number2Transmission(number,delay,interval)
local numbers=UTILS.GetCharacters(number)
local wait=0
for i=1,#numbers do
local n=numbers[i]
local transmission=UTILS.DeepCopy(self.numbers[n])
transmission.Tplay=timer.getAbsTime()+(delay or 0)
if interval and i==1 then
transmission.interval=interval
end
self:AddTransmission(transmission)
wait=wait+transmission.duration
end
return wait
end
function RADIOQUEUE:Broadcast(transmission)
if((transmission.soundfile and transmission.soundfile.useSRS)or transmission.soundtext)and self.msrs then
self:_BroadcastSRS(transmission)
return
end
local sender=self:_GetRadioSender()
local filename=string.format("%s%s",transmission.path,transmission.filename)
if sender then
self:T(self.lid..string.format("Broadcasting from aircraft %s",sender:GetName()))
if not self.senderinit then
local commandFrequency={
id="SetFrequency",
params={
frequency=self.frequency,
modulation=self.modulation,
}}
sender:SetCommand(commandFrequency)
self.senderinit=true
end
local subtitle=nil
local duration=nil
if transmission.subtitle and transmission.subduration and transmission.subduration>0 then
subtitle=transmission.subtitle
duration=transmission.subduration
end
local commandTransmit={
id="TransmitMessage",
params={
file=filename,
duration=duration,
subtitle=subtitle,
loop=false,
}}
sender:SetCommand(commandTransmit)
if self.Debugmode then
local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s",filename,self.frequency/1000000,transmission.duration,transmission.subtitle or"")
MESSAGE:New(text,2,"RADIOQUEUE "..self.alias):ToAll()
end
else
self:T(self.lid..string.format("Broadcasting via trigger.action.radioTransmission()."))
local vec3=nil
if self.sendername then
vec3=self:_GetRadioSenderCoord()
end
if self.sendercoord and not vec3 then
vec3=self.sendercoord:GetVec3()
end
if vec3 then
self:T("Sending")
self:T({filename=filename,vec3=vec3,modulation=self.modulation,frequency=self.frequency,power=self.power})
trigger.action.radioTransmission(filename,vec3,self.modulation,false,self.frequency,self.power)
if self.Debugmode then
local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s",filename,self.frequency/1000000,transmission.duration,transmission.subtitle or"")
MESSAGE:New(string.format(text,filename,transmission.duration,transmission.subtitle or""),5,"RADIOQUEUE "..self.alias):ToAll()
end
end
end
end
function RADIOQUEUE:_BroadcastSRS(transmission)
if transmission.soundfile and transmission.soundfile.useSRS then
self.msrs:PlaySoundFile(transmission.soundfile)
elseif transmission.soundtext then
self.msrs:PlaySoundText(transmission.soundtext)
end
end
function RADIOQUEUE:_CheckRadioQueueDelayed(delay)
self.checking=true
self:ScheduleOnce(delay or self.dt,RADIOQUEUE._CheckRadioQueue,self)
end
function RADIOQUEUE:_CheckRadioQueue()
if#self.queue==0 then
self.checking=false
return
end
local time=timer.getAbsTime()
local playing=false
local next=nil
local remove=nil
for i,_transmission in ipairs(self.queue)do
local transmission=_transmission
if time>=transmission.Tplay then
if transmission.isplaying then
if time>=transmission.Tstarted+transmission.duration then
transmission.isplaying=false
remove=i
self.Tlast=time
else
playing=true
end
else
local Tlast=self.Tlast
if transmission.interval==nil then
if next==nil then
next=transmission
end
else
if Tlast==nil or time-Tlast>=transmission.interval then
next=transmission
else
end
end
if next or Tlast then
break
end
end
else
end
end
if next~=nil and not playing then
self:Broadcast(next)
next.isplaying=true
next.Tstarted=time
end
if remove then
table.remove(self.queue,remove)
end
if self.schedonce then
self:_CheckRadioQueueDelayed()
end
end
function RADIOQUEUE:_GetRadioSender()
local sender=nil
if self.sendername then
sender=UNIT:FindByName(self.sendername)
if sender and sender:IsAlive()and(sender:IsAir()or sender:IsGround())then
return sender
end
end
return nil
end
function RADIOQUEUE:_GetRadioSenderCoord()
local vec3=nil
if self.sendername then
local sender=UNIT:FindByName(self.sendername)
if sender and sender:IsAlive()then
return sender:GetVec3()
end
local sender=STATIC:FindByName(self.sendername,false)
if sender then
return sender:GetVec3()
end
end
return nil
end
RADIOSPEECH={
ClassName="RADIOSPEECH",
Vocabulary={
EN={},
DE={},
RU={},
}
}
RADIOSPEECH.Vocabulary.EN={
["1"]={"1",0.25},
["2"]={"2",0.25},
["3"]={"3",0.30},
["4"]={"4",0.35},
["5"]={"5",0.35},
["6"]={"6",0.42},
["7"]={"7",0.38},
["8"]={"8",0.20},
["9"]={"9",0.32},
["10"]={"10",0.35},
["11"]={"11",0.40},
["12"]={"12",0.42},
["13"]={"13",0.38},
["14"]={"14",0.42},
["15"]={"15",0.42},
["16"]={"16",0.52},
["17"]={"17",0.59},
["18"]={"18",0.40},
["19"]={"19",0.47},
["20"]={"20",0.38},
["30"]={"30",0.29},
["40"]={"40",0.35},
["50"]={"50",0.32},
["60"]={"60",0.44},
["70"]={"70",0.48},
["80"]={"80",0.26},
["90"]={"90",0.36},
["100"]={"100",0.55},
["200"]={"200",0.55},
["300"]={"300",0.61},
["400"]={"400",0.60},
["500"]={"500",0.61},
["600"]={"600",0.65},
["700"]={"700",0.70},
["800"]={"800",0.54},
["900"]={"900",0.60},
["1000"]={"1000",0.60},
["2000"]={"2000",0.61},
["3000"]={"3000",0.64},
["4000"]={"4000",0.62},
["5000"]={"5000",0.69},
["6000"]={"6000",0.69},
["7000"]={"7000",0.75},
["8000"]={"8000",0.59},
["9000"]={"9000",0.65},
["chevy"]={"chevy",0.35},
["colt"]={"colt",0.35},
["springfield"]={"springfield",0.65},
["dodge"]={"dodge",0.35},
["enfield"]={"enfield",0.5},
["ford"]={"ford",0.32},
["pontiac"]={"pontiac",0.55},
["uzi"]={"uzi",0.28},
["degrees"]={"degrees",0.5},
["kilometers"]={"kilometers",0.65},
["km"]={"kilometers",0.65},
["miles"]={"miles",0.45},
["meters"]={"meters",0.41},
["mi"]={"miles",0.45},
["feet"]={"feet",0.29},
["br"]={"br",1.1},
["bra"]={"bra",0.3},
["returning to base"]={"returning_to_base",0.85},
["on route to ground target"]={"on_route_to_ground_target",1.05},
["intercepting bogeys"]={"intercepting_bogeys",1.00},
["engaging ground target"]={"engaging_ground_target",1.20},
["engaging bogeys"]={"engaging_bogeys",0.81},
["wheels up"]={"wheels_up",0.42},
["landing at base"]={"landing at base",0.8},
["patrolling"]={"patrolling",0.55},
["for"]={"for",0.31},
["and"]={"and",0.31},
["at"]={"at",0.3},
["dot"]={"dot",0.26},
["defender"]={"defender",0.45},
}
RADIOSPEECH.Vocabulary.RU={
["1"]={"1",0.34},
["2"]={"2",0.30},
["3"]={"3",0.23},
["4"]={"4",0.51},
["5"]={"5",0.31},
["6"]={"6",0.44},
["7"]={"7",0.25},
["8"]={"8",0.43},
["9"]={"9",0.45},
["10"]={"10",0.53},
["11"]={"11",0.66},
["12"]={"12",0.70},
["13"]={"13",0.66},
["14"]={"14",0.80},
["15"]={"15",0.65},
["16"]={"16",0.75},
["17"]={"17",0.74},
["18"]={"18",0.85},
["19"]={"19",0.80},
["20"]={"20",0.58},
["30"]={"30",0.51},
["40"]={"40",0.51},
["50"]={"50",0.67},
["60"]={"60",0.76},
["70"]={"70",0.68},
["80"]={"80",0.84},
["90"]={"90",0.71},
["100"]={"100",0.35},
["200"]={"200",0.59},
["300"]={"300",0.53},
["400"]={"400",0.70},
["500"]={"500",0.50},
["600"]={"600",0.58},
["700"]={"700",0.64},
["800"]={"800",0.77},
["900"]={"900",0.75},
["1000"]={"1000",0.87},
["2000"]={"2000",0.83},
["3000"]={"3000",0.84},
["4000"]={"4000",1.00},
["5000"]={"5000",0.77},
["6000"]={"6000",0.90},
["7000"]={"7000",0.77},
["8000"]={"8000",0.92},
["9000"]={"9000",0.87},
["градусы"]={"degrees",0.5},
["километры"]={"kilometers",0.65},
["km"]={"kilometers",0.65},
["мили"]={"miles",0.45},
["mi"]={"miles",0.45},
["метров"]={"meters",0.41},
["m"]={"meters",0.41},
["ноги"]={"feet",0.37},
["br"]={"br",1.1},
["bra"]={"bra",0.3},
["возвращение на базу"]={"returning_to_base",1.40},
["на пути к наземной цели"]={"on_route_to_ground_target",1.45},
["перехват боги"]={"intercepting_bogeys",1.22},
["поражение наземной цели"]={"engaging_ground_target",1.53},
["привлечение болотных птиц"]={"engaging_bogeys",1.68},
["колёса вверх..."]={"wheels_up",0.92},
["посадка на базу"]={"landing at base",1.04},
["патрулирование"]={"patrolling",0.96},
["для"]={"for",0.27},
["и"]={"and",0.17},
["на сайте"]={"at",0.19},
["точка"]={"dot",0.51},
["защитник"]={"defender",0.45},
}
function RADIOSPEECH:New(frequency,modulation)
local self=BASE:Inherit(self,RADIOQUEUE:New(frequency,modulation))
self.Language="EN"
self:BuildTree()
return self
end
function RADIOSPEECH:SetLanguage(Langauge)
self.Language=Langauge
end
function RADIOSPEECH:AddSentenceToSpeech(RemainingSentence,Speech,Sentence,Data)
self:I({RemainingSentence,Speech,Sentence,Data})
local Token,RemainingSentence=RemainingSentence:match("^ *([^ ]+)(.*)")
self:I({Token=Token,RemainingSentence=RemainingSentence})
if Token then
if not Speech[Token]then
Speech[Token]={}
if RemainingSentence and RemainingSentence~=""then
Speech[Token].Next={}
self:AddSentenceToSpeech(RemainingSentence,Speech[Token].Next,Sentence,Data)
else
Speech[Token].Sentence=Sentence
Speech[Token].Data=Data
end
end
end
end
function RADIOSPEECH:BuildTree()
self.Speech={}
for Language,Sentences in pairs(self.Vocabulary)do
self:I({Language=Language,Sentences=Sentences})
self.Speech[Language]={}
for Sentence,Data in pairs(Sentences)do
self:I({Sentence=Sentence,Data=Data})
self:AddSentenceToSpeech(Sentence,self.Speech[Language],Sentence,Data)
end
end
self:I({Speech=self.Speech})
return self
end
function RADIOSPEECH:SpeakWords(Sentence,Speech,Language)
local OriginalSentence=Sentence
local Word,RemainderSentence=Sentence:match("^[., ]*([^ .,]+)(.*)")
self:I({Word=Word,Speech=Speech[Word],RemainderSentence=RemainderSentence})
if Word then
if Word~=""and tonumber(Word)==nil then
Word=Word:lower()
if Speech[Word]then
if Speech[Word].Next==nil then
self:I({Sentence=Speech[Word].Sentence,Data=Speech[Word].Data})
self:NewTransmission(Speech[Word].Data[1]..".wav",Speech[Word].Data[2],Language.."/")
else
if RemainderSentence and RemainderSentence~=""then
return self:SpeakWords(RemainderSentence,Speech[Word].Next,Language)
end
end
end
return RemainderSentence
end
return OriginalSentence
else
return""
end
end
function RADIOSPEECH:SpeakDigits(Sentence,Speech,Langauge)
local OriginalSentence=Sentence
local Digits,RemainderSentence=Sentence:match("^[., ]*([^ .,]+)(.*)")
self:I({Digits=Digits,Speech=Speech[Digits],RemainderSentence=RemainderSentence})
if Digits then
if Digits~=""and tonumber(Digits)~=nil then
local Number=tonumber(Digits)
local Multiple=nil
while Number>=0 do
if Number>1000 then
Multiple=math.floor(Number/1000)*1000
elseif Number>100 then
Multiple=math.floor(Number/100)*100
elseif Number>20 then
Multiple=math.floor(Number/10)*10
elseif Number>=0 then
Multiple=Number
end
Sentence=tostring(Multiple)
if Speech[Sentence]then
self:I({Speech=Speech[Sentence].Sentence,Data=Speech[Sentence].Data})
self:NewTransmission(Speech[Sentence].Data[1]..".wav",Speech[Sentence].Data[2],Langauge.."/")
end
Number=Number-Multiple
Number=(Number==0)and-1 or Number
end
return RemainderSentence
end
return OriginalSentence
else
return""
end
end
function RADIOSPEECH:Speak(Sentence,Language)
self:I({Sentence,Language})
local Language=Language or"EN"
self:I({Language=Language})
local Speech=self.Speech[Language]
self:I({Speech=Speech,Language=Language})
self:NewTransmission("_In.wav",0.52,Language.."/")
repeat
Sentence=self:SpeakWords(Sentence,Speech,Language)
self:I({Sentence=Sentence})
Sentence=self:SpeakDigits(Sentence,Speech,Language)
self:I({Sentence=Sentence})
until not Sentence or Sentence==""
self:NewTransmission("_Out.wav",0.28,Language.."/")
end
MSRS={
ClassName="MSRS",
lid=nil,
port=5002,
name="MSRS",
frequencies={},
modulations={},
coalition=0,
gender="female",
culture=nil,
voice=nil,
volume=1,
speed=1,
coordinate=nil,
Label="ROBOT",
}
MSRS.version="0.1.1"
MSRS.Voices={
Microsoft={
["Hedda"]="Microsoft Hedda Desktop",
["Hazel"]="Microsoft Hazel Desktop",
["David"]="Microsoft David Desktop",
["Zira"]="Microsoft Zira Desktop",
["Hortense"]="Microsoft Hortense Desktop",
},
Google={
Standard={
["en_AU_Standard_A"]='en-AU-Standard-A',
["en_AU_Standard_B"]='en-AU-Standard-B',
["en_AU_Standard_C"]='en-AU-Standard-C',
["en_AU_Standard_D"]='en-AU-Standard-D',
["en_IN_Standard_A"]='en-IN-Standard-A',
["en_IN_Standard_B"]='en-IN-Standard-B',
["en_IN_Standard_C"]='en-IN-Standard-C',
["en_IN_Standard_D"]='en-IN-Standard-D',
["en_GB_Standard_A"]='en-GB-Standard-A',
["en_GB_Standard_B"]='en-GB-Standard-B',
["en_GB_Standard_C"]='en-GB-Standard-C',
["en_GB_Standard_D"]='en-GB-Standard-D',
["en_GB_Standard_F"]='en-GB-Standard-F',
["en_US_Standard_A"]='en-US-Standard-A',
["en_US_Standard_B"]='en-US-Standard-B',
["en_US_Standard_C"]='en-US-Standard-C',
["en_US_Standard_D"]='en-US-Standard-D',
["en_US_Standard_E"]='en-US-Standard-E',
["en_US_Standard_F"]='en-US-Standard-F',
["en_US_Standard_G"]='en-US-Standard-G',
["en_US_Standard_H"]='en-US-Standard-H',
["en_US_Standard_I"]='en-US-Standard-I',
["en_US_Standard_J"]='en-US-Standard-J',
["fr_FR_Standard_A"]="fr-FR-Standard-A",
["fr_FR_Standard_B"]="fr-FR-Standard-B",
["fr_FR_Standard_C"]="fr-FR-Standard-C",
["fr_FR_Standard_D"]="fr-FR-Standard-D",
["fr_FR_Standard_E"]="fr-FR-Standard-E",
["de_DE_Standard_A"]="de-DE-Standard-A",
["de_DE_Standard_B"]="de-DE-Standard-B",
["de_DE_Standard_C"]="de-DE-Standard-C",
["de_DE_Standard_D"]="de-DE-Standard-D",
["de_DE_Standard_E"]="de-DE-Standard-E",
["de_DE_Standard_F"]="de-DE-Standard-F",
["es_ES_Standard_A"]="es-ES-Standard-A",
["es_ES_Standard_B"]="es-ES-Standard-B",
["es_ES_Standard_C"]="es-ES-Standard-C",
["es_ES_Standard_D"]="es-ES-Standard-D",
["it_IT_Standard_A"]="it-IT-Standard-A",
["it_IT_Standard_B"]="it-IT-Standard-B",
["it_IT_Standard_C"]="it-IT-Standard-C",
["it_IT_Standard_D"]="it-IT-Standard-D",
},
Wavenet={
["en_AU_Wavenet_A"]='en-AU-Wavenet-A',
["en_AU_Wavenet_B"]='en-AU-Wavenet-B',
["en_AU_Wavenet_C"]='en-AU-Wavenet-C',
["en_AU_Wavenet_D"]='en-AU-Wavenet-D',
["en_IN_Wavenet_A"]='en-IN-Wavenet-A',
["en_IN_Wavenet_B"]='en-IN-Wavenet-B',
["en_IN_Wavenet_C"]='en-IN-Wavenet-C',
["en_IN_Wavenet_D"]='en-IN-Wavenet-D',
["en_GB_Wavenet_A"]='en-GB-Wavenet-A',
["en_GB_Wavenet_B"]='en-GB-Wavenet-B',
["en_GB_Wavenet_C"]='en-GB-Wavenet-C',
["en_GB_Wavenet_D"]='en-GB-Wavenet-D',
["en_GB_Wavenet_F"]='en-GB-Wavenet-F',
["en_US_Wavenet_A"]='en-US-Wavenet-A',
["en_US_Wavenet_B"]='en-US-Wavenet-B',
["en_US_Wavenet_C"]='en-US-Wavenet-C',
["en_US_Wavenet_D"]='en-US-Wavenet-D',
["en_US_Wavenet_E"]='en-US-Wavenet-E',
["en_US_Wavenet_F"]='en-US-Wavenet-F',
["en_US_Wavenet_G"]='en-US-Wavenet-G',
["en_US_Wavenet_H"]='en-US-Wavenet-H',
["en_US_Wavenet_I"]='en-US-Wavenet-I',
["en_US_Wavenet_J"]='en-US-Wavenet-J',
["fr_FR_Wavenet_A"]="fr-FR-Wavenet-A",
["fr_FR_Wavenet_B"]="fr-FR-Wavenet-B",
["fr_FR_Wavenet_C"]="fr-FR-Wavenet-C",
["fr_FR_Wavenet_D"]="fr-FR-Wavenet-D",
["fr_FR_Wavenet_E"]="fr-FR-Wavenet-E",
["de_DE_Wavenet_A"]="de-DE-Wavenet-A",
["de_DE_Wavenet_B"]="de-DE-Wavenet-B",
["de_DE_Wavenet_C"]="de-DE-Wavenet-C",
["de_DE_Wavenet_D"]="de-DE-Wavenet-D",
["de_DE_Wavenet_E"]="de-DE-Wavenet-E",
["de_DE_Wavenet_F"]="de-DE-Wavenet-F",
["es_ES_Wavenet_B"]="es-ES-Wavenet-B",
["es_ES_Wavenet_C"]="es-ES-Wavenet-C",
["es_ES_Wavenet_D"]="es-ES-Wavenet-D",
["it_IT_Wavenet_A"]="it-IT-Wavenet-A",
["it_IT_Wavenet_B"]="it-IT-Wavenet-B",
["it_IT_Wavenet_C"]="it-IT-Wavenet-C",
["it_IT_Wavenet_D"]="it-IT-Wavenet-D",
},
},
}
function MSRS:New(PathToSRS,Frequency,Modulation,Volume)
Frequency=Frequency or 143
Modulation=Modulation or radio.modulation.AM
local self=BASE:Inherit(self,BASE:New())
self:SetPath(PathToSRS)
self:SetPort()
self:SetFrequencies(Frequency)
self:SetModulations(Modulation)
self:SetGender()
self:SetCoalition()
self:SetLabel()
self:SetVolume()
self.lid=string.format("%s-%s | ",self.name,self.version)
if not io or not os then
self:E(self.lid.."***** ERROR - io or os NOT desanitized! MSRS will not work!")
end
return self
end
function MSRS:SetPath(Path)
if Path==nil then
self:E("ERROR: No path to SRS directory specified!")
return nil
end
self.path=Path
local n=1;local nmax=1000
while(self.path:sub(-1)=="/"or self.path:sub(-1)==[[\]])and n<=nmax do
self.path=self.path:sub(1,#self.path-1)
n=n+1
end
self:T(string.format("SRS path=%s",self:GetPath()))
return self
end
function MSRS:GetPath()
return self.path
end
function MSRS:SetVolume(Volume)
local volume=Volume or 1
if volume>1 then volume=1 elseif volume<0 then volume=0 end
self.volume=volume
return self
end
function MSRS:GetVolume()
return self.volume
end
function MSRS:SetLabel(Label)
self.Label=Label or"ROBOT"
return self
end
function MSRS:GetLabel()
return self.Label
end
function MSRS:SetPort(Port)
self.port=Port or 5002
return self
end
function MSRS:GetPort()
return self.port
end
function MSRS:SetCoalition(Coalition)
self.coalition=Coalition or 0
return self
end
function MSRS:GetCoalition()
return self.coalition
end
function MSRS:SetFrequencies(Frequencies)
if type(Frequencies)~="table"then
Frequencies={Frequencies}
end
self.frequencies=Frequencies
return self
end
function MSRS:AddFrequencies(Frequencies)
if type(Frequencies)~="table"then
Frequencies={Frequencies}
end
for _,_freq in pairs(Frequencies)do
table.insert(self.frequencies,_freq)
end
return self
end
function MSRS:GetFrequencies()
return self.frequencies
end
function MSRS:SetModulations(Modulations)
if type(Modulations)~="table"then
Modulations={Modulations}
end
self.modulations=Modulations
return self
end
function MSRS:AddModulations(Modulations)
if type(Modulations)~="table"then
Modulations={Modulations}
end
for _,_mod in pairs(Modulations)do
table.insert(self.modulations,_mod)
end
return self
end
function MSRS:GetModulations()
return self.modulations
end
function MSRS:SetGender(Gender)
Gender=Gender or"female"
self.gender=Gender:lower()
self:T("Setting gender to "..tostring(self.gender))
return self
end
function MSRS:SetCulture(Culture)
self.culture=Culture
return self
end
function MSRS:SetVoice(Voice)
self.voice=Voice
return self
end
function MSRS:SetCoordinate(Coordinate)
self.coordinate=Coordinate
return self
end
function MSRS:SetGoogle(PathToCredentials)
self.google=PathToCredentials
return self
end
function MSRS:Help()
local path=self:GetPath()or STTS.DIRECTORY
local exe=STTS.EXECUTABLE or"DCS-SR-ExternalAudio.exe"
local filename=os.getenv('TMP').."\\MSRS-help-"..STTS.uuid()..".txt"
local command=string.format("%s/%s --help > %s",path,exe,filename)
os.execute(command)
local f=assert(io.open(filename,"rb"))
local data=f:read("*all")
f:close()
env.info("SRS STTS help output:")
env.info("======================================================================")
env.info(data)
env.info("======================================================================")
return self
end
function MSRS:PlaySoundFile(Soundfile,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MSRS.PlaySoundFile,self,Soundfile,0)
else
local soundfile=Soundfile:GetName()
local command=self:_GetCommand()
command=command..' --file="'..tostring(soundfile)..'"'
self:_ExecCommand(command)
end
return self
end
function MSRS:PlaySoundText(SoundText,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MSRS.PlaySoundText,self,SoundText,0)
else
local command=self:_GetCommand(nil,nil,nil,SoundText.gender,SoundText.voice,SoundText.culture,SoundText.volume,SoundText.speed)
command=command..string.format(" --text=\"%s\"",tostring(SoundText.text))
self:_ExecCommand(command)
end
return self
end
function MSRS:PlayText(Text,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MSRS.PlayText,self,Text,0)
else
local command=self:_GetCommand()
command=command..string.format(" --text=\"%s\"",tostring(Text))
self:_ExecCommand(command)
end
return self
end
function MSRS:PlayTextExt(Text,Delay,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MSRS.PlayTextExt,self,Text,0,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label)
else
if Frequencies and type(Frequencies)~="table"then
Frequencies={Frequencies}
end
if Modulations and type(Modulations)~="table"then
Modulations={Modulations}
end
local command=self:_GetCommand(Frequencies,Modulations,nil,Gender,Voice,Culture,Volume,nil,nil,Label)
command=command..string.format(" --text=\"%s\"",tostring(Text))
self:_ExecCommand(command)
end
return self
end
function MSRS:PlayTextFile(TextFile,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MSRS.PlayTextFile,self,TextFile,0)
else
local exists=UTILS.FileExists(TextFile)
if not exists then
self:E("ERROR: MSRS Text file does not exist! File="..tostring(TextFile))
return self
end
local command=self:_GetCommand()
command=command..string.format(" --textFile=\"%s\"",tostring(TextFile))
self:T(string.format("MSRS TextFile command=%s",command))
local l=string.len(command)
self:_ExecCommand(command)
end
return self
end
function MSRS:_ExecCommand(command)
local filename=os.getenv('TMP').."\\MSRS-"..STTS.uuid()..".bat"
local script=io.open(filename,"w+")
script:write(command.." && exit")
script:close()
command=string.format('start /b "" "%s"',filename)
local res=nil
if true then
local filenvbs=os.getenv('TMP').."\\MSRS-"..STTS.uuid()..".vbs"
local script=io.open(filenvbs,"w+")
script:write(string.format('Dim WinScriptHost\n'))
script:write(string.format('Set WinScriptHost = CreateObject("WScript.Shell")\n'))
script:write(string.format('WinScriptHost.Run Chr(34) & "%s" & Chr(34), 0\n',filename))
script:write(string.format('Set WinScriptHost = Nothing'))
script:close()
local runvbs=string.format('cscript.exe //Nologo //B "%s"',filenvbs)
self:T("MSRS execute command="..command)
self:T("MSRS execute VBS command="..runvbs)
res=os.execute(runvbs)
timer.scheduleFunction(os.remove,filename,timer.getTime()+1)
timer.scheduleFunction(os.remove,filenvbs,timer.getTime()+1)
else
self:T("MSRS execute command="..command)
res=os.execute(command)
timer.scheduleFunction(os.remove,filename,timer.getTime()+1)
end
return res
end
function MSRS:_GetLatLongAlt(Coordinate)
local lat,lon,alt=coord.LOtoLL(Coordinate)
return lat,lon,math.floor(alt)
end
function MSRS:_GetCommand(freqs,modus,coal,gender,voice,culture,volume,speed,port,label)
local path=self:GetPath()or STTS.DIRECTORY
local exe=STTS.EXECUTABLE or"DCS-SR-ExternalAudio.exe"
freqs=table.concat(freqs or self.frequencies,",")
modus=table.concat(modus or self.modulations,",")
coal=coal or self.coalition
gender=gender or self.gender
voice=voice or self.voice
culture=culture or self.culture
volume=volume or self.volume
speed=speed or self.speed
port=port or self.port
label=label or self.Label
modus=modus:gsub("0","AM")
modus=modus:gsub("1","FM")
local command=string.format('"%s\\%s" -f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"',path,exe,freqs,modus,coal,port,label,volume)
if voice then
command=command..string.format(" --voice=\"%s\"",tostring(voice))
else
if gender and gender~="female"then
command=command..string.format(" -g %s",tostring(gender))
end
if culture and culture~="en-GB"then
command=command..string.format(" -l %s",tostring(culture))
end
end
if self.coordinate then
local lat,lon,alt=self:_GetLatLongAlt(self.coordinate)
command=command..string.format(" -L %.4f -O %.4f -A %d",lat,lon,alt)
end
if self.google then
command=command..string.format(' --ssml -G "%s"',self.google)
end
self:T("MSRS command="..command)
return command
end
MSRSQUEUE={
ClassName="MSRSQUEUE",
Debugmode=nil,
lid=nil,
queue={},
alias=nil,
dt=nil,
Tlast=nil,
checking=nil,
}
function MSRSQUEUE:New(alias)
local self=BASE:Inherit(self,BASE:New())
self.alias=alias or"My Radio"
self.dt=1.0
self.lid=string.format("MSRSQUEUE %s | ",self.alias)
return self
end
function MSRSQUEUE:Clear()
self:I(self.lid.."Clearning MSRSQUEUE")
self.queue={}
return self
end
function MSRSQUEUE:AddTransmission(transmission)
transmission.isplaying=false
transmission.Tstarted=nil
table.insert(self.queue,transmission)
if not self.checking then
self:_CheckRadioQueue()
end
return self
end
function MSRSQUEUE:SetTransmitOnlyWithPlayers(Switch)
self.TransmitOnlyWithPlayers=Switch
if Switch==false or Switch==nil then
if self.PlayerSet then
self.PlayerSet:FilterStop()
end
self.PlayerSet=nil
else
self.PlayerSet=SET_CLIENT:New():FilterStart()
end
return self
end
function MSRSQUEUE:NewTransmission(text,duration,msrs,tstart,interval,subgroups,subtitle,subduration,frequency,modulation)
if self.TransmitOnlyWithPlayers then
if self.PlayerSet and self.PlayerSet:CountAlive()==0 then
return self
end
end
if not text then
self:E(self.lid.."ERROR: No text specified.")
return nil
end
if type(text)~="string"then
self:E(self.lid.."ERROR: Text specified is NOT a string.")
return nil
end
local transmission={}
transmission.text=text
transmission.duration=duration or STTS.getSpeechTime(text)
transmission.msrs=msrs
transmission.Tplay=tstart or timer.getAbsTime()
transmission.subtitle=subtitle
transmission.interval=interval or 0
transmission.frequency=frequency
transmission.modulation=modulation
transmission.subgroups=subgroups
if transmission.subtitle then
transmission.subduration=subduration or transmission.duration
else
transmission.subduration=0
end
self:AddTransmission(transmission)
return transmission
end
function MSRSQUEUE:Broadcast(transmission)
if transmission.frequency then
transmission.msrs:PlayTextExt(transmission.text,nil,transmission.frequency,transmission.modulation,Gender,Culture,Voice,Volume,Label)
else
transmission.msrs:PlayText(transmission.text)
end
local function texttogroup(gid)
trigger.action.outTextForGroup(gid,transmission.subtitle,transmission.subduration,true)
end
if transmission.subgroups and#transmission.subgroups>0 then
for _,_group in pairs(transmission.subgroups)do
local group=_group
if group and group:IsAlive()then
local gid=group:GetID()
self:ScheduleOnce(4,texttogroup,gid)
end
end
end
end
function MSRSQUEUE:CalcTransmisstionDuration()
local Tnow=timer.getAbsTime()
local T=0
for _,_transmission in pairs(self.queue)do
local transmission=_transmission
if transmission.isplaying then
local dt=Tnow-transmission.Tstarted
T=T+transmission.duration-dt
else
T=T+transmission.duration
end
end
return T
end
function MSRSQUEUE:_CheckRadioQueue(delay)
local N=#self.queue
self:T2(self.lid..string.format("Check radio queue %s: delay=%.3f sec, N=%d, checking=%s",self.alias,delay or 0,N,tostring(self.checking)))
if delay and delay>0 then
self:ScheduleOnce(delay,MSRSQUEUE._CheckRadioQueue,self)
self.checking=true
else
if N==0 then
self:T(self.lid..string.format("Check radio queue %s empty ==> disable checking",self.alias))
self.checking=false
return
end
local time=timer.getAbsTime()
self.checking=true
local dt=self.dt
local playing=false
local next=nil
local remove=nil
for i,_transmission in ipairs(self.queue)do
local transmission=_transmission
if time>=transmission.Tplay then
if transmission.isplaying then
if time>=transmission.Tstarted+transmission.duration then
transmission.isplaying=false
remove=i
self.Tlast=time
else
playing=true
dt=transmission.duration-(time-transmission.Tstarted)
end
else
local Tlast=self.Tlast
if transmission.interval==nil then
if next==nil then
next=transmission
end
else
if Tlast==nil or time-Tlast>=transmission.interval then
next=transmission
else
end
end
if next or Tlast then
break
end
end
else
end
end
if next~=nil and not playing then
self:T(self.lid..string.format("Broadcasting text=\"%s\" at T=%.3f",next.text,time))
self:Broadcast(next)
next.isplaying=true
next.Tstarted=time
dt=next.duration
end
if remove then
table.remove(self.queue,remove)
N=N-1
if#self.queue==0 then
self:T(self.lid..string.format("Check radio queue %s empty ==> disable checking",self.alias))
self.checking=false
return
end
end
self:_CheckRadioQueue(dt)
end
end
COMMANDCENTER={
ClassName="COMMANDCENTER",
CommandCenterName="",
CommandCenterCoalition=nil,
CommandCenterPositionable=nil,
Name="",
ReferencePoints={},
ReferenceNames={},
CommunicationMode="80",
}
COMMANDCENTER.AutoAssignMethods={
["Random"]=1,
["Distance"]=2,
["Priority"]=3,
}
function COMMANDCENTER:New(CommandCenterPositionable,CommandCenterName)
local self=BASE:Inherit(self,BASE:New())
self.CommandCenterPositionable=CommandCenterPositionable
self.CommandCenterName=CommandCenterName or CommandCenterPositionable:GetName()
self.CommandCenterCoalition=CommandCenterPositionable:GetCoalition()
self.Missions={}
self:SetAutoAssignTasks(false)
self:SetAutoAcceptTasks(true)
self:SetAutoAssignMethod(COMMANDCENTER.AutoAssignMethods.Distance)
self:SetFlashStatus(false)
self:SetMessageDuration(10)
self:HandleEvent(EVENTS.Birth,
function(self,EventData)
if EventData.IniObjectCategory==1 then
local EventGroup=GROUP:Find(EventData.IniDCSGroup)
if EventGroup and EventGroup:IsAlive()and self:HasGroup(EventGroup)then
local CommandCenterMenu=MENU_GROUP:New(EventGroup,self:GetText())
local MenuReporting=MENU_GROUP:New(EventGroup,"Missions Reports",CommandCenterMenu)
local MenuMissionsSummary=MENU_GROUP_COMMAND:New(EventGroup,"Missions Status Report",MenuReporting,self.ReportSummary,self,EventGroup)
local MenuMissionsDetails=MENU_GROUP_COMMAND:New(EventGroup,"Missions Players Report",MenuReporting,self.ReportMissionsPlayers,self,EventGroup)
local PlayerUnit=EventData.IniUnit
for MissionID,Mission in pairs(self:GetMissions())do
local Mission=Mission
local PlayerGroup=EventData.IniGroup
Mission:JoinUnit(PlayerUnit,PlayerGroup)
end
self:SetMenu()
end
end
end
)
self:HandleEvent(EVENTS.MissionEnd,
function(self,EventData)
local PlayerUnit=EventData.IniUnit
for MissionID,Mission in pairs(self:GetMissions())do
local Mission=Mission
Mission:Stop()
end
end
)
self:HandleEvent(EVENTS.PlayerLeaveUnit,
function(self,EventData)
local PlayerUnit=EventData.IniUnit
for MissionID,Mission in pairs(self:GetMissions())do
local Mission=Mission
if Mission:IsENGAGED()then
Mission:AbortUnit(PlayerUnit)
end
end
end
)
self:HandleEvent(EVENTS.Crash,
function(self,EventData)
local PlayerUnit=EventData.IniUnit
for MissionID,Mission in pairs(self:GetMissions())do
local Mission=Mission
if Mission:IsENGAGED()then
Mission:CrashUnit(PlayerUnit)
end
end
end
)
self:SetMenu()
_SETTINGS:SetSystemMenu(CommandCenterPositionable)
self:SetCommandMenu()
return self
end
function COMMANDCENTER:GetName()
return self.CommandCenterName
end
function COMMANDCENTER:GetText()
return"Command Center ["..self.CommandCenterName.."]"
end
function COMMANDCENTER:GetShortText()
return"CC ["..self.CommandCenterName.."]"
end
function COMMANDCENTER:GetCoalition()
return self.CommandCenterCoalition
end
function COMMANDCENTER:GetPositionable()
return self.CommandCenterPositionable
end
function COMMANDCENTER:GetMissions()
return self.Missions or{}
end
function COMMANDCENTER:AddMission(Mission)
self.Missions[Mission]=Mission
return Mission
end
function COMMANDCENTER:RemoveMission(Mission)
self.Missions[Mission]=nil
return Mission
end
function COMMANDCENTER:SetReferenceZones(ReferenceZonePrefix)
local MatchPattern="(.*)#(.*)"
self:F({MatchPattern=MatchPattern})
for ReferenceZoneName in pairs(_DATABASE.ZONENAMES)do
local ZoneName,ReferenceName=string.match(ReferenceZoneName,MatchPattern)
self:F({ZoneName=ZoneName,ReferenceName=ReferenceName})
if ZoneName and ReferenceName and ZoneName==ReferenceZonePrefix then
self.ReferencePoints[ReferenceZoneName]=ZONE:New(ReferenceZoneName)
self.ReferenceNames[ReferenceZoneName]=ReferenceName
end
end
return self
end
function COMMANDCENTER:SetModeWWII()
self.CommunicationMode="WWII"
return self
end
function COMMANDCENTER:IsModeWWII()
return self.CommunicationMode=="WWII"
end
function COMMANDCENTER:SetMenu()
self:F2()
local MenuTime=timer.getTime()
for MissionID,Mission in pairs(self:GetMissions()or{})do
local Mission=Mission
Mission:SetMenu(MenuTime)
end
for MissionID,Mission in pairs(self:GetMissions()or{})do
Mission=Mission
Mission:RemoveMenu(MenuTime)
end
end
function COMMANDCENTER:GetMenu(TaskGroup)
local MenuTime=timer.getTime()
self.CommandCenterMenus=self.CommandCenterMenus or{}
local CommandCenterMenu
local CommandCenterText=self:GetText()
CommandCenterMenu=MENU_GROUP:New(TaskGroup,CommandCenterText):SetTime(MenuTime)
self.CommandCenterMenus[TaskGroup]=CommandCenterMenu
if self.AutoAssignTasks==false then
local AssignTaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,"Assign Task",CommandCenterMenu,self.AssignTask,self,TaskGroup):SetTime(MenuTime):SetTag("AutoTask")
end
CommandCenterMenu:Remove(MenuTime,"AutoTask")
return self.CommandCenterMenus[TaskGroup]
end
function COMMANDCENTER:AssignTask(TaskGroup)
local Tasks={}
local AssignPriority=99999999
local AutoAssignMethod=self.AutoAssignMethod
for MissionID,Mission in pairs(self:GetMissions())do
local Mission=Mission
local MissionTasks=Mission:GetGroupTasks(TaskGroup)
for MissionTaskName,MissionTask in pairs(MissionTasks or{})do
local MissionTask=MissionTask
if MissionTask:IsStatePlanned()or MissionTask:IsStateReplanned()or MissionTask:IsStateAssigned()then
local TaskPriority=MissionTask:GetAutoAssignPriority(self.AutoAssignMethod,self,TaskGroup)
if TaskPriority<AssignPriority then
AssignPriority=TaskPriority
Tasks={}
end
if TaskPriority==AssignPriority then
Tasks[#Tasks+1]=MissionTask
end
end
end
end
local Task=Tasks[math.random(1,#Tasks)]
if Task then
self:T("Assigning task "..Task:GetName().." using auto assign method "..self.AutoAssignMethod.." to "..TaskGroup:GetName().." with task priority "..AssignPriority)
if not self.AutoAcceptTasks==true then
Task:SetAutoAssignMethod(ACT_ASSIGN_MENU_ACCEPT:New(Task.TaskBriefing))
end
Task:AssignToGroup(TaskGroup)
end
end
function COMMANDCENTER:SetCommandMenu()
local MenuTime=timer.getTime()
if self.CommandCenterPositionable and self.CommandCenterPositionable:IsInstanceOf(GROUP)then
local CommandCenterText=self:GetText()
local CommandCenterMenu=MENU_GROUP:New(self.CommandCenterPositionable,CommandCenterText):SetTime(MenuTime)
if self.AutoAssignTasks==false then
local AutoAssignTaskMenu=MENU_GROUP_COMMAND:New(self.CommandCenterPositionable,"Assign Task On",CommandCenterMenu,self.SetAutoAssignTasks,self,true):SetTime(MenuTime):SetTag("AutoTask")
else
local AutoAssignTaskMenu=MENU_GROUP_COMMAND:New(self.CommandCenterPositionable,"Assign Task Off",CommandCenterMenu,self.SetAutoAssignTasks,self,false):SetTime(MenuTime):SetTag("AutoTask")
end
CommandCenterMenu:Remove(MenuTime,"AutoTask")
end
end
function COMMANDCENTER:SetAutoAssignTasks(AutoAssign)
self.AutoAssignTasks=AutoAssign or false
if self.AutoAssignTasks==true then
self.autoAssignTasksScheduleID=self:ScheduleRepeat(10,30,0,nil,self.AssignTasks,self)
else
self:ScheduleStop()
end
end
function COMMANDCENTER:SetAutoAcceptTasks(AutoAccept)
self.AutoAcceptTasks=AutoAccept or false
end
function COMMANDCENTER:SetAutoAssignMethod(AutoAssignMethod)
self.AutoAssignMethod=AutoAssignMethod or COMMANDCENTER.AutoAssignMethods.Random
end
function COMMANDCENTER:AssignTasks()
local GroupSet=self:AddGroups()
for GroupID,TaskGroup in pairs(GroupSet:GetSet())do
local TaskGroup=TaskGroup
if TaskGroup:IsAlive()then
self:GetMenu(TaskGroup)
if self:IsGroupAssigned(TaskGroup)then
else
if TaskGroup:IsAir()then
self:AssignTask(TaskGroup)
end
end
end
end
end
function COMMANDCENTER:AddGroups()
local GroupSet=SET_GROUP:New()
for MissionID,Mission in pairs(self.Missions)do
local Mission=Mission
GroupSet=Mission:AddGroups(GroupSet)
end
return GroupSet
end
function COMMANDCENTER:IsGroupAssigned(TaskGroup)
local Assigned=false
for MissionID,Mission in pairs(self.Missions)do
local Mission=Mission
if Mission:IsGroupAssigned(TaskGroup)then
Assigned=true
break
end
end
return Assigned
end
function COMMANDCENTER:HasGroup(MissionGroup)
local Has=false
for MissionID,Mission in pairs(self.Missions)do
local Mission=Mission
if Mission:HasGroup(MissionGroup)then
Has=true
break
end
end
return Has
end
function COMMANDCENTER:MessageToAll(Message)
self:GetPositionable():MessageToAll(Message,self.MessageDuration,self:GetName())
end
function COMMANDCENTER:MessageToGroup(Message,MessageGroup)
self:GetPositionable():MessageToGroup(Message,self.MessageDuration,MessageGroup,self:GetShortText())
end
function COMMANDCENTER:MessageTypeToGroup(Message,MessageGroup,MessageType)
self:GetPositionable():MessageTypeToGroup(Message,MessageType,MessageGroup,self:GetShortText())
end
function COMMANDCENTER:MessageToCoalition(Message)
local CCCoalition=self:GetPositionable():GetCoalition()
self:GetPositionable():MessageToCoalition(Message,self.MessageDuration,CCCoalition,self:GetShortText())
end
function COMMANDCENTER:MessageTypeToCoalition(Message,MessageType)
local CCCoalition=self:GetPositionable():GetCoalition()
self:GetPositionable():MessageTypeToCoalition(Message,MessageType,CCCoalition,self:GetShortText())
end
function COMMANDCENTER:ReportSummary(ReportGroup)
self:F(ReportGroup)
local Report=REPORT:New()
local Name=self:GetName()
Report:Add(string.format('%s - Report Summary Missions',Name))
for MissionID,Mission in pairs(self.Missions)do
local Mission=Mission
Report:Add(" - "..Mission:ReportSummary(ReportGroup))
end
self:MessageToGroup(Report:Text(),ReportGroup)
end
function COMMANDCENTER:ReportMissionsPlayers(ReportGroup)
self:F(ReportGroup)
local Report=REPORT:New()
Report:Add("Players active in all missions.")
for MissionID,MissionData in pairs(self.Missions)do
local Mission=MissionData
Report:Add(" - "..Mission:ReportPlayersPerTask(ReportGroup))
end
self:MessageToGroup(Report:Text(),ReportGroup)
end
function COMMANDCENTER:ReportDetails(ReportGroup,Task)
self:F(ReportGroup)
local Report=REPORT:New()
for MissionID,Mission in pairs(self.Missions)do
local Mission=Mission
Report:Add(" - "..Mission:ReportDetails())
end
self:MessageToGroup(Report:Text(),ReportGroup)
end
function COMMANDCENTER:SetFlashStatus(Flash)
self:F()
self.FlashStatus=Flash and true
end
function COMMANDCENTER:SetMessageDuration(seconds)
self:F()
self.MessageDuration=10 or seconds
end
MISSION={
ClassName="MISSION",
Name="",
MissionStatus="PENDING",
AssignedGroups={},
}
function MISSION:New(CommandCenter,MissionName,MissionPriority,MissionBriefing,MissionCoalition)
local self=BASE:Inherit(self,FSM:New())
self:T({MissionName,MissionPriority,MissionBriefing,MissionCoalition})
self.CommandCenter=CommandCenter
CommandCenter:AddMission(self)
self.Name=MissionName
self.MissionPriority=MissionPriority
self.MissionBriefing=MissionBriefing
self.MissionCoalition=MissionCoalition
self.Tasks={}
self.TaskNumber=0
self.PlayerNames={}
self:SetStartState("IDLE")
self:AddTransition("IDLE","Start","ENGAGED")
self:AddTransition("ENGAGED","Stop","IDLE")
self:AddTransition("ENGAGED","Complete","COMPLETED")
self:AddTransition("*","Fail","FAILED")
self:AddTransition("*","MissionGoals","*")
CommandCenter:SetMenu()
return self
end
function MISSION:onenterCOMPLETED(From,Event,To)
self:GetCommandCenter():MessageTypeToCoalition(self:GetText().." has been completed! Good job guys!",MESSAGE.Type.Information)
end
function MISSION:GetName()
return self.Name
end
function MISSION:GetText()
return string.format('Mission "%s (%s)"',self.Name,self.MissionPriority)
end
function MISSION:GetShortText()
return string.format('Mission "%s"',self.Name)
end
function MISSION:JoinUnit(PlayerUnit,PlayerGroup)
self:T({Mission=self:GetName(),PlayerUnit=PlayerUnit,PlayerGroup=PlayerGroup})
local PlayerUnitAdded=false
for TaskID,Task in pairs(self:GetTasks())do
local Task=Task
if Task:JoinUnit(PlayerUnit,PlayerGroup)then
PlayerUnitAdded=true
end
end
return PlayerUnitAdded
end
function MISSION:AbortUnit(PlayerUnit)
self:F({PlayerUnit=PlayerUnit})
for TaskID,Task in pairs(self:GetTasks())do
local Task=Task
local PlayerGroup=PlayerUnit:GetGroup()
Task:AbortGroup(PlayerGroup)
end
return self
end
function MISSION:CrashUnit(PlayerUnit)
self:F({PlayerUnit=PlayerUnit})
for TaskID,Task in pairs(self:GetTasks())do
local Task=Task
local PlayerGroup=PlayerUnit:GetGroup()
Task:CrashGroup(PlayerGroup)
end
return self
end
function MISSION:AddScoring(Scoring)
self.Scoring=Scoring
return self
end
function MISSION:GetScoring()
return self.Scoring
end
function MISSION:GetGroups()
return self:AddGroups()
end
function MISSION:AddGroups(GroupSet)
GroupSet=GroupSet or SET_GROUP:New()
for TaskID,Task in pairs(self:GetTasks())do
local Task=Task
GroupSet=Task:AddGroups(GroupSet)
end
return GroupSet
end
function MISSION:SetMenu(MenuTime)
self:F({self:GetName(),MenuTime})
local MenuCount={}
for TaskID,Task in pairs(self:GetTasks())do
local Task=Task
local TaskType=Task:GetType()
MenuCount[TaskType]=MenuCount[TaskType]or 1
if MenuCount[TaskType]<=10 then
Task:SetMenu(MenuTime)
MenuCount[TaskType]=MenuCount[TaskType]+1
end
end
end
function MISSION:RemoveMenu(MenuTime)
self:F({self:GetName(),MenuTime})
for _,Task in pairs(self:GetTasks())do
local Task=Task
Task:RemoveMenu(MenuTime)
end
end
do
function MISSION:IsGroupAssigned(MissionGroup)
local MissionGroupName=MissionGroup:GetName()
if self.AssignedGroups[MissionGroupName]==MissionGroup then
self:T2({"Mission is assigned to:",MissionGroup:GetName()})
return true
end
self:T2({"Mission is not assigned to:",MissionGroup:GetName()})
return false
end
function MISSION:SetGroupAssigned(MissionGroup)
local MissionName=self:GetName()
local MissionGroupName=MissionGroup:GetName()
self.AssignedGroups[MissionGroupName]=MissionGroup
self:T(string.format("Mission %s is assigned to %s",MissionName,MissionGroupName))
return self
end
function MISSION:ClearGroupAssignment(MissionGroup)
local MissionName=self:GetName()
local MissionGroupName=MissionGroup:GetName()
self.AssignedGroups[MissionGroupName]=nil
return self
end
end
function MISSION:GetCommandCenter()
return self.CommandCenter
end
function MISSION:RemoveTaskMenu(Task)
Task:RemoveMenu()
end
function MISSION:GetRootMenu(TaskGroup)
local CommandCenter=self:GetCommandCenter()
local CommandCenterMenu=CommandCenter:GetMenu(TaskGroup)
local MissionName=self:GetText()
self.MissionMenu=MENU_COALITION:New(self.MissionCoalition,MissionName,CommandCenterMenu)
return self.MissionMenu
end
function MISSION:GetMenu(TaskGroup)
local CommandCenter=self:GetCommandCenter()
local CommandCenterMenu=CommandCenter:GetMenu(TaskGroup)
self.MissionGroupMenu=self.MissionGroupMenu or{}
self.MissionGroupMenu[TaskGroup]=self.MissionGroupMenu[TaskGroup]or{}
local GroupMenu=self.MissionGroupMenu[TaskGroup]
local MissionText=self:GetText()
self.MissionMenu=MENU_GROUP:New(TaskGroup,MissionText,CommandCenterMenu)
GroupMenu.BriefingMenu=MENU_GROUP_COMMAND:New(TaskGroup,"Mission Briefing",self.MissionMenu,self.MenuReportBriefing,self,TaskGroup)
GroupMenu.MarkTasks=MENU_GROUP_COMMAND:New(TaskGroup,"Mark Task Locations on Map",self.MissionMenu,self.MarkTargetLocations,self,TaskGroup)
GroupMenu.TaskReportsMenu=MENU_GROUP:New(TaskGroup,"Task Reports",self.MissionMenu)
GroupMenu.ReportTasksMenu=MENU_GROUP_COMMAND:New(TaskGroup,"Report Tasks Summary",GroupMenu.TaskReportsMenu,self.MenuReportTasksSummary,self,TaskGroup)
GroupMenu.ReportPlannedTasksMenu=MENU_GROUP_COMMAND:New(TaskGroup,"Report Planned Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Planned")
GroupMenu.ReportAssignedTasksMenu=MENU_GROUP_COMMAND:New(TaskGroup,"Report Assigned Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Assigned")
GroupMenu.ReportSuccessTasksMenu=MENU_GROUP_COMMAND:New(TaskGroup,"Report Successful Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Success")
GroupMenu.ReportFailedTasksMenu=MENU_GROUP_COMMAND:New(TaskGroup,"Report Failed Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Failed")
GroupMenu.ReportHeldTasksMenu=MENU_GROUP_COMMAND:New(TaskGroup,"Report Held Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Hold")
GroupMenu.PlayerReportsMenu=MENU_GROUP:New(TaskGroup,"Statistics Reports",self.MissionMenu)
GroupMenu.ReportMissionHistory=MENU_GROUP_COMMAND:New(TaskGroup,"Report Mission Progress",GroupMenu.PlayerReportsMenu,self.MenuReportPlayersProgress,self,TaskGroup)
GroupMenu.ReportPlayersPerTaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,"Report Players per Task",GroupMenu.PlayerReportsMenu,self.MenuReportPlayersPerTask,self,TaskGroup)
return self.MissionMenu
end
function MISSION:GetTask(TaskName)
self:F({TaskName})
return self.Tasks[TaskName]
end
function MISSION:GetNextTaskID(Task)
self.TaskNumber=self.TaskNumber+1
return self.TaskNumber
end
function MISSION:AddTask(Task)
local TaskName=Task:GetTaskName()
self:T({"==> Adding TASK ",MissionName=self:GetName(),TaskName=TaskName})
self.Tasks[TaskName]=Task
self:GetCommandCenter():SetMenu()
return Task
end
function MISSION:RemoveTask(Task)
local TaskName=Task:GetTaskName()
self:T({"<== Removing TASK ",MissionName=self:GetName(),TaskName=TaskName})
self:F(TaskName)
self.Tasks[TaskName]=self.Tasks[TaskName]or{n=0}
self.Tasks[TaskName]=nil
Task=nil
collectgarbage()
self:GetCommandCenter():SetMenu()
return nil
end
function MISSION:IsCOMPLETED()
return self:Is("COMPLETED")
end
function MISSION:IsIDLE()
return self:Is("IDLE")
end
function MISSION:IsENGAGED()
return self:Is("ENGAGED")
end
function MISSION:IsFAILED()
return self:Is("FAILED")
end
function MISSION:IsHOLD()
return self:Is("HOLD")
end
function MISSION:HasGroup(TaskGroup)
local Has=false
for TaskID,Task in pairs(self:GetTasks())do
local Task=Task
if Task:HasGroup(TaskGroup)then
Has=true
break
end
end
return Has
end
function MISSION:GetTasksRemaining()
local TasksRemaining=0
for TaskID,Task in pairs(self:GetTasks())do
local Task=Task
if Task:IsStateSuccess()or Task:IsStateFailed()then
else
TasksRemaining=TasksRemaining+1
end
end
return TasksRemaining
end
function MISSION:GetTaskTypes()
local TaskTypeList={}
local TasksRemaining=0
for TaskID,Task in pairs(self:GetTasks())do
local Task=Task
local TaskType=Task:GetType()
TaskTypeList[TaskType]=TaskType
end
return TaskTypeList
end
function MISSION:AddPlayerName(PlayerName)
self.PlayerNames=self.PlayerNames or{}
self.PlayerNames[PlayerName]=PlayerName
return self
end
function MISSION:GetPlayerNames()
return self.PlayerNames
end
function MISSION:ReportBriefing()
local Report=REPORT:New()
local Name=self:GetText()
local Status="<"..self:GetState()..">"
Report:Add(string.format('%s - %s - Mission Briefing Report',Name,Status))
Report:Add(self.MissionBriefing)
return Report:Text()
end
function MISSION:ReportPlayersPerTask(ReportGroup)
local Report=REPORT:New()
local Name=self:GetText()
local Status="<"..self:GetState()..">"
Report:Add(string.format('%s - %s - Players per Task Report',Name,Status))
local PlayerList={}
for TaskID,Task in pairs(self:GetTasks())do
local Task=Task
local PlayerNames=Task:GetPlayerNames()
for PlayerName,PlayerGroup in pairs(PlayerNames)do
PlayerList[PlayerName]=Task:GetName()
end
end
for PlayerName,TaskName in pairs(PlayerList)do
Report:Add(string.format(' - Player (%s): Task "%s"',PlayerName,TaskName))
end
return Report:Text()
end
function MISSION:ReportPlayersProgress(ReportGroup)
local Report=REPORT:New()
local Name=self:GetText()
local Status="<"..self:GetState()..">"
Report:Add(string.format('%s - %s - Players per Task Progress Report',Name,Status))
local PlayerList={}
for TaskID,Task in pairs(self:GetTasks())do
local Task=Task
local TaskName=Task:GetName()
local Goal=Task:GetGoal()
PlayerList[TaskName]=PlayerList[TaskName]or{}
if Goal then
local TotalContributions=Goal:GetTotalContributions()
local PlayerContributions=Goal:GetPlayerContributions()
self:F({TotalContributions=TotalContributions,PlayerContributions=PlayerContributions})
for PlayerName,PlayerContribution in pairs(PlayerContributions)do
PlayerList[TaskName][PlayerName]=string.format('Player (%s): Task "%s": %d%%',PlayerName,TaskName,PlayerContributions[PlayerName]*100/TotalContributions)
end
else
PlayerList[TaskName]["_"]=string.format('Player (---): Task "%s": %d%%',TaskName,0)
end
end
for TaskName,TaskData in pairs(PlayerList)do
for PlayerName,TaskText in pairs(TaskData)do
Report:Add(string.format(' - %s',TaskText))
end
end
return Report:Text()
end
function MISSION:MarkTargetLocations(ReportGroup)
local Report=REPORT:New()
local Name=self:GetText()
local Status="<"..self:GetState()..">"
Report:Add(string.format('%s - %s - All Tasks are marked on the map. Select a Task from the Mission Menu and Join the Task!!!',Name,Status))
for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)<t[b]:ReportOrder(ReportGroup)end)do
local Task=Task
Task:MenuMarkToGroup(ReportGroup)
end
return Report:Text()
end
function MISSION:ReportSummary(ReportGroup)
local Report=REPORT:New()
local Name=self:GetText()
local Status="<"..self:GetState()..">"
Report:Add(string.format('%s - %s - Task Overview Report',Name,Status))
for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)<t[b]:ReportOrder(ReportGroup)end)do
local Task=Task
Report:Add("- "..Task:ReportSummary(ReportGroup))
end
return Report:Text()
end
function MISSION:ReportOverview(ReportGroup,TaskStatus)
self:F({TaskStatus=TaskStatus})
local Report=REPORT:New()
local Name=self:GetText()
local Status="<"..self:GetState()..">"
Report:Add(string.format('%s - %s - %s Tasks Report',Name,Status,TaskStatus))
local Tasks=0
for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)<t[b]:ReportOrder(ReportGroup)end)do
local Task=Task
if Task:Is(TaskStatus)then
Report:Add(string.rep("-",140))
Report:Add(Task:ReportOverview(ReportGroup))
end
Tasks=Tasks+1
if Tasks>=8 then
break
end
end
return Report:Text()
end
function MISSION:ReportDetails(ReportGroup)
local Report=REPORT:New()
local Name=self:GetText()
local Status="<"..self:GetState()..">"
Report:Add(string.format('%s - %s - Task Detailed Report',Name,Status))
local TasksRemaining=0
for TaskID,Task in pairs(self:GetTasks())do
local Task=Task
Report:Add(string.rep("-",140))
Report:Add(Task:ReportDetails(ReportGroup))
end
return Report:Text()
end
function MISSION:GetTasks()
return self.Tasks or{}
end
function MISSION:GetGroupTasks(TaskGroup)
local Tasks={}
for TaskID,Task in pairs(self:GetTasks())do
local Task=Task
if Task:HasGroup(TaskGroup)then
Tasks[#Tasks+1]=Task
end
end
return Tasks
end
function MISSION:MenuReportBriefing(ReportGroup)
local Report=self:ReportBriefing()
self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Briefing)
end
function MISSION:MenuMarkTargetLocations(ReportGroup)
local Report=self:MarkTargetLocations(ReportGroup)
self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview)
end
function MISSION:MenuReportTasksSummary(ReportGroup)
local Report=self:ReportSummary(ReportGroup)
self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview)
end
function MISSION:MenuReportTasksPerStatus(ReportGroup,TaskStatus)
local Report=self:ReportOverview(ReportGroup,TaskStatus)
self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview)
end
function MISSION:MenuReportPlayersPerTask(ReportGroup)
local Report=self:ReportPlayersPerTask()
self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview)
end
function MISSION:MenuReportPlayersProgress(ReportGroup)
local Report=self:ReportPlayersProgress()
self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview)
end
TASK={
ClassName="TASK",
TaskScheduler=nil,
ProcessClasses={},
Processes={},
Players=nil,
Scores={},
Menu={},
SetGroup=nil,
FsmTemplate=nil,
Mission=nil,
CommandCenter=nil,
TimeOut=0,
AssignedGroups={},
}
function TASK:New(Mission,SetGroupAssign,TaskName,TaskType,TaskBriefing)
local self=BASE:Inherit(self,FSM_TASK:New(TaskName))
self:SetStartState("Planned")
self:AddTransition("Planned","Assign","Assigned")
self:AddTransition("Assigned","AssignUnit","Assigned")
self:AddTransition("Assigned","Success","Success")
self:AddTransition("Assigned","Hold","Hold")
self:AddTransition("Assigned","Fail","Failed")
self:AddTransition({"Planned","Assigned"},"Abort","Aborted")
self:AddTransition("Assigned","Cancel","Cancelled")
self:AddTransition("Assigned","Goal","*")
self.Fsm={}
local Fsm=self:GetUnitProcess()
Fsm:SetStartState("Planned")
Fsm:AddProcess("Planned","Accept",ACT_ASSIGN_ACCEPT:New(self.TaskBriefing),{Assigned="Assigned",Rejected="Reject"})
Fsm:AddTransition("Assigned","Assigned","*")
self:AddTransition("*","PlayerCrashed","*")
self:AddTransition("*","PlayerAborted","*")
self:AddTransition("*","PlayerRejected","*")
self:AddTransition("*","PlayerDead","*")
self:AddTransition({"Failed","Aborted","Cancelled"},"Replan","Planned")
self:AddTransition("*","TimeOut","Cancelled")
self:F("New TASK "..TaskName)
self.Processes={}
self.Mission=Mission
self.CommandCenter=Mission:GetCommandCenter()
self.SetGroup=SetGroupAssign
self:SetType(TaskType)
self:SetName(TaskName)
self:SetID(Mission:GetNextTaskID(self))
self:SetBriefing(TaskBriefing)
self.TaskInfo=TASKINFO:New(self)
self.TaskProgress={}
return self
end
function TASK:GetUnitProcess(TaskUnit)
if TaskUnit then
return self:GetStateMachine(TaskUnit)
else
self.FsmTemplate=self.FsmTemplate or FSM_PROCESS:New()
return self.FsmTemplate
end
end
function TASK:SetUnitProcess(FsmTemplate)
self.FsmTemplate=FsmTemplate
end
function TASK:JoinUnit(PlayerUnit,PlayerGroup)
self:F({PlayerUnit=PlayerUnit,PlayerGroup=PlayerGroup})
local PlayerUnitAdded=false
local PlayerGroups=self:GetGroups()
if PlayerGroups:IsIncludeObject(PlayerGroup)then
if self:IsStatePlanned()or self:IsStateReplanned()then
end
if self:IsStateAssigned()then
local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup)
self:F({IsGroupAssigned=IsGroupAssigned})
if IsGroupAssigned then
self:AssignToUnit(PlayerUnit)
self:MessageToGroups(PlayerUnit:GetPlayerName().." joined Task "..self:GetName())
end
end
end
return PlayerUnitAdded
end
function TASK:RejectGroup(PlayerGroup)
local PlayerGroups=self:GetGroups()
if PlayerGroups:IsIncludeObject(PlayerGroup)then
if self:IsStatePlanned()then
local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup)
if IsGroupAssigned then
local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName()
self:GetMission():GetCommandCenter():MessageToGroup("Task "..self:GetName().." has been rejected! We will select another task.",PlayerGroup)
self:UnAssignFromGroup(PlayerGroup)
self:PlayerRejected(PlayerGroup:GetUnit(1))
end
end
end
return self
end
function TASK:AbortGroup(PlayerGroup)
local PlayerGroups=self:GetGroups()
if PlayerGroups:IsIncludeObject(PlayerGroup)then
if self:IsStateAssigned()then
local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup)
if IsGroupAssigned then
local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName()
self:UnAssignFromGroup(PlayerGroup)
PlayerGroups:Flush(self)
local IsRemaining=false
for GroupName,AssignedGroup in pairs(PlayerGroups:GetSet()or{})do
if self:IsGroupAssigned(AssignedGroup)==true then
IsRemaining=true
self:F({Task=self:GetName(),IsRemaining=IsRemaining})
break
end
end
self:F({Task=self:GetName(),IsRemaining=IsRemaining})
if IsRemaining==false then
self:Abort()
end
self:PlayerAborted(PlayerGroup:GetUnit(1))
end
end
end
return self
end
function TASK:CrashGroup(PlayerGroup)
self:F({PlayerGroup=PlayerGroup})
local PlayerGroups=self:GetGroups()
if PlayerGroups:IsIncludeObject(PlayerGroup)then
if self:IsStateAssigned()then
local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup)
self:F({IsGroupAssigned=IsGroupAssigned})
if IsGroupAssigned then
local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName()
self:MessageToGroups(PlayerName.." crashed! ")
self:UnAssignFromGroup(PlayerGroup)
PlayerGroups:Flush(self)
local IsRemaining=false
for GroupName,AssignedGroup in pairs(PlayerGroups:GetSet()or{})do
if self:IsGroupAssigned(AssignedGroup)==true then
IsRemaining=true
self:F({Task=self:GetName(),IsRemaining=IsRemaining})
break
end
end
self:F({Task=self:GetName(),IsRemaining=IsRemaining})
if IsRemaining==false then
self:Abort()
end
self:PlayerCrashed(PlayerGroup:GetUnit(1))
end
end
end
return self
end
function TASK:GetMission()
return self.Mission
end
function TASK:GetGroups()
return self.SetGroup
end
function TASK:AddGroups(GroupSet)
GroupSet=GroupSet or SET_GROUP:New()
self.SetGroup:ForEachGroup(
function(GroupItem)
GroupSet:Add(GroupItem:GetName(),GroupItem)
end
)
return GroupSet
end
do
function TASK:IsGroupAssigned(TaskGroup)
local TaskGroupName=TaskGroup:GetName()
if self.AssignedGroups[TaskGroupName]then
return true
end
return false
end
function TASK:SetGroupAssigned(TaskGroup)
local TaskName=self:GetName()
local TaskGroupName=TaskGroup:GetName()
self.AssignedGroups[TaskGroupName]=TaskGroup
self:F(string.format("Task %s is assigned to %s",TaskName,TaskGroupName))
self:GetMission():SetGroupAssigned(TaskGroup)
local SetAssignedGroups=self:GetGroups()
return self
end
function TASK:ClearGroupAssignment(TaskGroup)
local TaskName=self:GetName()
local TaskGroupName=TaskGroup:GetName()
self.AssignedGroups[TaskGroupName]=nil
self:GetMission():ClearGroupAssignment(TaskGroup)
local SetAssignedGroups=self:GetGroups()
SetAssignedGroups:ForEachGroup(
function(AssignedGroup)
if self:IsGroupAssigned(AssignedGroup)then
else
end
end
)
return self
end
end
do
function TASK:SetAssignMethod(AcceptClass)
local ProcessTemplate=self:GetUnitProcess()
ProcessTemplate:SetProcess("Planned","Accept",AcceptClass)
end
function TASK:AssignToGroup(TaskGroup)
self:F(TaskGroup:GetName())
local TaskGroupName=TaskGroup:GetName()
local Mission=self:GetMission()
local CommandCenter=Mission:GetCommandCenter()
self:SetGroupAssigned(TaskGroup)
local TaskUnits=TaskGroup:GetUnits()
for UnitID,UnitData in pairs(TaskUnits)do
local TaskUnit=UnitData
local PlayerName=TaskUnit:GetPlayerName()
self:F(PlayerName)
if PlayerName~=nil and PlayerName~=""then
self:AssignToUnit(TaskUnit)
CommandCenter:MessageToGroup(
string.format('Task "%s": Briefing for player (%s):\n%s',
self:GetName(),
PlayerName,
self:GetBriefing()
),TaskGroup
)
end
end
CommandCenter:SetMenu()
self:MenuFlashTaskStatus(TaskGroup,self:GetMission():GetCommandCenter().FlashStatus)
return self
end
function TASK:UnAssignFromGroup(TaskGroup)
self:F2({TaskGroup=TaskGroup:GetName()})
self:ClearGroupAssignment(TaskGroup)
local TaskUnits=TaskGroup:GetUnits()
for UnitID,UnitData in pairs(TaskUnits)do
local TaskUnit=UnitData
local PlayerName=TaskUnit:GetPlayerName()
if PlayerName~=nil and PlayerName~=""then
self:UnAssignFromUnit(TaskUnit)
end
end
local Mission=self:GetMission()
local CommandCenter=Mission:GetCommandCenter()
CommandCenter:SetMenu()
self:MenuFlashTaskStatus(TaskGroup,false)
end
end
function TASK:HasGroup(FindGroup)
local SetAttackGroup=self:GetGroups()
return SetAttackGroup:FindGroup(FindGroup:GetName())
end
function TASK:AssignToUnit(TaskUnit)
self:F(TaskUnit:GetName())
local FsmTemplate=self:GetUnitProcess()
local FsmUnit=self:SetStateMachine(TaskUnit,FsmTemplate:Copy(TaskUnit,self))
FsmUnit:SetStartState("Planned")
FsmUnit:Accept()
return self
end
function TASK:UnAssignFromUnit(TaskUnit)
self:F(TaskUnit:GetName())
self:RemoveStateMachine(TaskUnit)
self:RemoveTaskControlMenu(TaskUnit)
return self
end
function TASK:SetTimeOut(Timer)
self:F(Timer)
self.TimeOut=Timer
self:__TimeOut(self.TimeOut)
return self
end
function TASK:MessageToGroups(Message)
self:F({Message=Message})
local Mission=self:GetMission()
local CC=Mission:GetCommandCenter()
for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do
TaskGroup=TaskGroup
if TaskGroup:IsAlive()==true then
CC:MessageToGroup(Message,TaskGroup,TaskGroup:GetName())
end
end
end
function TASK:SendBriefingToAssignedGroups()
self:F2()
for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do
if TaskGroup:IsAlive()then
if self:IsGroupAssigned(TaskGroup)then
TaskGroup:Message(self.TaskBriefing,60)
end
end
end
end
function TASK:UnAssignFromGroups()
self:F2()
for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do
if TaskGroup:IsAlive()==true then
if self:IsGroupAssigned(TaskGroup)then
self:UnAssignFromGroup(TaskGroup)
end
end
end
end
function TASK:HasAliveUnits()
self:F()
for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
if TaskGroup:IsAlive()==true then
if self:IsStateAssigned()then
if self:IsGroupAssigned(TaskGroup)then
for TaskUnitID,TaskUnit in pairs(TaskGroup:GetUnits())do
if TaskUnit:IsAlive()then
self:T({HasAliveUnits=true})
return true
end
end
end
end
end
end
self:T({HasAliveUnits=false})
return false
end
function TASK:SetMenu(MenuTime)
self:F({self:GetName(),MenuTime})
for TaskGroupID,TaskGroupData in pairs(self.SetGroup:GetSet())do
local TaskGroup=TaskGroupData
if TaskGroup:IsAlive()==true and TaskGroup:GetPlayerNames()then
local Mission=self:GetMission()
local MissionMenu=Mission:GetMenu(TaskGroup)
if MissionMenu then
self:SetMenuForGroup(TaskGroup,MenuTime)
end
end
end
end
function TASK:SetMenuForGroup(TaskGroup,MenuTime)
if self:IsStatePlanned()or self:IsStateAssigned()then
self:SetPlannedMenuForGroup(TaskGroup,MenuTime)
if self:IsGroupAssigned(TaskGroup)then
self:SetAssignedMenuForGroup(TaskGroup,MenuTime)
end
end
end
function TASK:SetPlannedMenuForGroup(TaskGroup,MenuTime)
self:F(TaskGroup:GetName())
local Mission=self:GetMission()
local MissionName=Mission:GetName()
local MissionMenu=Mission:GetMenu(TaskGroup)
local TaskType=self:GetType()
local TaskPlayerCount=self:GetPlayerCount()
local TaskPlayerString=string.format(" (%dp)",TaskPlayerCount)
local TaskText=string.format("%s",self:GetName())
local TaskName=string.format("%s",self:GetName())
self.MenuPlanned=self.MenuPlanned or{}
self.MenuPlanned[TaskGroup]=MENU_GROUP_DELAYED:New(TaskGroup,"Join Planned Task",MissionMenu,Mission.MenuReportTasksPerStatus,Mission,TaskGroup,"Planned"):SetTime(MenuTime):SetTag("Tasking")
local TaskTypeMenu=MENU_GROUP_DELAYED:New(TaskGroup,TaskType,self.MenuPlanned[TaskGroup]):SetTime(MenuTime):SetTag("Tasking")
local TaskTypeMenu=MENU_GROUP_DELAYED:New(TaskGroup,TaskText,TaskTypeMenu):SetTime(MenuTime):SetTag("Tasking")
if not Mission:IsGroupAssigned(TaskGroup)then
local JoinTaskMenu=MENU_GROUP_COMMAND_DELAYED:New(TaskGroup,string.format("Join Task"),TaskTypeMenu,self.MenuAssignToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking")
local MarkTaskMenu=MENU_GROUP_COMMAND_DELAYED:New(TaskGroup,string.format("Mark Task Location on Map"),TaskTypeMenu,self.MenuMarkToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking")
end
local ReportTaskMenu=MENU_GROUP_COMMAND_DELAYED:New(TaskGroup,string.format("Report Task Details"),TaskTypeMenu,self.MenuTaskStatus,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking")
return self
end
function TASK:SetAssignedMenuForGroup(TaskGroup,MenuTime)
self:F({TaskGroup:GetName(),MenuTime})
local TaskType=self:GetType()
local TaskPlayerCount=self:GetPlayerCount()
local TaskPlayerString=string.format(" (%dp)",TaskPlayerCount)
local TaskText=string.format("%s%s",self:GetName(),TaskPlayerString)
local TaskName=string.format("%s",self:GetName())
for UnitName,TaskUnit in pairs(TaskGroup:GetPlayerUnits())do
local TaskUnit=TaskUnit
if TaskUnit then
local MenuControl=self:GetTaskControlMenu(TaskUnit)
local TaskControl=MENU_GROUP:New(TaskGroup,"Control Task",MenuControl):SetTime(MenuTime):SetTag("Tasking")
if self:IsStateAssigned()then
local TaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Abort Task"),TaskControl,self.MenuTaskAbort,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking")
end
local MarkMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Mark Task Location on Map"),TaskControl,self.MenuMarkToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking")
local TaskTypeMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Report Task Details"),TaskControl,self.MenuTaskStatus,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking")
if not self.FlashTaskStatus then
local TaskFlashStatusMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Flash Task Details"),TaskControl,self.MenuFlashTaskStatus,self,TaskGroup,true):SetTime(MenuTime):SetTag("Tasking")
else
local TaskFlashStatusMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Stop Flash Task Details"),TaskControl,self.MenuFlashTaskStatus,self,TaskGroup,nil):SetTime(MenuTime):SetTag("Tasking")
end
end
end
return self
end
function TASK:RemoveMenu(MenuTime)
self:F({self:GetName(),MenuTime})
for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
if TaskGroup:IsAlive()==true then
local TaskGroup=TaskGroup
if TaskGroup:IsAlive()==true and TaskGroup:GetPlayerNames()then
self:RefreshMenus(TaskGroup,MenuTime)
end
end
end
end
function TASK:RefreshMenus(TaskGroup,MenuTime)
self:F({TaskGroup:GetName(),MenuTime})
local Mission=self:GetMission()
local MissionName=Mission:GetName()
local MissionMenu=Mission:GetMenu(TaskGroup)
local TaskName=self:GetName()
self.MenuPlanned=self.MenuPlanned or{}
local PlannedMenu=self.MenuPlanned[TaskGroup]
self.MenuAssigned=self.MenuAssigned or{}
local AssignedMenu=self.MenuAssigned[TaskGroup]
if PlannedMenu then
self.MenuPlanned[TaskGroup]=PlannedMenu:Remove(MenuTime,"Tasking")
PlannedMenu:Set()
end
if AssignedMenu then
self.MenuAssigned[TaskGroup]=AssignedMenu:Remove(MenuTime,"Tasking")
AssignedMenu:Set()
end
end
function TASK:RemoveAssignedMenuForGroup(TaskGroup)
self:F()
local Mission=self:GetMission()
local MissionName=Mission:GetName()
local MissionMenu=Mission:GetMenu(TaskGroup)
if MissionMenu then
MissionMenu:RemoveSubMenus()
end
end
function TASK:MenuAssignToGroup(TaskGroup)
self:F("Join Task menu selected")
self:AssignToGroup(TaskGroup)
end
function TASK:MenuMarkToGroup(TaskGroup)
self:F()
self:UpdateTaskInfo(self.DetectedItem)
local TargetCoordinates=self.TaskInfo:GetData("Coordinates")
if TargetCoordinates then
for TargetCoordinateID,TargetCoordinate in pairs(TargetCoordinates)do
local Report=REPORT:New():SetIndent(0)
self.TaskInfo:Report(Report,"M",TaskGroup,self)
local MarkText=Report:Text(", ")
self:F({Coordinate=TargetCoordinate,MarkText=MarkText})
TargetCoordinate:MarkToGroup(MarkText,TaskGroup)
end
else
local TargetCoordinate=self.TaskInfo:GetData("Coordinate")
if TargetCoordinate then
local Report=REPORT:New():SetIndent(0)
self.TaskInfo:Report(Report,"M",TaskGroup,self)
local MarkText=Report:Text(", ")
self:F({Coordinate=TargetCoordinate,MarkText=MarkText})
TargetCoordinate:MarkToGroup(MarkText,TaskGroup)
end
end
end
function TASK:MenuTaskStatus(TaskGroup)
if TaskGroup:IsAlive()then
local ReportText=self:ReportDetails(TaskGroup)
self:T(ReportText)
self:GetMission():GetCommandCenter():MessageTypeToGroup(ReportText,TaskGroup,MESSAGE.Type.Detailed)
end
end
function TASK:MenuFlashTaskStatus(TaskGroup,Flash)
self.FlashTaskStatus=Flash
if self.FlashTaskStatus then
self.FlashTaskScheduler,self.FlashTaskScheduleID=SCHEDULER:New(self,self.MenuTaskStatus,{TaskGroup},0,60)
else
if self.FlashTaskScheduler then
self.FlashTaskScheduler:Stop(self.FlashTaskScheduleID)
self.FlashTaskScheduler=nil
self.FlashTaskScheduleID=nil
end
end
end
function TASK:MenuTaskAbort(TaskGroup)
self:AbortGroup(TaskGroup)
end
function TASK:GetTaskName()
return self.TaskName
end
function TASK:GetTaskBriefing()
return self.TaskBriefing
end
function TASK:GetProcessTemplate(ProcessName)
local ProcessTemplate=self.ProcessClasses[ProcessName]
return ProcessTemplate
end
function TASK:FailProcesses(TaskUnitName)
for ProcessID,ProcessData in pairs(self.Processes[TaskUnitName])do
local Process=ProcessData
Process.Fsm:Fail()
end
end
function TASK:SetStateMachine(TaskUnit,Fsm)
self:F2({TaskUnit,self.Fsm[TaskUnit]~=nil,Fsm:GetClassNameAndID()})
self.Fsm[TaskUnit]=Fsm
return Fsm
end
function TASK:GetStateMachine(TaskUnit)
self:F2({TaskUnit,self.Fsm[TaskUnit]~=nil})
return self.Fsm[TaskUnit]
end
function TASK:RemoveStateMachine(TaskUnit)
self:F({TaskUnit=TaskUnit:GetName(),HasFsm=(self.Fsm[TaskUnit]~=nil)})
if self.Fsm[TaskUnit]then
self.Fsm[TaskUnit]:Remove()
self.Fsm[TaskUnit]=nil
end
collectgarbage()
self:F("Garbage Collected, Processes should be finalized now ...")
end
function TASK:HasStateMachine(TaskUnit)
self:F({TaskUnit,self.Fsm[TaskUnit]~=nil})
return(self.Fsm[TaskUnit]~=nil)
end
function TASK:GetScoring()
return self.Mission:GetScoring()
end
function TASK:GetTaskIndex()
local TaskType=self:GetType()
local TaskName=self:GetName()
return TaskType.."."..TaskName
end
function TASK:SetName(TaskName)
self.TaskName=TaskName
end
function TASK:GetName()
return self.TaskName
end
function TASK:SetType(TaskType)
self.TaskType=TaskType
end
function TASK:GetType()
return self.TaskType
end
function TASK:SetID(TaskID)
self.TaskID=TaskID
end
function TASK:GetID()
return self.TaskID
end
function TASK:StateSuccess()
self:SetState(self,"State","Success")
return self
end
function TASK:IsStateSuccess()
return self:Is("Success")
end
function TASK:StateFailed()
self:SetState(self,"State","Failed")
return self
end
function TASK:IsStateFailed()
return self:Is("Failed")
end
function TASK:StatePlanned()
self:SetState(self,"State","Planned")
return self
end
function TASK:IsStatePlanned()
return self:Is("Planned")
end
function TASK:StateAborted()
self:SetState(self,"State","Aborted")
return self
end
function TASK:IsStateAborted()
return self:Is("Aborted")
end
function TASK:StateCancelled()
self:SetState(self,"State","Cancelled")
return self
end
function TASK:IsStateCancelled()
return self:Is("Cancelled")
end
function TASK:StateAssigned()
self:SetState(self,"State","Assigned")
return self
end
function TASK:IsStateAssigned()
return self:Is("Assigned")
end
function TASK:StateHold()
self:SetState(self,"State","Hold")
return self
end
function TASK:IsStateHold()
return self:Is("Hold")
end
function TASK:StateReplanned()
self:SetState(self,"State","Replanned")
return self
end
function TASK:IsStateReplanned()
return self:Is("Replanned")
end
function TASK:GetStateString()
return self:GetState(self,"State")
end
function TASK:SetBriefing(TaskBriefing)
self:F(TaskBriefing)
self.TaskBriefing=TaskBriefing
return self
end
function TASK:GetBriefing()
return self.TaskBriefing
end
function TASK:onenterAssigned(From,Event,To,PlayerUnit,PlayerName)
if From~="Assigned"then
local PlayerNames=self:GetPlayerNames()
local PlayerText=REPORT:New()
for PlayerName,TaskName in pairs(PlayerNames)do
PlayerText:Add(PlayerName)
end
self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." is assigned to players "..PlayerText:Text(",")..". Good Luck!")
self:SetGoalTotal()
if self.Dispatcher then
self:F("Firing Assign event ")
self.Dispatcher:Assign(self,PlayerUnit,PlayerName)
end
self:GetMission():__Start(1)
self:__Goal(-10,PlayerUnit,PlayerName)
self:SetMenu()
self:F({"--> Task Assigned",TaskName=self:GetName(),Mission=self:GetMission():GetName()})
self:F({"--> Task Player Names",PlayerNames=PlayerNames})
end
end
function TASK:onenterSuccess(From,Event,To)
self:F({"<-> Task Replanned",TaskName=self:GetName(),Mission=self:GetMission():GetName()})
self:F({"<-> Task Player Names",PlayerNames=self:GetPlayerNames()})
self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." is successful! Good job!")
self:UnAssignFromGroups()
self:GetMission():__MissionGoals(1)
end
function TASK:onenterAborted(From,Event,To)
self:F({"<-- Task Aborted",TaskName=self:GetName(),Mission=self:GetMission():GetName()})
self:F({"<-- Task Player Names",PlayerNames=self:GetPlayerNames()})
if From~="Aborted"then
self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." has been aborted! Task may be replanned.")
self:__Replan(5)
self:SetMenu()
end
end
function TASK:onenterCancelled(From,Event,To)
self:F({"<-- Task Cancelled",TaskName=self:GetName(),Mission=self:GetMission():GetName()})
self:F({"<-- Player Names",PlayerNames=self:GetPlayerNames()})
if From~="Cancelled"then
self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." has been cancelled! The tactical situation has changed.")
self:UnAssignFromGroups()
self:SetMenu()
end
end
function TASK:onafterReplan(From,Event,To)
self:F({"Task Replanned",TaskName=self:GetName(),Mission=self:GetMission():GetName()})
self:F({"Task Player Names",PlayerNames=self:GetPlayerNames()})
self:GetMission():GetCommandCenter():MessageToCoalition("Replanning Task "..self:GetName()..".")
self:SetMenu()
end
function TASK:onenterFailed(From,Event,To)
self:F({"Task Failed",TaskName=self:GetName(),Mission=self:GetMission():GetName()})
self:F({"Task Player Names",PlayerNames=self:GetPlayerNames()})
self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." has failed!")
self:UnAssignFromGroups()
end
function TASK:onstatechange(From,Event,To)
if self:IsTrace()then
end
if self.Scores[To]then
local Scoring=self:GetScoring()
if Scoring then
self:F({self.Scores[To].ScoreText,self.Scores[To].Score})
Scoring:_AddMissionScore(self.Mission,self.Scores[To].ScoreText,self.Scores[To].Score)
end
end
end
function TASK:onenterPlanned(From,Event,To)
if not self.TimeOut==0 then
self.__TimeOut(self.TimeOut)
end
end
function TASK:onbeforeTimeOut(From,Event,To)
if From=="Planned"then
self:RemoveMenu()
return true
end
return false
end
do
function TASK:SetGoal(Goal)
self.Goal=Goal
end
function TASK:GetGoal()
return self.Goal
end
function TASK:SetDispatcher(Dispatcher)
self.Dispatcher=Dispatcher
end
function TASK:SetDetection(Detection,DetectedItem)
self:F({DetectedItem,Detection})
self.Detection=Detection
self.DetectedItem=DetectedItem
end
end
do
function TASK:ReportSummary(ReportGroup)
self:UpdateTaskInfo(self.DetectedItem)
local Report=REPORT:New()
Report:Add("Task "..self:GetName())
Report:Add("State: <"..self:GetState()..">")
self.TaskInfo:Report(Report,"S",ReportGroup,self)
return Report:Text(', ')
end
function TASK:ReportOverview(ReportGroup)
self:UpdateTaskInfo(self.DetectedItem)
local TaskName=self:GetName()
local Report=REPORT:New()
self.TaskInfo:Report(Report,"O",ReportGroup,self)
return Report:Text()
end
function TASK:GetPlayerCount()
local PlayerCount=0
for TaskGroupID,PlayerGroup in pairs(self:GetGroups():GetSet())do
local PlayerGroup=PlayerGroup
if PlayerGroup:IsAlive()==true then
if self:IsGroupAssigned(PlayerGroup)then
local PlayerNames=PlayerGroup:GetPlayerNames()
PlayerCount=PlayerCount+((PlayerNames)and#PlayerNames or 0)
end
end
end
return PlayerCount
end
function TASK:GetPlayerNames()
local PlayerNameMap={}
for TaskGroupID,PlayerGroup in pairs(self:GetGroups():GetSet())do
local PlayerGroup=PlayerGroup
if PlayerGroup:IsAlive()==true then
if self:IsGroupAssigned(PlayerGroup)then
local PlayerNames=PlayerGroup:GetPlayerNames()
for PlayerNameID,PlayerName in pairs(PlayerNames or{})do
PlayerNameMap[PlayerName]=PlayerGroup
end
end
end
end
return PlayerNameMap
end
function TASK:ReportDetails(ReportGroup)
self:UpdateTaskInfo(self.DetectedItem)
local Report=REPORT:New():SetIndent(3)
local Name=self:GetName()
local Status="<"..self:GetState()..">"
Report:Add("Task "..Name.." - "..Status.." - Detailed Report")
local PlayerNames=self:GetPlayerNames()
local PlayerReport=REPORT:New()
for PlayerName,PlayerGroup in pairs(PlayerNames)do
PlayerReport:Add("Players group "..PlayerGroup:GetCallsign()..": "..PlayerName)
end
local Players=PlayerReport:Text()
if Players~=""then
Report:AddIndent("Players assigned:","-")
Report:AddIndent(Players)
end
self.TaskInfo:Report(Report,"D",ReportGroup,self)
return Report:Text()
end
end
do
function TASK:AddProgress(PlayerName,ProgressText,ProgressTime,ProgressPoints)
self.TaskProgress=self.TaskProgress or{}
self.TaskProgress[ProgressTime]=self.TaskProgress[ProgressTime]or{}
self.TaskProgress[ProgressTime].PlayerName=PlayerName
self.TaskProgress[ProgressTime].ProgressText=ProgressText
self.TaskProgress[ProgressTime].ProgressPoints=ProgressPoints
self:GetMission():AddPlayerName(PlayerName)
return self
end
function TASK:GetPlayerProgress(PlayerName)
local ProgressPlayer=0
for ProgressTime,ProgressData in pairs(self.TaskProgress)do
if PlayerName==ProgressData.PlayerName then
ProgressPlayer=ProgressPlayer+ProgressData.ProgressPoints
end
end
return ProgressPlayer
end
function TASK:SetScoreOnProgress(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScoreProcess("Engaging","Account","AccountPlayer","Player "..PlayerName.." has achieved progress.",Score)
return self
end
function TASK:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Success","The task is a success!",Score)
return self
end
function TASK:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
self:F({PlayerName,Penalty,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Failed","The task is a failure!",Penalty)
return self
end
end
do
function TASK:InitTaskControlMenu(TaskUnit)
self.TaskControlMenuTime=timer.getTime()
return self.TaskControlMenuTime
end
function TASK:GetTaskControlMenu(TaskUnit,TaskName)
TaskName=TaskName or""
local TaskGroup=TaskUnit:GetGroup()
local TaskPlayerCount=TaskGroup:GetPlayerCount()
if TaskPlayerCount<=1 then
self.TaskControlMenu=MENU_GROUP:New(TaskUnit:GetGroup(),"Task "..self:GetName().." control"):SetTime(self.TaskControlMenuTime)
else
self.TaskControlMenu=MENU_GROUP:New(TaskUnit:GetGroup(),"Task "..self:GetName().." control for "..TaskUnit:GetPlayerName()):SetTime(self.TaskControlMenuTime)
end
return self.TaskControlMenu
end
function TASK:RemoveTaskControlMenu(TaskUnit)
if self.TaskControlMenu then
self.TaskControlMenu:Remove()
self.TaskControlMenu=nil
end
end
function TASK:RefreshTaskControlMenu(TaskUnit,MenuTime,MenuTag)
if self.TaskControlMenu then
self.TaskControlMenu:Remove(MenuTime,MenuTag)
end
end
end
TASKINFO={
ClassName="TASKINFO",
}
TASKINFO.Detail=""
function TASKINFO:New(Task)
local self=BASE:Inherit(self,BASE:New())
self.Task=Task
self.VolatileInfo=SET_BASE:New()
self.PersistentInfo=SET_BASE:New()
self.Info=self.VolatileInfo
return self
end
function TASKINFO:AddInfo(Key,Data,Order,Detail,Keep,ShowKey,Type)
self.VolatileInfo:Add(Key,{Data=Data,Order=Order,Detail=Detail,ShowKey=ShowKey,Type=Type})
if Keep==true then
self.PersistentInfo:Add(Key,{Data=Data,Order=Order,Detail=Detail,ShowKey=ShowKey,Type=Type})
end
return self
end
function TASKINFO:GetInfo(Key)
local Object=self:Get(Key)
return Object.Data,Object.Order,Object.Detail
end
function TASKINFO:GetData(Key)
local Object=self.Info:Get(Key)
return Object and Object.Data
end
function TASKINFO:AddText(Key,Text,Order,Detail,Keep)
self:AddInfo(Key,Text,Order,Detail,Keep)
return self
end
function TASKINFO:AddTaskName(Order,Detail,Keep)
self:AddInfo("TaskName",self.Task:GetName(),Order,Detail,Keep)
return self
end
function TASKINFO:AddCoordinate(Coordinate,Order,Detail,Keep,ShowKey,Name)
self:AddInfo(Name or"Coordinate",Coordinate,Order,Detail,Keep,ShowKey,"Coordinate")
return self
end
function TASKINFO:GetCoordinate(Name)
return self:GetData(Name or"Coordinate")
end
function TASKINFO:AddCoordinates(Coordinates,Order,Detail,Keep)
self:AddInfo("Coordinates",Coordinates,Order,Detail,Keep)
return self
end
function TASKINFO:AddThreat(ThreatText,ThreatLevel,Order,Detail,Keep)
self:AddInfo("Threat"," ["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]:"..ThreatText,Order,Detail,Keep)
return self
end
function TASKINFO:GetThreat()
self:GetInfo("Threat")
return self
end
function TASKINFO:AddTargetCount(TargetCount,Order,Detail,Keep)
self:AddInfo("Counting",string.format("%d",TargetCount),Order,Detail,Keep)
return self
end
function TASKINFO:AddTargets(TargetCount,TargetTypes,Order,Detail,Keep)
self:AddInfo("Targets",string.format("%d of %s",TargetCount,TargetTypes),Order,Detail,Keep)
return self
end
function TASKINFO:GetTargets()
self:GetInfo("Targets")
return self
end
function TASKINFO:AddQFEAtCoordinate(Coordinate,Order,Detail,Keep)
self:AddInfo("QFE",Coordinate,Order,Detail,Keep)
return self
end
function TASKINFO:AddTemperatureAtCoordinate(Coordinate,Order,Detail,Keep)
self:AddInfo("Temperature",Coordinate,Order,Detail,Keep)
return self
end
function TASKINFO:AddWindAtCoordinate(Coordinate,Order,Detail,Keep)
self:AddInfo("Wind",Coordinate,Order,Detail,Keep)
return self
end
function TASKINFO:AddCargo(Cargo,Order,Detail,Keep)
self:AddInfo("Cargo",Cargo,Order,Detail,Keep)
return self
end
function TASKINFO:AddCargoSet(SetCargo,Order,Detail,Keep)
local CargoReport=REPORT:New()
CargoReport:Add("")
SetCargo:ForEachCargo(
function(Cargo)
CargoReport:Add(string.format(' - %s (%s) %s - status %s ',Cargo:GetName(),Cargo:GetType(),Cargo:GetTransportationMethod(),Cargo:GetCurrentState()))
end
)
self:AddInfo("Cargo",CargoReport:Text(),Order,Detail,Keep)
return self
end
function TASKINFO:Report(Report,Detail,ReportGroup,Task)
local Line=0
local LineReport=REPORT:New()
if not self.Task:IsStatePlanned()and not self.Task:IsStateAssigned()then
self.Info=self.PersistentInfo
end
for Key,Data in UTILS.spairs(self.Info.Set,function(t,a,b)return t[a].Order<t[b].Order end)do
if Data.Detail:find(Detail)then
local Text=""
local ShowKey=(Data.ShowKey==nil or Data.ShowKey==true)
if Key=="TaskName"then
Key=nil
Text=Data.Data
elseif Data.Type and Data.Type=="Coordinate"then
local Coordinate=Data.Data
Text=Coordinate:ToString(ReportGroup:GetUnit(1),nil,Task)
elseif Key=="Threat"then
local DataText=Data.Data
Text=DataText
elseif Key=="Counting"then
local DataText=Data.Data
Text=DataText
elseif Key=="Targets"then
local DataText=Data.Data
Text=DataText
elseif Key=="QFE"then
local Coordinate=Data.Data
Text=Coordinate:ToStringPressure(ReportGroup:GetUnit(1),nil,Task)
elseif Key=="Temperature"then
local Coordinate=Data.Data
Text=Coordinate:ToStringTemperature(ReportGroup:GetUnit(1),nil,Task)
elseif Key=="Wind"then
local Coordinate=Data.Data
Text=Coordinate:ToStringWind(ReportGroup:GetUnit(1),nil,Task)
elseif Key=="Cargo"then
local DataText=Data.Data
Text=DataText
elseif Key=="Friendlies"then
local DataText=Data.Data
Text=DataText
elseif Key=="Players"then
local DataText=Data.Data
Text=DataText
else
local DataText=Data.Data
if type(DataText)=="string"then
Text=DataText
end
end
if Line<math.floor(Data.Order/10)then
if Line==0 then
Report:AddIndent(LineReport:Text(", "),"-")
else
Report:AddIndent(LineReport:Text(", "))
end
LineReport=REPORT:New()
Line=math.floor(Data.Order/10)
end
if Text~=""then
LineReport:Add(((Key and ShowKey==true)and(Key..": ")or"")..Text)
end
end
end
Report:AddIndent(LineReport:Text(", "))
end
do
TASK_MANAGER={
ClassName="TASK_MANAGER",
SetGroup=nil,
}
function TASK_MANAGER:New(SetGroup)
local self=BASE:Inherit(self,FSM:New())
self.SetGroup=SetGroup
self:SetStartState("Stopped")
self:AddTransition("Stopped","StartTasks","Started")
self:AddTransition("Started","StopTasks","Stopped")
self:AddTransition("Started","Manage","Started")
self:AddTransition("Started","Success","Started")
self:AddTransition("Started","Failed","Started")
self:AddTransition("Started","Aborted","Started")
self:AddTransition("Started","Cancelled","Started")
self:SetRefreshTimeInterval(30)
return self
end
function TASK_MANAGER:onafterStartTasks(From,Event,To)
self:Manage()
end
function TASK_MANAGER:onafterManage(From,Event,To)
self:__Manage(-self._RefreshTimeInterval)
self:ManageTasks()
end
function TASK_MANAGER:SetRefreshTimeInterval(RefreshTimeInterval)
self:F2()
self._RefreshTimeInterval=RefreshTimeInterval
end
function TASK_MANAGER:ManageTasks()
end
end
do
DETECTION_MANAGER={
ClassName="DETECTION_MANAGER",
SetGroup=nil,
Detection=nil,
}
DETECTION_MANAGER.CC=nil
function DETECTION_MANAGER:New(SetGroup,Detection)
local self=BASE:Inherit(self,FSM:New())
self.SetGroup=SetGroup
self.Detection=Detection
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Started")
self:AddTransition("Started","Stop","Stopped")
self:AddTransition("Started","Success","Started")
self:AddTransition("Started","Failed","Started")
self:AddTransition("Started","Aborted","Started")
self:AddTransition("Started","Cancelled","Started")
self:AddTransition("Started","Report","Started")
self:SetRefreshTimeInterval(30)
self:SetReportDisplayTime(25)
Detection:__Start(3)
return self
end
function DETECTION_MANAGER:onafterStart(From,Event,To)
self:Report()
end
function DETECTION_MANAGER:onafterReport(From,Event,To)
self:__Report(-self._RefreshTimeInterval)
self:ProcessDetected(self.Detection)
end
function DETECTION_MANAGER:SetRefreshTimeInterval(RefreshTimeInterval)
self:F2()
self._RefreshTimeInterval=RefreshTimeInterval
end
function DETECTION_MANAGER:SetReportDisplayTime(ReportDisplayTime)
self:F2()
self._ReportDisplayTime=ReportDisplayTime
end
function DETECTION_MANAGER:GetReportDisplayTime()
self:F2()
return self._ReportDisplayTime
end
function DETECTION_MANAGER:SetTacticalMenu(DispatcherMainMenuText,DispatcherMenuText)
local DispatcherMainMenu=MENU_MISSION:New(DispatcherMainMenuText,nil)
local DispatcherMenu=MENU_MISSION_COMMAND:New(DispatcherMenuText,DispatcherMainMenu,
function()
self:ShowTacticalDisplay(self.Detection)
end
)
return self
end
function DETECTION_MANAGER:SetCommandCenter(CommandCenter)
self.CC=CommandCenter
return self
end
function DETECTION_MANAGER:GetCommandCenter()
return self.CC
end
function DETECTION_MANAGER:MessageToPlayers(Squadron,Message,DefenderGroup)
self:F({Message=Message})
if self.CC then
self.CC:MessageToCoalition(Message)
end
Message=Message:gsub("°"," degrees ")
Message=Message:gsub("(%d)%.(%d)","%1 dot %2")
local RadioQueue=Squadron.RadioQueue
if RadioQueue then
local DefenderUnit=DefenderGroup:GetUnit(1)
if DefenderUnit and DefenderUnit:IsAlive()then
RadioQueue:SetSenderUnitName(DefenderUnit:GetName())
end
RadioQueue:Speak(Message,Squadron.Language)
end
return self
end
function DETECTION_MANAGER:ProcessDetected(Detection)
end
end
do
DETECTION_REPORTING={
ClassName="DETECTION_REPORTING",
}
function DETECTION_REPORTING:New(SetGroup,Detection)
local self=BASE:Inherit(self,DETECTION_MANAGER:New(SetGroup,Detection))
self:Schedule(1,30)
return self
end
function DETECTION_REPORTING:GetDetectedItemsText(DetectedSet)
self:F2()
local MT={}
local UnitTypes={}
for DetectedUnitID,DetectedUnitData in pairs(DetectedSet:GetSet())do
local DetectedUnit=DetectedUnitData
if DetectedUnit:IsAlive()then
local UnitType=DetectedUnit:GetTypeName()
if not UnitTypes[UnitType]then
UnitTypes[UnitType]=1
else
UnitTypes[UnitType]=UnitTypes[UnitType]+1
end
end
end
for UnitTypeID,UnitType in pairs(UnitTypes)do
MT[#MT+1]=UnitType.." of "..UnitTypeID
end
return table.concat(MT,", ")
end
function DETECTION_REPORTING:ProcessDetected(Group,Detection)
self:F2(Group)
local DetectedMsg={}
for DetectedAreaID,DetectedAreaData in pairs(Detection:GetDetectedAreas())do
local DetectedArea=DetectedAreaData
DetectedMsg[#DetectedMsg+1]=" - Group #"..DetectedAreaID..": "..self:GetDetectedItemsText(DetectedArea.Set)
end
local FACGroup=Detection:GetDetectionGroups()
FACGroup:MessageToGroup("Reporting detected target groups:\n"..table.concat(DetectedMsg,"\n"),self:GetReportDisplayTime(),Group)
return true
end
end
do
TASK_A2G_DISPATCHER={
ClassName="TASK_A2G_DISPATCHER",
Mission=nil,
Detection=nil,
Tasks={}
}
function TASK_A2G_DISPATCHER:New(Mission,SetGroup,Detection)
local self=BASE:Inherit(self,DETECTION_MANAGER:New(SetGroup,Detection))
self.Detection=Detection
self.Mission=Mission
self.FlashNewTask=true
self.Detection:FilterCategories({Unit.Category.GROUND_UNIT})
self:AddTransition("Started","Assign","Started")
self:__Start(5)
return self
end
function TASK_A2G_DISPATCHER:SetSendMessages(onoff)
self.FlashNewTask=onoff
end
function TASK_A2G_DISPATCHER:EvaluateSEAD(DetectedItem)
self:F({DetectedItem.ItemID})
local DetectedSet=DetectedItem.Set
local DetectedZone=DetectedItem.Zone
local RadarCount=DetectedSet:HasSEAD()
if RadarCount>0 then
local TargetSetUnit=SET_UNIT:New()
TargetSetUnit:SetDatabase(DetectedSet)
TargetSetUnit:FilterHasSEAD()
TargetSetUnit:FilterOnce()
return TargetSetUnit
end
return nil
end
function TASK_A2G_DISPATCHER:EvaluateCAS(DetectedItem)
self:F({DetectedItem.ItemID})
local DetectedSet=DetectedItem.Set
local DetectedZone=DetectedItem.Zone
local GroundUnitCount=DetectedSet:HasGroundUnits()
local FriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT)
local RadarCount=DetectedSet:HasSEAD()
if RadarCount==0 and GroundUnitCount>0 and FriendliesNearBy==true then
local TargetSetUnit=SET_UNIT:New()
TargetSetUnit:SetDatabase(DetectedSet)
TargetSetUnit:FilterOnce()
return TargetSetUnit
end
return nil
end
function TASK_A2G_DISPATCHER:EvaluateBAI(DetectedItem,FriendlyCoalition)
self:F({DetectedItem.ItemID})
local DetectedSet=DetectedItem.Set
local DetectedZone=DetectedItem.Zone
local GroundUnitCount=DetectedSet:HasGroundUnits()
local FriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT)
local RadarCount=DetectedSet:HasSEAD()
if RadarCount==0 and GroundUnitCount>0 and FriendliesNearBy==false then
local TargetSetUnit=SET_UNIT:New()
TargetSetUnit:SetDatabase(DetectedSet)
TargetSetUnit:FilterOnce()
return TargetSetUnit
end
return nil
end
function TASK_A2G_DISPATCHER:RemoveTask(TaskIndex)
self.Mission:RemoveTask(self.Tasks[TaskIndex])
self.Tasks[TaskIndex]=nil
end
function TASK_A2G_DISPATCHER:EvaluateRemoveTask(Mission,Task,TaskIndex,DetectedItemChanged)
if Task then
if(Task:IsStatePlanned()and DetectedItemChanged==true)or Task:IsStateCancelled()then
self:RemoveTask(TaskIndex)
end
end
return Task
end
function TASK_A2G_DISPATCHER:ProcessDetected(Detection)
self:F()
local AreaMsg={}
local TaskMsg={}
local ChangeMsg={}
local Mission=self.Mission
if Mission:IsIDLE()or Mission:IsENGAGED()then
local TaskReport=REPORT:New()
for TaskIndex,TaskData in pairs(self.Tasks)do
local Task=TaskData
if Task:IsStatePlanned()then
local DetectedItem=Detection:GetDetectedItemByIndex(TaskIndex)
if not DetectedItem then
local TaskText=Task:GetName()
for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
if self.FlashNewTask then
Mission:GetCommandCenter():MessageToGroup(string.format("Obsolete A2G task %s for %s removed.",TaskText,Mission:GetShortText()),TaskGroup)
end
end
Task=self:RemoveTask(TaskIndex)
end
end
end
for DetectedItemID,DetectedItem in pairs(Detection:GetDetectedItems())do
local DetectedItem=DetectedItem
local DetectedSet=DetectedItem.Set
local DetectedZone=DetectedItem.Zone
local DetectedItemID=DetectedItem.ID
local TaskIndex=DetectedItem.Index
local DetectedItemChanged=DetectedItem.Changed
self:F({DetectedItemChanged=DetectedItemChanged,DetectedItemID=DetectedItemID,TaskIndex=TaskIndex})
local Task=self.Tasks[TaskIndex]
if Task then
if Task:IsStateAssigned()then
if DetectedItemChanged==true then
local TargetsReport=REPORT:New()
local TargetSetUnit=self:EvaluateSEAD(DetectedItem)
if TargetSetUnit then
if Task:IsInstanceOf(TASK_A2G_SEAD)then
Task:SetTargetSetUnit(TargetSetUnit)
Task:SetDetection(Detection,DetectedItem)
Task:UpdateTaskInfo(DetectedItem)
TargetsReport:Add(Detection:GetChangeText(DetectedItem))
else
Task:Cancel()
end
else
local TargetSetUnit=self:EvaluateCAS(DetectedItem)
if TargetSetUnit then
if Task:IsInstanceOf(TASK_A2G_CAS)then
Task:SetTargetSetUnit(TargetSetUnit)
Task:SetDetection(Detection,DetectedItem)
Task:UpdateTaskInfo(DetectedItem)
TargetsReport:Add(Detection:GetChangeText(DetectedItem))
else
Task:Cancel()
Task=self:RemoveTask(TaskIndex)
end
else
local TargetSetUnit=self:EvaluateBAI(DetectedItem)
if TargetSetUnit then
if Task:IsInstanceOf(TASK_A2G_BAI)then
Task:SetTargetSetUnit(TargetSetUnit)
Task:SetDetection(Detection,DetectedItem)
Task:UpdateTaskInfo(DetectedItem)
TargetsReport:Add(Detection:GetChangeText(DetectedItem))
else
Task:Cancel()
Task=self:RemoveTask(TaskIndex)
end
end
end
end
for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
local TargetsText=TargetsReport:Text(", ")
if(Mission:IsGroupAssigned(TaskGroup))and TargetsText~=""and self.FlashNewTask then
Mission:GetCommandCenter():MessageToGroup(string.format("Task %s has change of targets:\n %s",Task:GetName(),TargetsText),TaskGroup)
end
end
end
end
end
if Task then
if Task:IsStatePlanned()then
if DetectedItemChanged==true then
if Task:IsInstanceOf(TASK_A2G_SEAD)then
local TargetSetUnit=self:EvaluateSEAD(DetectedItem)
if TargetSetUnit then
Task:SetTargetSetUnit(TargetSetUnit)
Task:SetDetection(Detection,DetectedItem)
Task:UpdateTaskInfo(DetectedItem)
else
Task:Cancel()
Task=self:RemoveTask(TaskIndex)
end
else
if Task:IsInstanceOf(TASK_A2G_CAS)then
local TargetSetUnit=self:EvaluateCAS(DetectedItem)
if TargetSetUnit then
Task:SetTargetSetUnit(TargetSetUnit)
Task:SetDetection(Detection,DetectedItem)
Task:UpdateTaskInfo(DetectedItem)
else
Task:Cancel()
Task=self:RemoveTask(TaskIndex)
end
else
if Task:IsInstanceOf(TASK_A2G_BAI)then
local TargetSetUnit=self:EvaluateBAI(DetectedItem)
if TargetSetUnit then
Task:SetTargetSetUnit(TargetSetUnit)
Task:SetDetection(Detection,DetectedItem)
Task:UpdateTaskInfo(DetectedItem)
else
Task:Cancel()
Task=self:RemoveTask(TaskIndex)
end
else
Task:Cancel()
Task=self:RemoveTask(TaskIndex)
end
end
end
end
end
end
if not Task then
local TargetSetUnit=self:EvaluateSEAD(DetectedItem)
if TargetSetUnit then
Task=TASK_A2G_SEAD:New(Mission,self.SetGroup,string.format("SEAD.%03d",DetectedItemID),TargetSetUnit)
DetectedItem.DesignateMenuName=string.format("SEAD.%03d",DetectedItemID)
Task:SetDetection(Detection,DetectedItem)
end
if not Task then
local TargetSetUnit=self:EvaluateCAS(DetectedItem)
if TargetSetUnit then
Task=TASK_A2G_CAS:New(Mission,self.SetGroup,string.format("CAS.%03d",DetectedItemID),TargetSetUnit)
DetectedItem.DesignateMenuName=string.format("CAS.%03d",DetectedItemID)
Task:SetDetection(Detection,DetectedItem)
end
if not Task then
local TargetSetUnit=self:EvaluateBAI(DetectedItem,self.Mission:GetCommandCenter():GetPositionable():GetCoalition())
if TargetSetUnit then
Task=TASK_A2G_BAI:New(Mission,self.SetGroup,string.format("BAI.%03d",DetectedItemID),TargetSetUnit)
DetectedItem.DesignateMenuName=string.format("BAI.%03d",DetectedItemID)
Task:SetDetection(Detection,DetectedItem)
end
end
end
if Task then
self.Tasks[TaskIndex]=Task
Task:SetTargetZone(DetectedZone)
Task:SetDispatcher(self)
Task:UpdateTaskInfo(DetectedItem)
Mission:AddTask(Task)
function Task.OnEnterSuccess(Task,From,Event,To)
self:Success(Task)
end
function Task.OnEnterCancelled(Task,From,Event,To)
self:Cancelled(Task)
end
function Task.OnEnterFailed(Task,From,Event,To)
self:Failed(Task)
end
function Task.OnEnterAborted(Task,From,Event,To)
self:Aborted(Task)
end
TaskReport:Add(Task:GetName())
else
self:F("This should not happen")
end
end
Detection:AcceptChanges(DetectedItem)
end
Mission:GetCommandCenter():SetMenu()
local TaskText=TaskReport:Text(", ")
for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
if(not Mission:IsGroupAssigned(TaskGroup))and TaskText~=""and self.FlashNewTask then
Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetShortText(),TaskText),TaskGroup)
end
end
end
return true
end
end
do
TASK_A2G={
ClassName="TASK_A2G"
}
function TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskType,TaskBriefing)
local self=BASE:Inherit(self,TASK:New(Mission,SetGroup,TaskName,TaskType,TaskBriefing))
self:F()
self.TargetSetUnit=TargetSetUnit
self.TaskType=TaskType
local Fsm=self:GetUnitProcess()
Fsm:AddTransition("Assigned","RouteToRendezVous","RoutingToRendezVous")
Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtRendezVous"})
Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtRendezVous"})
Fsm:AddTransition({"Arrived","RoutingToRendezVous"},"ArriveAtRendezVous","ArrivedAtRendezVous")
Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"Engage","Engaging")
Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"HoldAtRendezVous","HoldingAtRendezVous")
Fsm:AddProcess("Engaging","Account",ACT_ACCOUNT_DEADS:New(),{})
Fsm:AddTransition("Engaging","RouteToTarget","Engaging")
Fsm:AddProcess("Engaging","RouteToTargetZone",ACT_ROUTE_ZONE:New(),{})
Fsm:AddProcess("Engaging","RouteToTargetPoint",ACT_ROUTE_POINT:New(),{})
Fsm:AddTransition("Engaging","RouteToTargets","Engaging")
Fsm:AddTransition("Rejected","Reject","Aborted")
Fsm:AddTransition("Failed","Fail","Failed")
function Fsm:onafterAssigned(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
self:RouteToRendezVous()
end
function Fsm:onafterRouteToRendezVous(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
if Task:GetRendezVousZone(TaskUnit)then
self:__RouteToRendezVousZone(0.1)
else
if Task:GetRendezVousCoordinate(TaskUnit)then
self:__RouteToRendezVousPoint(0.1)
else
self:__ArriveAtRendezVous(0.1)
end
end
end
function Fsm:OnAfterArriveAtRendezVous(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
self:__Engage(0.1)
end
function Fsm:onafterEngage(TaskUnit,Task)
self:F({self})
self:__Account(0.1)
self:__RouteToTarget(0.1)
self:__RouteToTargets(-10)
end
function Fsm:onafterRouteToTarget(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
if Task:GetTargetZone(TaskUnit)then
self:__RouteToTargetZone(0.1)
else
local TargetUnit=Task.TargetSetUnit:GetFirst()
if TargetUnit then
local Coordinate=TargetUnit:GetPointVec3()
self:T({TargetCoordinate=Coordinate,Coordinate:GetX(),Coordinate:GetY(),Coordinate:GetZ()})
Task:SetTargetCoordinate(Coordinate,TaskUnit)
end
self:__RouteToTargetPoint(0.1)
end
end
function Fsm:onafterRouteToTargets(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
local TargetUnit=Task.TargetSetUnit:GetFirst()
if TargetUnit then
Task:SetTargetCoordinate(TargetUnit:GetCoordinate(),TaskUnit)
end
self:__RouteToTargets(-10)
end
return self
end
function TASK_A2G:SetTargetSetUnit(TargetSetUnit)
self.TargetSetUnit=TargetSetUnit
end
function TASK_A2G:GetPlannedMenuText()
return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )"
end
function TASK_A2G:SetRendezVousCoordinate(RendezVousCoordinate,RendezVousRange,TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint")
ActRouteRendezVous:SetCoordinate(RendezVousCoordinate)
ActRouteRendezVous:SetRange(RendezVousRange)
end
function TASK_A2G:GetRendezVousCoordinate(TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint")
return ActRouteRendezVous:GetCoordinate(),ActRouteRendezVous:GetRange()
end
function TASK_A2G:SetRendezVousZone(RendezVousZone,TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone")
ActRouteRendezVous:SetZone(RendezVousZone)
end
function TASK_A2G:GetRendezVousZone(TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone")
return ActRouteRendezVous:GetZone()
end
function TASK_A2G:SetTargetCoordinate(TargetCoordinate,TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint")
ActRouteTarget:SetCoordinate(TargetCoordinate)
end
function TASK_A2G:GetTargetCoordinate(TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint")
return ActRouteTarget:GetCoordinate()
end
function TASK_A2G:SetTargetZone(TargetZone,TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone")
ActRouteTarget:SetZone(TargetZone)
end
function TASK_A2G:GetTargetZone(TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone")
return ActRouteTarget:GetZone()
end
function TASK_A2G:SetGoalTotal()
self.GoalTotal=self.TargetSetUnit:Count()
end
function TASK_A2G:GetGoalTotal()
return self.GoalTotal
end
function TASK_A2G:ReportOrder(ReportGroup)
self:UpdateTaskInfo(self.DetectedItem)
local Coordinate=self.TaskInfo:GetData("Coordinate")
local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
return Distance
end
function TASK_A2G:onafterGoal(TaskUnit,From,Event,To)
local TargetSetUnit=self.TargetSetUnit
if TargetSetUnit:Count()==0 then
self:Success()
end
self:__Goal(-10)
end
function TASK_A2G:UpdateTaskInfo(DetectedItem)
if self:IsStatePlanned()or self:IsStateAssigned()then
local TargetCoordinate=DetectedItem and self.Detection:GetDetectedItemCoordinate(DetectedItem)or self.TargetSetUnit:GetFirst():GetCoordinate()
self.TaskInfo:AddTaskName(0,"MSOD")
self.TaskInfo:AddCoordinate(TargetCoordinate,1,"SOD")
local ThreatLevel,ThreatText
if DetectedItem then
ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(DetectedItem)
else
ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G()
end
self.TaskInfo:AddThreat(ThreatText,ThreatLevel,10,"MOD",true)
if self.Detection then
local DetectedItemsCount=self.TargetSetUnit:Count()
local ReportTypes=REPORT:New()
local TargetTypes={}
for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do
local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit)
if not TargetTypes[TargetType]then
TargetTypes[TargetType]=TargetType
ReportTypes:Add(TargetType)
end
end
self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true)
self.TaskInfo:AddTargets(DetectedItemsCount,ReportTypes:Text(", "),20,"D",true)
else
local DetectedItemsCount=self.TargetSetUnit:Count()
local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true)
self.TaskInfo:AddTargets(DetectedItemsCount,DetectedItemsTypes,20,"D",true)
end
self.TaskInfo:AddQFEAtCoordinate(TargetCoordinate,30,"MOD")
self.TaskInfo:AddTemperatureAtCoordinate(TargetCoordinate,31,"MD")
self.TaskInfo:AddWindAtCoordinate(TargetCoordinate,32,"MD")
end
end
function TASK_A2G:GetAutoAssignPriority(AutoAssignMethod,CommandCenter,TaskGroup)
if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then
return math.random(1,9)
elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then
local Coordinate=self.TaskInfo:GetData("Coordinate")
local Distance=Coordinate:Get2DDistance(CommandCenter:GetPositionable():GetCoordinate())
self:F({Distance=Distance})
return math.floor(Distance)
elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then
return 1
end
return 0
end
end
do
TASK_A2G_SEAD={
ClassName="TASK_A2G_SEAD"
}
function TASK_A2G_SEAD:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing)
local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"SEAD",TaskBriefing))
self:F()
Mission:AddTask(self)
self:SetBriefing(TaskBriefing or"Execute a Suppression of Enemy Air Defenses.")
return self
end
function TASK_A2G_SEAD:SetScoreOnProgress(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has SEADed a target.",Score)
return self
end
function TASK_A2G_SEAD:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Success","All radar emitting targets have been successfully SEADed!",Score)
return self
end
function TASK_A2G_SEAD:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
self:F({PlayerName,Penalty,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Failed","The SEADing has failed!",Penalty)
return self
end
end
do
TASK_A2G_BAI={ClassName="TASK_A2G_BAI"}
function TASK_A2G_BAI:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing)
local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"BAI",TaskBriefing))
self:F()
Mission:AddTask(self)
self:SetBriefing(TaskBriefing or"Execute a Battlefield Air Interdiction of a group of enemy targets.")
return self
end
function TASK_A2G_BAI:SetScoreOnProgress(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has destroyed a target in Battlefield Air Interdiction (BAI).",Score)
return self
end
function TASK_A2G_BAI:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Success","All targets have been successfully destroyed! The Battlefield Air Interdiction (BAI) is a success!",Score)
return self
end
function TASK_A2G_BAI:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
self:F({PlayerName,Penalty,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Failed","The Battlefield Air Interdiction (BAI) has failed!",Penalty)
return self
end
end
do
TASK_A2G_CAS={ClassName="TASK_A2G_CAS"}
function TASK_A2G_CAS:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing)
local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"CAS",TaskBriefing))
self:F()
Mission:AddTask(self)
self:SetBriefing(TaskBriefing or("Execute a Close Air Support for a group of enemy targets. ".."Beware of friendlies at the vicinity! "))
return self
end
function TASK_A2G_CAS:SetScoreOnProgress(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has destroyed a target in Close Air Support (CAS).",Score)
return self
end
function TASK_A2G_CAS:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Success","All targets have been successfully destroyed! The Close Air Support (CAS) was a success!",Score)
return self
end
function TASK_A2G_CAS:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
self:F({PlayerName,Penalty,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Failed","The Close Air Support (CAS) has failed!",Penalty)
return self
end
end
do
TASK_A2A_DISPATCHER={
ClassName="TASK_A2A_DISPATCHER",
Mission=nil,
Detection=nil,
Tasks={},
SweepZones={},
}
function TASK_A2A_DISPATCHER:New(Mission,SetGroup,Detection)
local self=BASE:Inherit(self,DETECTION_MANAGER:New(SetGroup,Detection))
self.Detection=Detection
self.Mission=Mission
self.FlashNewTask=false
self.Detection:FilterCategories(Unit.Category.AIRPLANE,Unit.Category.HELICOPTER)
self.Detection:InitDetectRadar(true)
self.Detection:SetRefreshTimeInterval(30)
self:AddTransition("Started","Assign","Started")
self:__Start(5)
return self
end
function TASK_A2A_DISPATCHER:SetEngageRadius(EngageRadius)
self.Detection:SetFriendliesRange(EngageRadius or 100000)
return self
end
function TASK_A2A_DISPATCHER:SetSendMessages(onoff)
self.FlashNewTask=onoff
end
function TASK_A2A_DISPATCHER:EvaluateINTERCEPT(DetectedItem)
self:F({DetectedItem.ItemID})
local DetectedSet=DetectedItem.Set
local DetectedZone=DetectedItem.Zone
if DetectedItem.IsDetected==true then
local TargetSetUnit=SET_UNIT:New()
TargetSetUnit:SetDatabase(DetectedSet)
TargetSetUnit:FilterOnce()
return TargetSetUnit
end
return nil
end
function TASK_A2A_DISPATCHER:EvaluateSWEEP(DetectedItem)
self:F({DetectedItem.ItemID})
local DetectedSet=DetectedItem.Set
local DetectedZone=DetectedItem.Zone
if DetectedItem.IsDetected==false then
local TargetSetUnit=SET_UNIT:New()
TargetSetUnit:SetDatabase(DetectedSet)
TargetSetUnit:FilterOnce()
return TargetSetUnit
end
return nil
end
function TASK_A2A_DISPATCHER:EvaluateENGAGE(DetectedItem)
self:F({DetectedItem.ItemID})
local DetectedSet=DetectedItem.Set
local DetectedZone=DetectedItem.Zone
local PlayersCount,PlayersReport=self:GetPlayerFriendliesNearBy(DetectedItem)
if PlayersCount>0 and DetectedItem.IsDetected==true then
local TargetSetUnit=SET_UNIT:New()
TargetSetUnit:SetDatabase(DetectedSet)
TargetSetUnit:FilterOnce()
return TargetSetUnit
end
return nil
end
function TASK_A2A_DISPATCHER:EvaluateRemoveTask(Mission,Task,Detection,DetectedItem,DetectedItemIndex,DetectedItemChanged)
if Task then
if Task:IsStatePlanned()then
local TaskName=Task:GetName()
local TaskType=TaskName:match("(%u+)%.%d+")
self:T2({TaskType=TaskType})
local Remove=false
local IsPlayers=Detection:IsPlayersNearBy(DetectedItem)
if TaskType=="ENGAGE"then
if IsPlayers==false then
Remove=true
end
end
if TaskType=="INTERCEPT"then
if IsPlayers==true then
Remove=true
end
if DetectedItem.IsDetected==false then
Remove=true
end
end
if TaskType=="SWEEP"then
if DetectedItem.IsDetected==true then
Remove=true
end
end
local DetectedSet=DetectedItem.Set
if DetectedSet:Count()==0 then
Remove=true
end
if DetectedItemChanged==true or Remove then
Task=self:RemoveTask(DetectedItemIndex)
end
end
end
return Task
end
function TASK_A2A_DISPATCHER:GetFriendliesNearBy(DetectedItem)
local DetectedSet=DetectedItem.Set
local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem,Unit.Category.AIRPLANE)
local FriendlyTypes={}
local FriendliesCount=0
if FriendlyUnitsNearBy then
local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G()
for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do
local FriendlyUnit=FriendlyUnitData
if FriendlyUnit:IsAirPlane()then
local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel()
FriendliesCount=FriendliesCount+1
local FriendlyType=FriendlyUnit:GetTypeName()
FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1
if DetectedTreatLevel<FriendlyUnitThreatLevel+2 then
end
end
end
end
local FriendlyTypesReport=REPORT:New()
if FriendliesCount>0 then
for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do
FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType))
end
else
FriendlyTypesReport:Add("-")
end
return FriendliesCount,FriendlyTypesReport
end
function TASK_A2A_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem)
local DetectedSet=DetectedItem.Set
local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem)
local PlayerTypes={}
local PlayersCount=0
if PlayersNearBy then
local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G()
for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do
local PlayerUnit=PlayerUnitData
local PlayerName=PlayerUnit:GetPlayerName()
if PlayerUnit:IsAirPlane()and PlayerName~=nil then
local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel()
PlayersCount=PlayersCount+1
local PlayerType=PlayerUnit:GetTypeName()
PlayerTypes[PlayerName]=PlayerType
if DetectedTreatLevel<FriendlyUnitThreatLevel+2 then
end
end
end
end
local PlayerTypesReport=REPORT:New()
if PlayersCount>0 then
for PlayerName,PlayerType in pairs(PlayerTypes)do
PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType))
end
else
PlayerTypesReport:Add("-")
end
return PlayersCount,PlayerTypesReport
end
function TASK_A2A_DISPATCHER:RemoveTask(TaskIndex)
self.Mission:RemoveTask(self.Tasks[TaskIndex])
self.Tasks[TaskIndex]=nil
end
function TASK_A2A_DISPATCHER:ProcessDetected(Detection)
self:F()
local AreaMsg={}
local TaskMsg={}
local ChangeMsg={}
local Mission=self.Mission
if Mission:IsIDLE()or Mission:IsENGAGED()then
local TaskReport=REPORT:New()
for TaskIndex,TaskData in pairs(self.Tasks)do
local Task=TaskData
if Task:IsStatePlanned()then
local DetectedItem=Detection:GetDetectedItemByIndex(TaskIndex)
if not DetectedItem then
local TaskText=Task:GetName()
for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
Mission:GetCommandCenter():MessageToGroup(string.format("Obsolete A2A task %s for %s removed.",TaskText,Mission:GetShortText()),TaskGroup)
end
Task=self:RemoveTask(TaskIndex)
end
end
end
for DetectedItemID,DetectedItem in pairs(Detection:GetDetectedItems())do
local DetectedItem=DetectedItem
local DetectedSet=DetectedItem.Set
local DetectedCount=DetectedSet:Count()
local DetectedZone=DetectedItem.Zone
local DetectedID=DetectedItem.ID
local TaskIndex=DetectedItem.Index
local DetectedItemChanged=DetectedItem.Changed
local Task=self.Tasks[TaskIndex]
Task=self:EvaluateRemoveTask(Mission,Task,Detection,DetectedItem,TaskIndex,DetectedItemChanged)
if not Task and DetectedCount>0 then
local TargetSetUnit=self:EvaluateENGAGE(DetectedItem)
if TargetSetUnit then
Task=TASK_A2A_ENGAGE:New(Mission,self.SetGroup,string.format("ENGAGE.%03d",DetectedID),TargetSetUnit)
Task:SetDetection(Detection,DetectedItem)
Task:UpdateTaskInfo(DetectedItem)
else
local TargetSetUnit=self:EvaluateINTERCEPT(DetectedItem)
if TargetSetUnit then
Task=TASK_A2A_INTERCEPT:New(Mission,self.SetGroup,string.format("INTERCEPT.%03d",DetectedID),TargetSetUnit)
Task:SetDetection(Detection,DetectedItem)
Task:UpdateTaskInfo(DetectedItem)
else
local TargetSetUnit=self:EvaluateSWEEP(DetectedItem)
if TargetSetUnit then
Task=TASK_A2A_SWEEP:New(Mission,self.SetGroup,string.format("SWEEP.%03d",DetectedID),TargetSetUnit)
Task:SetDetection(Detection,DetectedItem)
Task:UpdateTaskInfo(DetectedItem)
end
end
end
if Task then
self.Tasks[TaskIndex]=Task
Task:SetTargetZone(DetectedZone,DetectedItem.Coordinate.y,DetectedItem.Coordinate.Heading)
Task:SetDispatcher(self)
Mission:AddTask(Task)
function Task.OnEnterSuccess(Task,From,Event,To)
self:Success(Task)
end
function Task.OnEnterCancelled(Task,From,Event,To)
self:Cancelled(Task)
end
function Task.OnEnterFailed(Task,From,Event,To)
self:Failed(Task)
end
function Task.OnEnterAborted(Task,From,Event,To)
self:Aborted(Task)
end
TaskReport:Add(Task:GetName())
else
self:F("This should not happen")
end
end
if Task then
local FriendliesCount,FriendliesReport=self:GetFriendliesNearBy(DetectedItem,Unit.Category.AIRPLANE)
Task.TaskInfo:AddText("Friendlies",string.format("%d ( %s )",FriendliesCount,FriendliesReport:Text(",")),40,"MOD")
local PlayersCount,PlayersReport=self:GetPlayerFriendliesNearBy(DetectedItem)
Task.TaskInfo:AddText("Players",string.format("%d ( %s )",PlayersCount,PlayersReport:Text(",")),40,"MOD")
end
Detection:AcceptChanges(DetectedItem)
end
Mission:GetCommandCenter():SetMenu()
local TaskText=TaskReport:Text(", ")
for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
if(not Mission:IsGroupAssigned(TaskGroup))and TaskText~=""and(self.FlashNewTask)then
Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetShortText(),TaskText),TaskGroup)
end
end
end
return true
end
end
do
TASK_A2A={
ClassName="TASK_A2A"
}
function TASK_A2A:New(Mission,SetAttack,TaskName,TargetSetUnit,TaskType,TaskBriefing)
local self=BASE:Inherit(self,TASK:New(Mission,SetAttack,TaskName,TaskType,TaskBriefing))
self:F()
self.TargetSetUnit=TargetSetUnit
self.TaskType=TaskType
local Fsm=self:GetUnitProcess()
Fsm:AddTransition("Assigned","RouteToRendezVous","RoutingToRendezVous")
Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtRendezVous"})
Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtRendezVous"})
Fsm:AddTransition({"Arrived","RoutingToRendezVous"},"ArriveAtRendezVous","ArrivedAtRendezVous")
Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"Engage","Engaging")
Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"HoldAtRendezVous","HoldingAtRendezVous")
Fsm:AddProcess("Engaging","Account",ACT_ACCOUNT_DEADS:New(),{})
Fsm:AddTransition("Engaging","RouteToTarget","Engaging")
Fsm:AddProcess("Engaging","RouteToTargetZone",ACT_ROUTE_ZONE:New(),{})
Fsm:AddProcess("Engaging","RouteToTargetPoint",ACT_ROUTE_POINT:New(),{})
Fsm:AddTransition("Engaging","RouteToTargets","Engaging")
Fsm:AddTransition("Rejected","Reject","Aborted")
Fsm:AddTransition("Failed","Fail","Failed")
function Fsm:OnLeaveAssigned(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
self:SelectAction()
end
function Fsm:onafterRouteToRendezVous(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
if Task:GetRendezVousZone(TaskUnit)then
self:__RouteToRendezVousZone(0.1)
else
if Task:GetRendezVousCoordinate(TaskUnit)then
self:__RouteToRendezVousPoint(0.1)
else
self:__ArriveAtRendezVous(0.1)
end
end
end
function Fsm:OnAfterArriveAtRendezVous(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
self:__Engage(0.1)
end
function Fsm:onafterEngage(TaskUnit,Task)
self:F({self})
self:__Account(0.1)
self:__RouteToTarget(0.1)
self:__RouteToTargets(-10)
end
function Fsm:onafterRouteToTarget(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
if Task:GetTargetZone(TaskUnit)then
self:__RouteToTargetZone(0.1)
else
local TargetUnit=Task.TargetSetUnit:GetFirst()
if TargetUnit then
local Coordinate=TargetUnit:GetPointVec3()
self:T({TargetCoordinate=Coordinate,Coordinate:GetX(),Coordinate:GetAlt(),Coordinate:GetZ()})
Task:SetTargetCoordinate(Coordinate,TaskUnit)
end
self:__RouteToTargetPoint(0.1)
end
end
function Fsm:onafterRouteToTargets(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
local TargetUnit=Task.TargetSetUnit:GetFirst()
if TargetUnit then
Task:SetTargetCoordinate(TargetUnit:GetCoordinate(),TaskUnit)
end
self:__RouteToTargets(-10)
end
return self
end
function TASK_A2A:SetTargetSetUnit(TargetSetUnit)
self.TargetSetUnit=TargetSetUnit
end
function TASK_A2A:GetPlannedMenuText()
return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )"
end
function TASK_A2A:SetRendezVousCoordinate(RendezVousCoordinate,RendezVousRange,TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint")
ActRouteRendezVous:SetCoordinate(RendezVousCoordinate)
ActRouteRendezVous:SetRange(RendezVousRange)
end
function TASK_A2A:GetRendezVousCoordinate(TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint")
return ActRouteRendezVous:GetCoordinate(),ActRouteRendezVous:GetRange()
end
function TASK_A2A:SetRendezVousZone(RendezVousZone,TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone")
ActRouteRendezVous:SetZone(RendezVousZone)
end
function TASK_A2A:GetRendezVousZone(TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone")
return ActRouteRendezVous:GetZone()
end
function TASK_A2A:SetTargetCoordinate(TargetCoordinate,TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint")
ActRouteTarget:SetCoordinate(TargetCoordinate)
end
function TASK_A2A:GetTargetCoordinate(TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint")
return ActRouteTarget:GetCoordinate()
end
function TASK_A2A:SetTargetZone(TargetZone,Altitude,Heading,TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone")
ActRouteTarget:SetZone(TargetZone,Altitude,Heading)
end
function TASK_A2A:GetTargetZone(TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone")
return ActRouteTarget:GetZone()
end
function TASK_A2A:SetGoalTotal()
self.GoalTotal=self.TargetSetUnit:Count()
end
function TASK_A2A:GetGoalTotal()
return self.GoalTotal
end
function TASK_A2A:ReportOrder(ReportGroup)
self:UpdateTaskInfo(self.DetectedItem)
local Coordinate=self.TaskInfo:GetData("Coordinate")
local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
return Distance
end
function TASK_A2A:onafterGoal(TaskUnit,From,Event,To)
local TargetSetUnit=self.TargetSetUnit
if TargetSetUnit:Count()==0 then
self:Success()
end
self:__Goal(-10)
end
function TASK_A2A:UpdateTaskInfo(DetectedItem)
if self:IsStatePlanned()or self:IsStateAssigned()then
local TargetCoordinate=DetectedItem and self.Detection:GetDetectedItemCoordinate(DetectedItem)or self.TargetSetUnit:GetFirst():GetCoordinate()
self.TaskInfo:AddTaskName(0,"MSOD")
self.TaskInfo:AddCoordinate(TargetCoordinate,1,"SOD")
local ThreatLevel,ThreatText
if DetectedItem then
ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(DetectedItem)
else
ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G()
end
self.TaskInfo:AddThreat(ThreatText,ThreatLevel,10,"MOD",true)
if self.Detection then
local DetectedItemsCount=self.TargetSetUnit:Count()
local ReportTypes=REPORT:New()
local TargetTypes={}
for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do
local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit)
if not TargetTypes[TargetType]then
TargetTypes[TargetType]=TargetType
ReportTypes:Add(TargetType)
end
end
self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true)
self.TaskInfo:AddTargets(DetectedItemsCount,ReportTypes:Text(", "),20,"D",true)
else
local DetectedItemsCount=self.TargetSetUnit:Count()
local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true)
self.TaskInfo:AddTargets(DetectedItemsCount,DetectedItemsTypes,20,"D",true)
end
end
end
function TASK_A2A:GetAutoAssignPriority(AutoAssignMethod,CommandCenter,TaskGroup)
if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then
return math.random(1,9)
elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then
local Coordinate=self.TaskInfo:GetData("Coordinate")
local Distance=Coordinate:Get2DDistance(CommandCenter:GetPositionable():GetCoordinate())
return math.floor(Distance)
elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then
return 1
end
return 0
end
end
do
TASK_A2A_INTERCEPT={
ClassName="TASK_A2A_INTERCEPT"
}
function TASK_A2A_INTERCEPT:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing)
local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"INTERCEPT",TaskBriefing))
self:F()
Mission:AddTask(self)
self:SetBriefing(TaskBriefing or"Intercept incoming intruders.\n")
return self
end
function TASK_A2A_INTERCEPT:SetScoreOnProgress(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has intercepted a target.",Score)
return self
end
function TASK_A2A_INTERCEPT:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Success","All targets have been successfully intercepted!",Score)
return self
end
function TASK_A2A_INTERCEPT:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
self:F({PlayerName,Penalty,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Failed","The intercept has failed!",Penalty)
return self
end
end
do
TASK_A2A_SWEEP={
ClassName="TASK_A2A_SWEEP"
}
function TASK_A2A_SWEEP:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing)
local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"SWEEP",TaskBriefing))
self:F()
Mission:AddTask(self)
self:SetBriefing(TaskBriefing or"Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n")
return self
end
function TASK_A2A_SWEEP:onafterGoal(TaskUnit,From,Event,To)
local TargetSetUnit=self.TargetSetUnit
if TargetSetUnit:Count()==0 then
self:Success()
end
self:__Goal(-10)
end
function TASK_A2A_SWEEP:SetScoreOnProgress(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has sweeped a target.",Score)
return self
end
function TASK_A2A_SWEEP:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Success","All targets have been successfully sweeped!",Score)
return self
end
function TASK_A2A_SWEEP:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
self:F({PlayerName,Penalty,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Failed","The sweep has failed!",Penalty)
return self
end
end
do
TASK_A2A_ENGAGE={
ClassName="TASK_A2A_ENGAGE"
}
function TASK_A2A_ENGAGE:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing)
local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"ENGAGE",TaskBriefing))
self:F()
Mission:AddTask(self)
self:SetBriefing(TaskBriefing or"Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n")
return self
end
function TASK_A2A_ENGAGE:SetScoreOnProgress(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has engaged and destroyed a target.",Score)
return self
end
function TASK_A2A_ENGAGE:SetScoreOnSuccess(PlayerName,Score,TaskUnit)
self:F({PlayerName,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Success","All targets have been successfully engaged!",Score)
return self
end
function TASK_A2A_ENGAGE:SetScoreOnFail(PlayerName,Penalty,TaskUnit)
self:F({PlayerName,Penalty,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Failed","The target engagement has failed!",Penalty)
return self
end
end
do
TASK_CARGO={
ClassName="TASK_CARGO",
}
function TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,TaskType,TaskBriefing)
local self=BASE:Inherit(self,TASK:New(Mission,SetGroup,TaskName,TaskType,TaskBriefing))
self:F({Mission,SetGroup,TaskName,SetCargo,TaskType})
self.SetCargo=SetCargo
self.TaskType=TaskType
self.SmokeColor=SMOKECOLOR.Red
self.CargoItemCount={}
self.CargoLimit=10
self.DeployZones={}
self:AddTransition("*","CargoDeployed","*")
self:AddTransition("*","CargoPickedUp","*")
local Fsm=self:GetUnitProcess()
Fsm:AddTransition({"Planned","Assigned","Cancelled","WaitingForCommand","ArrivedAtPickup","ArrivedAtDeploy","Boarded","UnBoarded","Loaded","UnLoaded","Landed","Boarding"},"SelectAction","*")
Fsm:AddTransition("*","RouteToPickup","RoutingToPickup")
Fsm:AddProcess("RoutingToPickup","RouteToPickupPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtPickup",Cancelled="CancelRouteToPickup"})
Fsm:AddTransition("Arrived","ArriveAtPickup","ArrivedAtPickup")
Fsm:AddTransition("Cancelled","CancelRouteToPickup","Cancelled")
Fsm:AddTransition("*","RouteToDeploy","RoutingToDeploy")
Fsm:AddProcess("RoutingToDeploy","RouteToDeployZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtDeploy",Cancelled="CancelRouteToDeploy"})
Fsm:AddTransition("Arrived","ArriveAtDeploy","ArrivedAtDeploy")
Fsm:AddTransition("Cancelled","CancelRouteToDeploy","Cancelled")
Fsm:AddTransition({"ArrivedAtPickup","ArrivedAtDeploy","Landing"},"Land","Landing")
Fsm:AddTransition("Landing","Landed","Landed")
Fsm:AddTransition("*","PrepareBoarding","AwaitBoarding")
Fsm:AddTransition("AwaitBoarding","Board","Boarding")
Fsm:AddTransition("Boarding","Boarded","Boarded")
Fsm:AddTransition("*","Load","Loaded")
Fsm:AddTransition("*","PrepareUnBoarding","AwaitUnBoarding")
Fsm:AddTransition("AwaitUnBoarding","UnBoard","UnBoarding")
Fsm:AddTransition("UnBoarding","UnBoarded","UnBoarded")
Fsm:AddTransition("*","Unload","Unloaded")
Fsm:AddTransition("*","Planned","Planned")
Fsm:AddTransition("Deployed","Success","Success")
Fsm:AddTransition("Rejected","Reject","Aborted")
Fsm:AddTransition("Failed","Fail","Failed")
function Fsm:OnAfterAssigned(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
self:SelectAction()
end
function Fsm:onafterSelectAction(TaskUnit,Task)
local TaskUnitName=TaskUnit:GetName()
local MenuTime=Task:InitTaskControlMenu(TaskUnit)
local MenuControl=Task:GetTaskControlMenu(TaskUnit)
Task.SetCargo:ForEachCargo(
function(Cargo)
if Cargo:IsAlive()then
local TaskGroup=TaskUnit:GetGroup()
if Cargo:IsUnLoaded()then
local CargoBayFreeWeight=TaskUnit:GetCargoBayFreeWeight()
local CargoWeight=Cargo:GetWeight()
self:F({CargoBayFreeWeight=CargoBayFreeWeight})
if CargoBayFreeWeight>CargoWeight then
if Cargo:IsInReportRadius(TaskUnit:GetPointVec2())then
local NotInDeployZones=true
for DeployZoneName,DeployZone in pairs(Task.DeployZones)do
if Cargo:IsInZone(DeployZone)then
NotInDeployZones=false
end
end
if NotInDeployZones then
if not TaskUnit:InAir()then
if Cargo:CanBoard()==true then
if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then
Cargo:Report("Ready for boarding.","board",TaskUnit:GetGroup())
local BoardMenu=MENU_GROUP:New(TaskGroup,"Board cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo")
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,BoardMenu,self.MenuBoardCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
else
Cargo:Report("Board at "..Cargo:GetCoordinate():ToString(TaskUnit:GetGroup().."."),"reporting",TaskUnit:GetGroup())
end
else
if Cargo:CanLoad()==true then
if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then
Cargo:Report("Ready for loading.","load",TaskUnit:GetGroup())
local LoadMenu=MENU_GROUP:New(TaskGroup,"Load cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo")
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,LoadMenu,self.MenuLoadCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
else
Cargo:Report("Load at "..Cargo:GetCoordinate():ToString(TaskUnit:GetGroup()).." within "..Cargo.NearRadius..".","reporting",TaskUnit:GetGroup())
end
else
if Cargo:CanSlingload()==true then
if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then
Cargo:Report("Ready for sling loading.","slingload",TaskUnit:GetGroup())
local SlingloadMenu=MENU_GROUP:New(TaskGroup,"Slingload cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo")
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,SlingloadMenu,self.MenuLoadCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
else
Cargo:Report("Slingload at "..Cargo:GetCoordinate():ToString(TaskUnit:GetGroup())..".","reporting",TaskUnit:GetGroup())
end
end
end
end
else
Cargo:ReportResetAll(TaskUnit:GetGroup())
end
end
else
if not Cargo:IsDeployed()==true then
local RouteToPickupMenu=MENU_GROUP:New(TaskGroup,"Route to pickup cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo")
Cargo:ReportResetAll(TaskUnit:GetGroup())
if Cargo:CanBoard()==true then
if not Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then
local BoardMenu=MENU_GROUP:New(TaskGroup,"Board cargo",RouteToPickupMenu):SetTime(MenuTime):SetTag("Cargo")
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,BoardMenu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
end
else
if Cargo:CanLoad()==true then
if not Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then
local LoadMenu=MENU_GROUP:New(TaskGroup,"Load cargo",RouteToPickupMenu):SetTime(MenuTime):SetTag("Cargo")
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,LoadMenu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
end
else
if Cargo:CanSlingload()==true then
if not Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then
local SlingloadMenu=MENU_GROUP:New(TaskGroup,"Slingload cargo",RouteToPickupMenu):SetTime(MenuTime):SetTag("Cargo")
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,SlingloadMenu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
end
end
end
end
end
end
end
for DeployZoneName,DeployZone in pairs(Task.DeployZones)do
if Cargo:IsInZone(DeployZone)then
Task:I({CargoIsDeployed=Task.CargoDeployed and"true"or"false"})
if Cargo:IsDeployed()==false then
Cargo:SetDeployed(true)
Task:I({CargoIsAlive=Cargo:IsAlive()and"true"or"false"})
if Cargo:IsAlive()then
Task:CargoDeployed(TaskUnit,Cargo,DeployZone)
end
end
end
end
end
if Cargo:IsLoaded()==true and Cargo:IsLoadedInCarrier(TaskUnit)==true then
if not TaskUnit:InAir()then
if Cargo:CanUnboard()==true then
local UnboardMenu=MENU_GROUP:New(TaskGroup,"Unboard cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo")
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,UnboardMenu,self.MenuUnboardCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
else
if Cargo:CanUnload()==true then
local UnloadMenu=MENU_GROUP:New(TaskGroup,"Unload cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo")
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,UnloadMenu,self.MenuUnloadCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
end
end
end
end
for DeployZoneName,DeployZone in pairs(Task.DeployZones)do
if not Cargo:IsInZone(DeployZone)then
local RouteToDeployMenu=MENU_GROUP:New(TaskGroup,"Route to deploy cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo")
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),"Zone "..DeployZoneName,RouteToDeployMenu,self.MenuRouteToDeploy,self,DeployZone):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
end
end
end
end
)
Task:RefreshTaskControlMenu(TaskUnit,MenuTime,"Cargo")
self:__SelectAction(-1)
end
function Fsm:OnLeaveWaitingForCommand(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
end
function Fsm:MenuBoardCargo(Cargo)
self:__PrepareBoarding(1.0,Cargo)
end
function Fsm:MenuLoadCargo(Cargo)
self:__Load(1.0,Cargo)
end
function Fsm:MenuUnboardCargo(Cargo,DeployZone)
self:__PrepareUnBoarding(1.0,Cargo,DeployZone)
end
function Fsm:MenuUnloadCargo(Cargo,DeployZone)
self:__Unload(1.0,Cargo,DeployZone)
end
function Fsm:MenuRouteToPickup(Cargo)
self:__RouteToPickup(1.0,Cargo)
end
function Fsm:MenuRouteToDeploy(DeployZone)
self:__RouteToDeploy(1.0,DeployZone)
end
function Fsm:onafterRouteToPickup(TaskUnit,Task,From,Event,To,Cargo)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
if Cargo:IsAlive()then
self.Cargo=Cargo
Task:SetCargoPickup(self.Cargo,TaskUnit)
self:__RouteToPickupPoint(-0.1)
end
end
function Fsm:onafterArriveAtPickup(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
if self.Cargo:IsAlive()then
if TaskUnit:IsAir()then
Task:GetMission():GetCommandCenter():MessageToGroup("Land",TaskUnit:GetGroup())
self:__Land(-0.1,"Pickup")
else
self:__SelectAction(-0.1)
end
end
end
function Fsm:onafterCancelRouteToPickup(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
Task:GetMission():GetCommandCenter():MessageToGroup("Cancelled routing to Cargo "..self.Cargo:GetName(),TaskUnit:GetGroup())
self:__SelectAction(-0.1)
end
function Fsm:onafterRouteToDeploy(TaskUnit,Task,From,Event,To,DeployZone)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
self:F(DeployZone)
self.DeployZone=DeployZone
Task:SetDeployZone(self.DeployZone,TaskUnit)
self:__RouteToDeployZone(-0.1)
end
function Fsm:onafterArriveAtDeploy(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
if TaskUnit:IsAir()then
Task:GetMission():GetCommandCenter():MessageToGroup("Land",TaskUnit:GetGroup())
self:__Land(-0.1,"Deploy")
else
self:__SelectAction(-0.1)
end
end
function Fsm:onafterCancelRouteToDeploy(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
Task:GetMission():GetCommandCenter():MessageToGroup("Cancelled routing to deploy zone "..self.DeployZone:GetName(),TaskUnit:GetGroup())
self:__SelectAction(-0.1)
end
function Fsm:onafterLand(TaskUnit,Task,From,Event,To,Action)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
if Action=="Pickup"then
if self.Cargo:IsAlive()then
if self.Cargo:IsInReportRadius(TaskUnit:GetPointVec2())then
if TaskUnit:InAir()then
self:__Land(-10,Action)
else
Task:GetMission():GetCommandCenter():MessageToGroup("Landed at pickup location...",TaskUnit:GetGroup())
self:__Landed(-0.1,Action)
end
else
self:__RouteToPickup(-0.1,self.Cargo)
end
end
else
if TaskUnit:IsAlive()then
if TaskUnit:IsInZone(self.DeployZone)then
if TaskUnit:InAir()then
self:__Land(-10,Action)
else
Task:GetMission():GetCommandCenter():MessageToGroup("Landed at deploy zone "..self.DeployZone:GetName(),TaskUnit:GetGroup())
self:__Landed(-0.1,Action)
end
else
self:__RouteToDeploy(-0.1,self.Cargo)
end
end
end
end
function Fsm:onafterLanded(TaskUnit,Task,From,Event,To,Action)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
if Action=="Pickup"then
if self.Cargo:IsAlive()then
if self.Cargo:IsInReportRadius(TaskUnit:GetPointVec2())then
if TaskUnit:InAir()then
self:__Land(-0.1,Action)
else
self:__SelectAction(-0.1)
end
else
self:__RouteToPickup(-0.1,self.Cargo)
end
end
else
if TaskUnit:IsAlive()then
if TaskUnit:IsInZone(self.DeployZone)then
if TaskUnit:InAir()then
self:__Land(-10,Action)
else
self:__SelectAction(-0.1)
end
else
self:__RouteToDeploy(-0.1,self.Cargo)
end
end
end
end
function Fsm:onafterPrepareBoarding(TaskUnit,Task,From,Event,To,Cargo)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
if Cargo and Cargo:IsAlive()then
self:__Board(-0.1,Cargo)
end
end
function Fsm:onafterBoard(TaskUnit,Task,From,Event,To,Cargo)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
function Cargo:OnEnterLoaded(From,Event,To,TaskUnit,TaskProcess)
self:F({From,Event,To,TaskUnit,TaskProcess})
TaskProcess:__Boarded(0.1,self)
end
if Cargo:IsAlive()then
if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then
if TaskUnit:InAir()then
else
Cargo:MessageToGroup("Boarding ...",TaskUnit:GetGroup())
if not Cargo:IsBoarding()then
Cargo:Board(TaskUnit,nil,self)
end
end
else
end
end
end
function Fsm:onafterBoarded(TaskUnit,Task,From,Event,To,Cargo)
local TaskUnitName=TaskUnit:GetName()
self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()})
Cargo:MessageToGroup("Boarded cargo "..Cargo:GetName(),TaskUnit:GetGroup())
self:__Load(-0.1,Cargo)
end
function Fsm:onafterLoad(TaskUnit,Task,From,Event,To,Cargo)
local TaskUnitName=TaskUnit:GetName()
self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()})
if not Cargo:IsLoaded()then
Cargo:Load(TaskUnit)
end
Cargo:MessageToGroup("Loaded cargo "..Cargo:GetName(),TaskUnit:GetGroup())
TaskUnit:AddCargo(Cargo)
Task:CargoPickedUp(TaskUnit,Cargo)
self:SelectAction(-1)
end
function Fsm:onafterPrepareUnBoarding(TaskUnit,Task,From,Event,To,Cargo)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID(),From,Event,To,Cargo})
self.Cargo=Cargo
self.DeployZone=nil
if Cargo:IsAlive()then
for DeployZoneName,DeployZone in pairs(Task.DeployZones)do
if Cargo:IsInZone(DeployZone)then
self.DeployZone=DeployZone
break
end
end
self:__UnBoard(-0.1,Cargo,self.DeployZone)
end
end
function Fsm:onafterUnBoard(TaskUnit,Task,From,Event,To,Cargo,DeployZone)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID(),From,Event,To,Cargo,DeployZone})
function self.Cargo:OnEnterUnLoaded(From,Event,To,DeployZone,TaskProcess)
self:F({From,Event,To,DeployZone,TaskProcess})
TaskProcess:__UnBoarded(-0.1)
end
if self.Cargo:IsAlive()then
self.Cargo:MessageToGroup("UnBoarding ...",TaskUnit:GetGroup())
if DeployZone then
self.Cargo:UnBoard(DeployZone:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self)
else
self.Cargo:UnBoard(TaskUnit:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self)
end
end
end
function Fsm:onafterUnBoarded(TaskUnit,Task)
local TaskUnitName=TaskUnit:GetName()
self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()})
self.Cargo:MessageToGroup("UnBoarded cargo "..self.Cargo:GetName(),TaskUnit:GetGroup())
self:Unload(self.Cargo)
end
function Fsm:onafterUnload(TaskUnit,Task,From,Event,To,Cargo,DeployZone)
local TaskUnitName=TaskUnit:GetName()
self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()})
if not Cargo:IsUnLoaded()then
if DeployZone then
Cargo:UnLoad(DeployZone:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self)
else
Cargo:UnLoad(TaskUnit:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self)
end
end
TaskUnit:RemoveCargo(Cargo)
Cargo:MessageToGroup("Unloaded cargo "..Cargo:GetName(),TaskUnit:GetGroup())
self:Planned()
self:__SelectAction(1)
end
return self
end
function TASK_CARGO:SetCargoLimit(CargoLimit)
self.CargoLimit=CargoLimit
return self
end
function TASK_CARGO:SetSmokeColor(SmokeColor)
if SmokeColor==nil then
self.SmokeColor=SMOKECOLOR.Red
elseif type(SmokeColor)=="number"then
self:F2(SmokeColor)
if SmokeColor>0 and SmokeColor<=5 then
self.SmokeColor=SMOKECOLOR.SmokeColor
end
end
end
function TASK_CARGO:GetSmokeColor()
return self.SmokeColor
end
function TASK_CARGO:GetPlannedMenuText()
return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )"
end
function TASK_CARGO:GetCargoSet()
return self.SetCargo
end
function TASK_CARGO:GetDeployZones()
return self.DeployZones
end
function TASK_CARGO:SetCargoPickup(Cargo,TaskUnit)
self:F({Cargo,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local MenuTime=self:InitTaskControlMenu(TaskUnit)
local MenuControl=self:GetTaskControlMenu(TaskUnit)
local ActRouteCargo=ProcessUnit:GetProcess("RoutingToPickup","RouteToPickupPoint")
ActRouteCargo:Reset()
ActRouteCargo:SetCoordinate(Cargo:GetCoordinate())
ActRouteCargo:SetRange(Cargo:GetLoadRadius())
ActRouteCargo:SetMenuCancel(TaskUnit:GetGroup(),"Cancel Routing to Cargo "..Cargo:GetName(),MenuControl,MenuTime,"Cargo")
ActRouteCargo:Start()
return self
end
function TASK_CARGO:SetDeployZone(DeployZone,TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local MenuTime=self:InitTaskControlMenu(TaskUnit)
local MenuControl=self:GetTaskControlMenu(TaskUnit)
local ActRouteDeployZone=ProcessUnit:GetProcess("RoutingToDeploy","RouteToDeployZone")
ActRouteDeployZone:Reset()
ActRouteDeployZone:SetZone(DeployZone)
ActRouteDeployZone:SetMenuCancel(TaskUnit:GetGroup(),"Cancel Routing to Deploy Zone"..DeployZone:GetName(),MenuControl,MenuTime,"Cargo")
ActRouteDeployZone:Start()
return self
end
function TASK_CARGO:AddDeployZone(DeployZone,TaskUnit)
self.DeployZones[DeployZone:GetName()]=DeployZone
return self
end
function TASK_CARGO:RemoveDeployZone(DeployZone,TaskUnit)
self.DeployZones[DeployZone:GetName()]=nil
return self
end
function TASK_CARGO:SetDeployZones(DeployZones,TaskUnit)
for DeployZoneID,DeployZone in pairs(DeployZones or{})do
self.DeployZones[DeployZone:GetName()]=DeployZone
end
return self
end
function TASK_CARGO:GetTargetZone(TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone")
return ActRouteTarget:GetZone()
end
function TASK_CARGO:SetScoreOnProgress(Text,Score,TaskUnit)
self:F({Text,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScoreProcess("Engaging","Account","Account",Text,Score)
return self
end
function TASK_CARGO:SetScoreOnSuccess(Text,Score,TaskUnit)
self:F({Text,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Success",Text,Score)
return self
end
function TASK_CARGO:SetScoreOnFail(Text,Penalty,TaskUnit)
self:F({Text,Score,TaskUnit})
local ProcessUnit=self:GetUnitProcess(TaskUnit)
ProcessUnit:AddScore("Failed",Text,Penalty)
return self
end
function TASK_CARGO:SetGoalTotal()
self.GoalTotal=self.SetCargo:Count()
end
function TASK_CARGO:GetGoalTotal()
return self.GoalTotal
end
function TASK_CARGO:UpdateTaskInfo()
if self:IsStatePlanned()or self:IsStateAssigned()then
self.TaskInfo:AddTaskName(0,"MSOD")
self.TaskInfo:AddCargoSet(self.SetCargo,10,"SOD",true)
local Coordinates={}
for CargoName,Cargo in pairs(self.SetCargo:GetSet())do
local Cargo=Cargo
if not Cargo:IsLoaded()then
Coordinates[#Coordinates+1]=Cargo:GetCoordinate()
end
end
self.TaskInfo:AddCoordinates(Coordinates,1,"M")
end
end
function TASK_CARGO:ReportOrder(ReportGroup)
return 0
end
function TASK_CARGO:GetAutoAssignPriority(AutoAssignMethod,TaskGroup)
if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then
return math.random(1,9)
elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then
return 0
elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then
return 1
end
return 0
end
end
do
TASK_CARGO_TRANSPORT={
ClassName="TASK_CARGO_TRANSPORT",
}
function TASK_CARGO_TRANSPORT:New(Mission,SetGroup,TaskName,SetCargo,TaskBriefing)
local self=BASE:Inherit(self,TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,"Transport",TaskBriefing))
self:F()
Mission:AddTask(self)
local Fsm=self:GetUnitProcess()
local CargoReport=REPORT:New("Transport Cargo. The following cargo needs to be transported including initial positions:")
SetCargo:ForEachCargo(
function(Cargo)
local CargoType=Cargo:GetType()
local CargoName=Cargo:GetName()
local CargoCoordinate=Cargo:GetCoordinate()
CargoReport:Add(string.format('- "%s" (%s) at %s',CargoName,CargoType,CargoCoordinate:ToStringMGRS()))
end
)
self:SetBriefing(
TaskBriefing or
CargoReport:Text()
)
return self
end
function TASK_CARGO_TRANSPORT:ReportOrder(ReportGroup)
return 0
end
function TASK_CARGO_TRANSPORT:IsAllCargoTransported()
local CargoSet=self:GetCargoSet()
local Set=CargoSet:GetSet()
local DeployZones=self:GetDeployZones()
local CargoDeployed=true
for CargoID,CargoData in pairs(Set)do
local Cargo=CargoData
self:F({Cargo=Cargo:GetName(),CargoDeployed=Cargo:IsDeployed()})
if Cargo:IsDeployed()then
else
CargoDeployed=false
end
end
self:F({CargoDeployed=CargoDeployed})
return CargoDeployed
end
function TASK_CARGO_TRANSPORT:onafterGoal(TaskUnit,From,Event,To)
local CargoSet=self.CargoSet
if self:IsAllCargoTransported()then
self:Success()
end
self:__Goal(-10)
end
end
do
TASK_CARGO_CSAR={
ClassName="TASK_CARGO_CSAR",
}
function TASK_CARGO_CSAR:New(Mission,SetGroup,TaskName,SetCargo,TaskBriefing)
local self=BASE:Inherit(self,TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,"CSAR",TaskBriefing))
self:F()
Mission:AddTask(self)
self:AddTransition("*","CargoPickedUp","*")
self:AddTransition("*","CargoDeployed","*")
self:F({CargoDeployed=self.CargoDeployed~=nil and"true"or"false"})
local Fsm=self:GetUnitProcess()
local CargoReport=REPORT:New("Rescue a downed pilot from the following position:")
SetCargo:ForEachCargo(
function(Cargo)
local CargoType=Cargo:GetType()
local CargoName=Cargo:GetName()
local CargoCoordinate=Cargo:GetCoordinate()
CargoReport:Add(string.format('- "%s" (%s) at %s',CargoName,CargoType,CargoCoordinate:ToStringMGRS()))
end
)
self:SetBriefing(
TaskBriefing or
CargoReport:Text()
)
return self
end
function TASK_CARGO_CSAR:ReportOrder(ReportGroup)
return 0
end
function TASK_CARGO_CSAR:IsAllCargoTransported()
local CargoSet=self:GetCargoSet()
local Set=CargoSet:GetSet()
local DeployZones=self:GetDeployZones()
local CargoDeployed=true
for CargoID,CargoData in pairs(Set)do
local Cargo=CargoData
self:F({Cargo=Cargo:GetName(),CargoDeployed=Cargo:IsDeployed()})
if Cargo:IsDeployed()then
else
CargoDeployed=false
end
end
self:F({CargoDeployed=CargoDeployed})
return CargoDeployed
end
function TASK_CARGO_CSAR:onafterGoal(TaskUnit,From,Event,To)
local CargoSet=self.CargoSet
if self:IsAllCargoTransported()then
self:Success()
end
self:__Goal(-10)
end
end
do
TASK_CARGO_DISPATCHER={
ClassName="TASK_CARGO_DISPATCHER",
Mission=nil,
Tasks={},
CSAR={},
CSARSpawned=0,
Transport={},
TransportCount=0,
}
function TASK_CARGO_DISPATCHER:New(Mission,SetGroup)
local self=BASE:Inherit(self,TASK_MANAGER:New(SetGroup))
self.Mission=Mission
self:AddTransition("Started","Assign","Started")
self:AddTransition("Started","CargoPickedUp","Started")
self:AddTransition("Started","CargoDeployed","Started")
self:SetCSARRadius()
self:__StartTasks(5)
self.MaxCSAR=nil
self.CountCSAR=0
self:HandleEvent(EVENTS.Ejection)
return self
end
function TASK_CARGO_DISPATCHER:SetCSARZones(SetZonesCSAR)
self.SetZonesCSAR=SetZonesCSAR
end
function TASK_CARGO_DISPATCHER:SetMaxCSAR(MaxCSAR)
self.MaxCSAR=MaxCSAR
end
function TASK_CARGO_DISPATCHER:OnEventEjection(EventData)
self:F({EventData=EventData})
if self.CSARTasks==true then
local CSARCoordinate=EventData.IniUnit:GetCoordinate()
local CSARCoalition=EventData.IniUnit:GetCoalition()
local CSARCountry=EventData.IniUnit:GetCountry()
local CSARHeading=EventData.IniUnit:GetHeading()
if CSARCoalition==self.Mission:GetCommandCenter():GetCoalition()then
if not self.SetZonesCSAR or(self.SetZonesCSAR and self.SetZonesCSAR:IsCoordinateInZone(CSARCoordinate))then
if not self.MaxCSAR or(self.MaxCSAR and self.CountCSAR<self.MaxCSAR)then
local CSARTaskName=self:AddCSARTask(self.CSARTaskName,CSARCoordinate,CSARHeading,CSARCountry,self.CSARBriefing)
self:SetCSARDeployZones(CSARTaskName,self.CSARDeployZones)
self.CountCSAR=self.CountCSAR+1
end
end
end
end
return self
end
function TASK_CARGO_DISPATCHER:SetDefaultDeployZone(DefaultDeployZone)
self.DefaultDeployZones={DefaultDeployZone}
return self
end
function TASK_CARGO_DISPATCHER:SetDefaultDeployZones(DefaultDeployZones)
self.DefaultDeployZones=DefaultDeployZones
return self
end
function TASK_CARGO_DISPATCHER:StartCSARTasks(CSARTaskName,CSARDeployZones,CSARBriefing)
if not self.CSARTasks then
self.CSARTasks=true
self.CSARTaskName=CSARTaskName
self.CSARDeployZones=CSARDeployZones
self.CSARBriefing=CSARBriefing
else
error("TASK_CARGO_DISPATCHER: The generation of CSAR tasks has already started.")
end
return self
end
function TASK_CARGO_DISPATCHER:StopCSARTasks()
if self.CSARTasks then
self.CSARTasks=nil
self.CSARTaskName=nil
self.CSARDeployZones=nil
self.CSARBriefing=nil
else
error("TASK_CARGO_DISPATCHER: The generation of CSAR tasks was not yet started.")
end
return self
end
function TASK_CARGO_DISPATCHER:AddCSARTask(CSARTaskPrefix,CSARCoordinate,CSARHeading,CSARCountry,CSARBriefing)
local CSARCoalition=self.Mission:GetCommandCenter():GetCoalition()
CSARHeading=CSARHeading or 0
CSARCountry=CSARCountry or self.Mission:GetCommandCenter():GetCountry()
self.CSARSpawned=self.CSARSpawned+1
local CSARTaskName=string.format((CSARTaskPrefix or"CSAR")..".%03d",self.CSARSpawned)
local Template={
["visible"]=false,
["hidden"]=false,
["task"]="Ground Nothing",
["name"]=string.format("CSAR Pilot#%03d",self.CSARSpawned),
["x"]=CSARCoordinate.x,
["y"]=CSARCoordinate.z,
["units"]=
{
[1]=
{
["type"]=(CSARCoalition==coalition.side.BLUE)and"Soldier M4"or"Infantry AK",
["name"]=string.format("CSAR Pilot#%03d-01",self.CSARSpawned),
["skill"]="Excellent",
["playerCanDrive"]=false,
["x"]=CSARCoordinate.x,
["y"]=CSARCoordinate.z,
["heading"]=CSARHeading,
},
},
}
local CSARGroup=GROUP:NewTemplate(Template,CSARCoalition,Group.Category.GROUND,CSARCountry)
self.CSAR[CSARTaskName]={}
self.CSAR[CSARTaskName].PilotGroup=CSARGroup
self.CSAR[CSARTaskName].Briefing=CSARBriefing
self.CSAR[CSARTaskName].Task=nil
self.CSAR[CSARTaskName].TaskPrefix=CSARTaskPrefix
return CSARTaskName
end
function TASK_CARGO_DISPATCHER:SetCSARRadius(CSARRadius)
self.CSARRadius=CSARRadius or 50000
return self
end
function TASK_CARGO_DISPATCHER:SetCSARDeployZone(CSARTaskName,CSARDeployZone)
if CSARTaskName then
self.CSAR[CSARTaskName].DeployZones={CSARDeployZone}
end
return self
end
function TASK_CARGO_DISPATCHER:SetCSARDeployZones(CSARTaskName,CSARDeployZones)
if CSARTaskName and self.CSAR[CSARTaskName]then
self.CSAR[CSARTaskName].DeployZones=CSARDeployZones
end
return self
end
function TASK_CARGO_DISPATCHER:AddTransportTask(TaskPrefix,SetCargo,Briefing,Silent)
self.TransportCount=self.TransportCount+1
local verbose=Silent or false
local TaskName=string.format((TaskPrefix or"Transport")..".%03d",self.TransportCount)
self.Transport[TaskName]={}
self.Transport[TaskName].SetCargo=SetCargo
self.Transport[TaskName].Briefing=Briefing
self.Transport[TaskName].Task=nil
self.Transport[TaskName].TaskPrefix=TaskPrefix
self:ManageTasks(verbose)
return self.Transport[TaskName]and self.Transport[TaskName].Task
end
function TASK_CARGO_DISPATCHER:SetTransportDeployZone(Task,TransportDeployZone)
if self.Transport[Task.TaskName]then
self.Transport[Task.TaskName].DeployZones={TransportDeployZone}
else
error("Task does not exist")
end
self:ManageTasks()
return self
end
function TASK_CARGO_DISPATCHER:SetTransportDeployZones(Task,TransportDeployZones)
if self.Transport[Task.TaskName]then
self.Transport[Task.TaskName].DeployZones=TransportDeployZones
else
error("Task does not exist")
end
self:ManageTasks()
return self
end
function TASK_CARGO_DISPATCHER:EvaluateCSAR(CSARUnit)
local CSARCargo=CARGO_GROUP:New(CSARUnit,"Pilot",CSARUnit:GetName(),80,1500,10)
local SetCargo=SET_CARGO:New()
SetCargo:AddCargosByName(CSARUnit:GetName())
SetCargo:Flush(self)
return SetCargo
end
function TASK_CARGO_DISPATCHER:ManageTasks(Silent)
self:F()
local verbose=Silent and true
local AreaMsg={}
local TaskMsg={}
local ChangeMsg={}
local Mission=self.Mission
if Mission:IsIDLE()or Mission:IsENGAGED()then
local TaskReport=REPORT:New()
for TaskIndex,TaskData in pairs(self.Tasks)do
local Task=TaskData
if Task:IsStatePlanned()then
end
end
for CSARName,CSAR in pairs(self.CSAR)do
if not CSAR.Task then
local SetCargo=self:EvaluateCSAR(CSAR.PilotGroup)
CSAR.Task=TASK_CARGO_CSAR:New(Mission,self.SetGroup,CSARName,SetCargo,CSAR.Briefing)
CSAR.Task.TaskPrefix=CSAR.TaskPrefix
Mission:AddTask(CSAR.Task)
TaskReport:Add(CSARName)
if CSAR.DeployZones then
CSAR.Task:SetDeployZones(CSAR.DeployZones or{})
else
CSAR.Task:SetDeployZones(self.DefaultDeployZones or{})
end
function CSAR.Task.OnAfterCargoPickedUp(Task,From,Event,To,TaskUnit,Cargo)
self:CargoPickedUp(Task,Task.TaskPrefix,TaskUnit,Cargo)
end
function CSAR.Task.OnAfterCargoDeployed(Task,From,Event,To,TaskUnit,Cargo,DeployZone)
self:CargoDeployed(Task,Task.TaskPrefix,TaskUnit,Cargo,DeployZone)
end
end
end
for TransportName,Transport in pairs(self.Transport)do
if not Transport.Task then
Transport.Task=TASK_CARGO_TRANSPORT:New(Mission,self.SetGroup,TransportName,Transport.SetCargo,Transport.Briefing)
Transport.Task.TaskPrefix=Transport.TaskPrefix
Mission:AddTask(Transport.Task)
TaskReport:Add(TransportName)
function Transport.Task.OnEnterSuccess(Task,From,Event,To)
self:Success(Task)
end
function Transport.Task.OnEnterCancelled(Task,From,Event,To)
self:Cancelled(Task)
end
function Transport.Task.OnEnterFailed(Task,From,Event,To)
self:Failed(Task)
end
function Transport.Task.OnEnterAborted(Task,From,Event,To)
self:Aborted(Task)
end
function Transport.Task.OnAfterCargoPickedUp(Task,From,Event,To,TaskUnit,Cargo)
self:CargoPickedUp(Task,Task.TaskPrefix,TaskUnit,Cargo)
end
function Transport.Task.OnAfterCargoDeployed(Task,From,Event,To,TaskUnit,Cargo,DeployZone)
self:CargoDeployed(Task,Task.TaskPrefix,TaskUnit,Cargo,DeployZone)
end
end
if Transport.DeployZones then
Transport.Task:SetDeployZones(Transport.DeployZones or{})
else
Transport.Task:SetDeployZones(self.DefaultDeployZones or{})
end
end
Mission:GetCommandCenter():SetMenu()
local TaskText=TaskReport:Text(", ")
for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
if(not Mission:IsGroupAssigned(TaskGroup))and TaskText~=""and not verbose then
Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetShortText(),TaskText),TaskGroup)
end
end
end
return true
end
end
do
TASK_ZONE_GOAL={
ClassName="TASK_ZONE_GOAL",
}
function TASK_ZONE_GOAL:New(Mission,SetGroup,TaskName,ZoneGoal,TaskType,TaskBriefing)
local self=BASE:Inherit(self,TASK:New(Mission,SetGroup,TaskName,TaskType,TaskBriefing))
self:F()
self.ZoneGoal=ZoneGoal
self.TaskType=TaskType
local Fsm=self:GetUnitProcess()
Fsm:AddTransition("Assigned","StartMonitoring","Monitoring")
Fsm:AddTransition("Monitoring","Monitor","Monitoring",{})
Fsm:AddProcess("Monitoring","RouteToZone",ACT_ROUTE_ZONE:New(),{})
Fsm:AddTransition("Rejected","Reject","Aborted")
Fsm:AddTransition("Failed","Fail","Failed")
self:SetTargetZone(self.ZoneGoal:GetZone())
function Fsm:OnAfterAssigned(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
self:__StartMonitoring(0.1)
self:__RouteToZone(0.1)
end
function Fsm:onafterStartMonitoring(TaskUnit,Task)
self:F({self})
self:__Monitor(0.1)
end
function Fsm:onafterMonitor(TaskUnit,Task)
self:F({self})
self:__Monitor(15)
end
function Fsm:onafterRouteTo(TaskUnit,Task)
self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
if Task:GetTargetZone(TaskUnit)then
self:__RouteToZone(0.1)
end
end
return self
end
function TASK_ZONE_GOAL:SetProtect(ZoneGoal)
self.ZoneGoal=ZoneGoal
end
function TASK_ZONE_GOAL:GetPlannedMenuText()
return self:GetStateString().." - "..self:GetTaskName().." ( "..self.ZoneGoal:GetZoneName().." )"
end
function TASK_ZONE_GOAL:SetTargetZone(TargetZone,TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteZone=ProcessUnit:GetProcess("Monitoring","RouteToZone")
ActRouteZone:SetZone(TargetZone)
end
function TASK_ZONE_GOAL:GetTargetZone(TaskUnit)
local ProcessUnit=self:GetUnitProcess(TaskUnit)
local ActRouteZone=ProcessUnit:GetProcess("Monitoring","RouteToZone")
return ActRouteZone:GetZone()
end
function TASK_ZONE_GOAL:SetGoalTotal(GoalTotal)
self.GoalTotal=GoalTotal
end
function TASK_ZONE_GOAL:GetGoalTotal()
return self.GoalTotal
end
end
do
TASK_CAPTURE_ZONE={
ClassName="TASK_CAPTURE_ZONE",
}
function TASK_CAPTURE_ZONE:New(Mission,SetGroup,TaskName,ZoneGoalCoalition,TaskBriefing)
local self=BASE:Inherit(self,TASK_ZONE_GOAL:New(Mission,SetGroup,TaskName,ZoneGoalCoalition,"CAPTURE",TaskBriefing))
self:F()
Mission:AddTask(self)
self.TaskCoalition=ZoneGoalCoalition:GetCoalition()
self.TaskCoalitionName=ZoneGoalCoalition:GetCoalitionName()
self.TaskZoneName=ZoneGoalCoalition:GetZoneName()
ZoneGoalCoalition:MonitorDestroyedUnits()
self:SetBriefing(
TaskBriefing or
"Capture Zone "..self.TaskZoneName
)
self:UpdateTaskInfo(true)
self:SetGoal(self.ZoneGoal.Goal)
return self
end
function TASK_CAPTURE_ZONE:UpdateTaskInfo(Persist)
Persist=Persist or false
local ZoneCoordinate=self.ZoneGoal:GetZone():GetCoordinate()
self.TaskInfo:AddTaskName(0,"MSOD",Persist)
self.TaskInfo:AddCoordinate(ZoneCoordinate,1,"SOD",Persist)
local SetUnit=self.ZoneGoal:GetScannedSetUnit()
local ThreatLevel,ThreatText=SetUnit:CalculateThreatLevelA2G()
local ThreatCount=SetUnit:Count()
self.TaskInfo:AddThreat(ThreatText,ThreatLevel,20,"MOD",Persist)
self.TaskInfo:AddInfo("Remaining Units",ThreatCount,21,"MOD",Persist,true)
if self.Dispatcher then
local DefenseTaskCaptureDispatcher=self.Dispatcher:GetDefenseTaskCaptureDispatcher()
if DefenseTaskCaptureDispatcher then
for TaskName,CaptureZone in pairs(DefenseTaskCaptureDispatcher.Zones or{})do
local Task=CaptureZone.Task
if Task and Task:IsStateAssigned()then
self.TaskInfo:AddInfo("Defense Player Zone",Task.ZoneGoal:GetName(),30,"MOD",Persist)
self.TaskInfo:AddCoordinate(Task.ZoneGoal:GetZone():GetCoordinate(),31,"MOD",Persist,false,"Defense Player Coordinate")
end
end
end
local DefenseAIA2GDispatcher=self.Dispatcher:GetDefenseAIA2GDispatcher()
if DefenseAIA2GDispatcher then
for Defender,Task in pairs(DefenseAIA2GDispatcher:GetDefenderTasks()or{})do
local DetectedItem=DefenseAIA2GDispatcher:GetDefenderTaskTarget(Defender)
if DetectedItem then
local DetectedZone=DefenseAIA2GDispatcher.Detection:GetDetectedItemZone(DetectedItem)
if DetectedZone then
self.TaskInfo:AddInfo("Defense AI Zone",DetectedZone:GetName(),40,"MOD",Persist)
self.TaskInfo:AddCoordinate(DetectedZone:GetCoordinate(),41,"MOD",Persist,false,"Defense AI Coordinate")
end
end
end
end
end
end
function TASK_CAPTURE_ZONE:ReportOrder(ReportGroup)
local Coordinate=self.TaskInfo:GetCoordinate()
local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
return Distance
end
function TASK_CAPTURE_ZONE:OnAfterGoal(From,Event,To,PlayerUnit,PlayerName)
self:F({PlayerUnit=PlayerUnit,Achieved=self.ZoneGoal.Goal:IsAchieved()})
if self.ZoneGoal then
if self.ZoneGoal.Goal:IsAchieved()then
local TotalContributions=self.ZoneGoal.Goal:GetTotalContributions()
local PlayerContributions=self.ZoneGoal.Goal:GetPlayerContributions()
self:F({TotalContributions=TotalContributions,PlayerContributions=PlayerContributions})
for PlayerName,PlayerContribution in pairs(PlayerContributions)do
local Scoring=self:GetScoring()
if Scoring then
Scoring:_AddMissionGoalScore(self.Mission,PlayerName,"Zone "..self.ZoneGoal:GetZoneName().." captured",PlayerContribution*200/TotalContributions)
end
end
self:Success()
end
end
self:__Goal(-10,PlayerUnit,PlayerName)
end
function TASK_CAPTURE_ZONE:GetAutoAssignPriority(AutoAssignMethod,CommandCenter,TaskGroup,AutoAssignReference)
if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then
return math.random(1,9)
elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then
local Coordinate=self.TaskInfo:GetCoordinate()
local Distance=Coordinate:Get2DDistance(CommandCenter:GetPositionable():GetCoordinate())
return math.floor(Distance)
elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then
return 1
end
return 0
end
end
do
TASK_CAPTURE_DISPATCHER={
ClassName="TASK_CAPTURE_DISPATCHER",
Mission=nil,
Tasks={},
Zones={},
ZoneCount=0,
}
TASK_CAPTURE_DISPATCHER.AI_A2G_Dispatcher=nil
function TASK_CAPTURE_DISPATCHER:New(Mission,SetGroup)
local self=BASE:Inherit(self,TASK_MANAGER:New(SetGroup))
self.Mission=Mission
self.FlashNewTask=false
self:AddTransition("Started","Assign","Started")
self:AddTransition("Started","ZoneCaptured","Started")
self:__StartTasks(5)
return self
end
function TASK_CAPTURE_DISPATCHER:SetDefenseTaskCaptureDispatcher(DefenseTaskCaptureDispatcher)
self.DefenseTaskCaptureDispatcher=DefenseTaskCaptureDispatcher
end
function TASK_CAPTURE_DISPATCHER:GetDefenseTaskCaptureDispatcher()
return self.DefenseTaskCaptureDispatcher
end
function TASK_CAPTURE_DISPATCHER:SetDefenseAIA2GDispatcher(DefenseAIA2GDispatcher)
self.DefenseAIA2GDispatcher=DefenseAIA2GDispatcher
end
function TASK_CAPTURE_DISPATCHER:GetDefenseAIA2GDispatcher()
return self.DefenseAIA2GDispatcher
end
function TASK_CAPTURE_DISPATCHER:AddCaptureZoneTask(TaskPrefix,CaptureZone,Briefing)
local TaskName=TaskPrefix or"Capture"
if self.Zones[TaskName]then
self.ZoneCount=self.ZoneCount+1
TaskName=string.format("%s.%03d",TaskName,self.ZoneCount)
end
self.Zones[TaskName]={}
self.Zones[TaskName].CaptureZone=CaptureZone
self.Zones[TaskName].Briefing=Briefing
self.Zones[TaskName].Task=nil
self.Zones[TaskName].TaskPrefix=TaskPrefix
self:ManageTasks()
return self.Zones[TaskName]and self.Zones[TaskName].Task
end
function TASK_CAPTURE_DISPATCHER:Link_AI_A2G_Dispatcher(AI_A2G_Dispatcher)
self.AI_A2G_Dispatcher=AI_A2G_Dispatcher
AI_A2G_Dispatcher.Detection:LockDetectedItems()
return self
end
function TASK_CAPTURE_DISPATCHER:ManageTasks()
self:F()
local AreaMsg={}
local TaskMsg={}
local ChangeMsg={}
local Mission=self.Mission
if Mission:IsIDLE()or Mission:IsENGAGED()then
local TaskReport=REPORT:New()
for TaskIndex,TaskData in pairs(self.Tasks)do
local Task=TaskData
if Task:IsStatePlanned()then
end
end
for TaskName,CaptureZone in pairs(self.Zones)do
if not CaptureZone.Task then
CaptureZone.Task=TASK_CAPTURE_ZONE:New(Mission,self.SetGroup,TaskName,CaptureZone.CaptureZone,CaptureZone.Briefing)
CaptureZone.Task.TaskPrefix=CaptureZone.TaskPrefix
Mission:AddTask(CaptureZone.Task)
TaskReport:Add(TaskName)
CaptureZone.Task:SetDispatcher(self)
CaptureZone.Task:UpdateTaskInfo()
function CaptureZone.Task.OnEnterAssigned(Task,From,Event,To)
if self.AI_A2G_Dispatcher then
self.AI_A2G_Dispatcher:Unlock(Task.TaskZoneName)
end
CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked=true
end
function CaptureZone.Task.OnEnterSuccess(Task,From,Event,To)
if self.AI_A2G_Dispatcher then
self.AI_A2G_Dispatcher:Lock(Task.TaskZoneName)
end
CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked=false
end
function CaptureZone.Task.OnEnterCancelled(Task,From,Event,To)
self:Cancelled(Task)
if self.AI_A2G_Dispatcher then
self.AI_A2G_Dispatcher:Lock(Task.TaskZoneName)
end
CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked=false
end
function CaptureZone.Task.OnEnterFailed(Task,From,Event,To)
self:Failed(Task)
if self.AI_A2G_Dispatcher then
self.AI_A2G_Dispatcher:Lock(Task.TaskZoneName)
end
CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked=false
end
function CaptureZone.Task.OnEnterAborted(Task,From,Event,To)
self:Aborted(Task)
if self.AI_A2G_Dispatcher then
self.AI_A2G_Dispatcher:Lock(Task.TaskZoneName)
end
CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked=false
end
function CaptureZone.Task.OnAfterCaptured(Task,From,Event,To,TaskUnit)
self:Captured(Task,Task.TaskPrefix,TaskUnit)
if self.AI_A2G_Dispatcher then
self.AI_A2G_Dispatcher:Lock(Task.TaskZoneName)
end
CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked=false
end
end
end
Mission:GetCommandCenter():SetMenu()
local TaskText=TaskReport:Text(", ")
for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
if(not Mission:IsGroupAssigned(TaskGroup))and TaskText~=""and(not self.FlashNewTask)then
Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetShortText(),TaskText),TaskGroup)
end
end
end
return true
end
end
_EVENTDISPATCHER=EVENT:New()
_SCHEDULEDISPATCHER=SCHEDULEDISPATCHER:New()
_DATABASE=DATABASE:New()
_SETTINGS=SETTINGS:Set()
_SETTINGS:SetPlayerMenuOn()
_DATABASE:_RegisterCargos()
_DATABASE:_RegisterZones()
_DATABASE:_RegisterAirbases()
BASE:I("Checking de-sanitization of os, io and lfs:")
local __na=false
if os then
BASE:I("- os available")
else
BASE:I("- os NOT available! Some functions may not work.")
__na=true
end
if io then
BASE:I("- io available")
else
BASE:I("- io NOT available! Some functions may not work.")
__na=true
end
if lfs then
BASE:I("- lfs available")
else
BASE:I("- lfs NOT available! Some functions may not work.")
__na=true
end
if __na then
BASE:I("Check <DCS install folder>/Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)")
end
BASE.ServerName="Unknown"
if lfs and loadfile then
local serverfile=lfs.writedir()..'Config/serverSettings.lua'
if UTILS.FileExists(serverfile)then
loadfile(serverfile)()
if cfg and cfg.name then
BASE.ServerName=cfg.name
end
end
BASE.ServerName=BASE.ServerName or"Unknown"
BASE:I("Server Name: "..tostring(BASE.ServerName))
end
BASE:TraceOnOff(false)
env.info('*** MOOSE INCLUDE END *** ')
