mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
26889 lines
827 KiB
Lua
26889 lines
827 KiB
Lua
env.info('*** MOOSE STATIC INCLUDE START *** ')
|
|
env.info('Moose Generation Timestamp: 20170929_0658')
|
|
env.setErrorMessageBoxEnabled(false)
|
|
routines={}
|
|
routines.majorVersion=3
|
|
routines.minorVersion=3
|
|
routines.build=22
|
|
routines.utils={}
|
|
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
|
|
return tostring(tbl)
|
|
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('%q',s)
|
|
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 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)
|
|
return
|
|
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)
|
|
return
|
|
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.RED)
|
|
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
|
|
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)
|
|
self:E(err_str)
|
|
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.MetersToFeet=function(meters)
|
|
return meters/0.3048
|
|
end
|
|
UTILS.NMToMeters=function(NM)
|
|
return NM*1852
|
|
end
|
|
UTILS.FeetToMeters=function(feet)
|
|
return feet*0.3048
|
|
end
|
|
UTILS.MpsToKnots=function(mps)
|
|
return mps*3600/1852
|
|
end
|
|
UTILS.MpsToKmph=function(mps)
|
|
return mps*3.6
|
|
end
|
|
UTILS.KnotsToMps=function(knots)
|
|
return knots*1852/3600
|
|
end
|
|
UTILS.KnotsToKmph=function(knots)
|
|
return knots*1.852
|
|
end
|
|
UTILS.KmphToMps=function(kmph)
|
|
return kmph/3.6
|
|
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'
|
|
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=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('%02d',latDeg)..' '..string.format(minFrmtStr,latMin)..'\''..latHemi..' '
|
|
..string.format('%02d',lonDeg)..' '..string.format(minFrmtStr,lonMin)..'\''..lonHemi
|
|
end
|
|
end
|
|
UTILS.tostringMGRS=function(MGRS,acc)
|
|
if acc==0 then
|
|
return MGRS.UTMZone..' '..MGRS.MGRSDigraph
|
|
else
|
|
return MGRS.UTMZone..' '..MGRS.MGRSDigraph..' '..string.format('%0'..acc..'d',UTILS.Round(MGRS.Easting/(10^(5-acc)),0))
|
|
..' '..string.format('%0'..acc..'d',UTILS.Round(MGRS.Northing/(10^(5-acc)),0))
|
|
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.GetMarkID()
|
|
UTILS._MarkID=UTILS._MarkID+1
|
|
return UTILS._MarkID
|
|
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
|
|
local _TraceOnOff=true
|
|
local _TraceLevel=1
|
|
local _TraceAll=false
|
|
local _TraceClass={}
|
|
local _TraceClassMethod={}
|
|
local _ClassID=0
|
|
BASE={
|
|
ClassName="BASE",
|
|
ClassID=0,
|
|
Events={},
|
|
States={},
|
|
}
|
|
BASE.__={}
|
|
BASE._={
|
|
Schedules={}
|
|
}
|
|
FORMATION={
|
|
Cone="Cone",
|
|
Vee="Vee"
|
|
}
|
|
function BASE:New()
|
|
local self=routines.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
|
|
function BASE:GetParent(Child)
|
|
local Parent
|
|
if Child.ClassName=='BASE'then
|
|
Parent=nil
|
|
elseif rawget(Child,"__")then
|
|
Parent=getmetatable(Child.__).__index
|
|
else
|
|
Parent=getmetatable(Child).__index
|
|
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=self:GetParent(self)
|
|
while Parent do
|
|
if string.upper(Parent.ClassName)==ClassName then
|
|
return true
|
|
end
|
|
Parent=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(Event,EventFunction)
|
|
self:EventDispatcher():OnEventGeneric(EventFunction,self,Event)
|
|
return self
|
|
end
|
|
function BASE:UnHandleEvent(Event)
|
|
self:EventDispatcher():RemoveEvent(self,Event)
|
|
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)
|
|
self:F({EventTime,Initiator})
|
|
local Event={
|
|
id=world.event.S_EVENT_CRASH,
|
|
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: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,...)
|
|
self:F2({Start})
|
|
self:T3({...})
|
|
local ObjectName="-"
|
|
ObjectName=self.ClassName..self.ClassID
|
|
self:F3({"ScheduleOnce: ",ObjectName,Start})
|
|
self.SchedulerObject=self
|
|
local ScheduleID=_SCHEDULEDISPATCHER:AddSchedule(
|
|
self,
|
|
SchedulerFunction,
|
|
{...},
|
|
Start,
|
|
nil,
|
|
nil,
|
|
nil
|
|
)
|
|
self._.Schedules[#self.Schedules+1]=ScheduleID
|
|
return 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})
|
|
self.SchedulerObject=self
|
|
local ScheduleID=_SCHEDULEDISPATCHER:AddSchedule(
|
|
self,
|
|
SchedulerFunction,
|
|
{...},
|
|
Start,
|
|
Repeat,
|
|
RandomizeFactor,
|
|
Stop
|
|
)
|
|
self._.Schedules[SchedulerFunction]=ScheduleID
|
|
return self._.Schedules
|
|
end
|
|
function BASE:ScheduleStop(SchedulerFunction)
|
|
self:F3({"ScheduleStop:"})
|
|
_SCHEDULEDISPATCHER:Stop(self,self._.Schedules[SchedulerFunction])
|
|
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:TraceOnOff(TraceOnOff)
|
|
_TraceOnOff=TraceOnOff
|
|
end
|
|
function BASE:IsTrace()
|
|
if 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
|
|
self:E("Tracing level "..Level)
|
|
end
|
|
function BASE:TraceAll(TraceAll)
|
|
_TraceAll=TraceAll
|
|
if _TraceAll then
|
|
self:E("Tracing all methods in MOOSE ")
|
|
else
|
|
self:E("Switched off tracing all methods in MOOSE")
|
|
end
|
|
end
|
|
function BASE:TraceClass(Class)
|
|
_TraceClass[Class]=true
|
|
_TraceClassMethod[Class]={}
|
|
self:E("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:E("Tracing method "..Method.." of class "..Class)
|
|
end
|
|
function BASE:_F(Arguments,DebugInfoCurrentParam,DebugInfoFromParam)
|
|
if debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then
|
|
local DebugInfoCurrent=DebugInfoCurrentParam and DebugInfoCurrentParam or debug.getinfo(2,"nl")
|
|
local DebugInfoFrom=DebugInfoFromParam and DebugInfoFromParam or 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:%20s%05d.%s(%s)",LineCurrent,LineFrom,"F",self.ClassName,self.ClassID,Function,routines.utils.oneLineSerialize(Arguments)))
|
|
end
|
|
end
|
|
end
|
|
function BASE:F(Arguments)
|
|
if debug and _TraceOnOff then
|
|
local DebugInfoCurrent=debug.getinfo(2,"nl")
|
|
local DebugInfoFrom=debug.getinfo(3,"l")
|
|
if _TraceLevel>=1 then
|
|
self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom)
|
|
end
|
|
end
|
|
end
|
|
function BASE:F2(Arguments)
|
|
if debug and _TraceOnOff then
|
|
local DebugInfoCurrent=debug.getinfo(2,"nl")
|
|
local DebugInfoFrom=debug.getinfo(3,"l")
|
|
if _TraceLevel>=2 then
|
|
self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom)
|
|
end
|
|
end
|
|
end
|
|
function BASE:F3(Arguments)
|
|
if debug and _TraceOnOff then
|
|
local DebugInfoCurrent=debug.getinfo(2,"nl")
|
|
local DebugInfoFrom=debug.getinfo(3,"l")
|
|
if _TraceLevel>=3 then
|
|
self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom)
|
|
end
|
|
end
|
|
end
|
|
function BASE:_T(Arguments,DebugInfoCurrentParam,DebugInfoFromParam)
|
|
if debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then
|
|
local DebugInfoCurrent=DebugInfoCurrentParam and DebugInfoCurrentParam or debug.getinfo(2,"nl")
|
|
local DebugInfoFrom=DebugInfoFromParam and DebugInfoFromParam or 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:%20s%05d.%s",LineCurrent,LineFrom,"T",self.ClassName,self.ClassID,routines.utils.oneLineSerialize(Arguments)))
|
|
end
|
|
end
|
|
end
|
|
function BASE:T(Arguments)
|
|
if debug and _TraceOnOff then
|
|
local DebugInfoCurrent=debug.getinfo(2,"nl")
|
|
local DebugInfoFrom=debug.getinfo(3,"l")
|
|
if _TraceLevel>=1 then
|
|
self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom)
|
|
end
|
|
end
|
|
end
|
|
function BASE:T2(Arguments)
|
|
if debug and _TraceOnOff then
|
|
local DebugInfoCurrent=debug.getinfo(2,"nl")
|
|
local DebugInfoFrom=debug.getinfo(3,"l")
|
|
if _TraceLevel>=2 then
|
|
self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom)
|
|
end
|
|
end
|
|
end
|
|
function BASE:T3(Arguments)
|
|
if debug and _TraceOnOff then
|
|
local DebugInfoCurrent=debug.getinfo(2,"nl")
|
|
local DebugInfoFrom=debug.getinfo(3,"l")
|
|
if _TraceLevel>=3 then
|
|
self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom)
|
|
end
|
|
end
|
|
end
|
|
function BASE:E(Arguments)
|
|
if debug then
|
|
local DebugInfoCurrent=debug.getinfo(2,"nl")
|
|
local DebugInfoFrom=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:%20s%05d.%s(%s)",LineCurrent,LineFrom,"E",self.ClassName,self.ClassID,Function,routines.utils.oneLineSerialize(Arguments)))
|
|
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)
|
|
self.Report[#self.Report+1]=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={},
|
|
}
|
|
function SCHEDULER:New(SchedulerObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop)
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self:F2({Start,Repeat,RandomizeFactor,Stop})
|
|
local ScheduleID=nil
|
|
self.MasterObject=SchedulerObject
|
|
if SchedulerFunction then
|
|
ScheduleID=self:Schedule(SchedulerObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop)
|
|
end
|
|
return self,ScheduleID
|
|
end
|
|
function SCHEDULER:Schedule(SchedulerObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop)
|
|
self:F2({Start,Repeat,RandomizeFactor,Stop})
|
|
self:T3({SchedulerArguments})
|
|
local ObjectName="-"
|
|
if SchedulerObject and SchedulerObject.ClassName and SchedulerObject.ClassID then
|
|
ObjectName=SchedulerObject.ClassName..SchedulerObject.ClassID
|
|
end
|
|
self:F3({"Schedule :",ObjectName,tostring(SchedulerObject),Start,Repeat,RandomizeFactor,Stop})
|
|
self.SchedulerObject=SchedulerObject
|
|
local ScheduleID=_SCHEDULEDISPATCHER:AddSchedule(
|
|
self,
|
|
SchedulerFunction,
|
|
SchedulerArguments,
|
|
Start,
|
|
Repeat,
|
|
RandomizeFactor,
|
|
Stop
|
|
)
|
|
self.Schedules[#self.Schedules+1]=ScheduleID
|
|
return ScheduleID
|
|
end
|
|
function SCHEDULER:Start(ScheduleID)
|
|
self:F3({ScheduleID})
|
|
_SCHEDULEDISPATCHER:Start(self,ScheduleID)
|
|
end
|
|
function SCHEDULER:Stop(ScheduleID)
|
|
self:F3({ScheduleID})
|
|
_SCHEDULEDISPATCHER:Stop(self,ScheduleID)
|
|
end
|
|
function SCHEDULER:Remove(ScheduleID)
|
|
self:F3({ScheduleID})
|
|
_SCHEDULEDISPATCHER:Remove(self,ScheduleID)
|
|
end
|
|
function SCHEDULER:Clear()
|
|
self:F3()
|
|
_SCHEDULEDISPATCHER:Clear(self)
|
|
end
|
|
SCHEDULEDISPATCHER={
|
|
ClassName="SCHEDULEDISPATCHER",
|
|
CallID=0,
|
|
}
|
|
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)
|
|
self:F2({Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop})
|
|
self.CallID=self.CallID+1
|
|
local CallID=self.CallID.."#"..(Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID()or"")or""
|
|
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+.1
|
|
self.Schedule[Scheduler][CallID].Repeat=Repeat or 0
|
|
self.Schedule[Scheduler][CallID].Randomize=Randomize or 0
|
|
self.Schedule[Scheduler][CallID].Stop=Stop
|
|
self:T3(self.Schedule[Scheduler][CallID])
|
|
self.Schedule[Scheduler][CallID].CallHandler=function(CallID)
|
|
self:F2(CallID)
|
|
local ErrorHandler=function(errmsg)
|
|
env.info("Error in timer function: "..errmsg)
|
|
if debug~=nil then
|
|
env.info(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 ScheduleObject=Scheduler.SchedulerObject
|
|
local ScheduleFunction=Schedule.Function
|
|
local ScheduleArguments=Schedule.Arguments
|
|
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 Status,Result
|
|
if ScheduleObject then
|
|
local function Timer()
|
|
return ScheduleFunction(ScheduleObject,unpack(ScheduleArguments))
|
|
end
|
|
Status,Result=xpcall(Timer,ErrorHandler)
|
|
else
|
|
local function Timer()
|
|
return ScheduleFunction(unpack(ScheduleArguments))
|
|
end
|
|
Status,Result=xpcall(Timer,ErrorHandler)
|
|
end
|
|
local CurrentTime=timer.getTime()
|
|
local StartTime=Schedule.StartTime
|
|
self:F3({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.01
|
|
return ScheduleTime
|
|
else
|
|
self:Stop(Scheduler,CallID)
|
|
end
|
|
else
|
|
self:Stop(Scheduler,CallID)
|
|
end
|
|
else
|
|
self:E("Scheduled obsolete call for CallID: "..CallID)
|
|
end
|
|
return nil
|
|
end
|
|
self:Start(Scheduler,CallID)
|
|
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)
|
|
self:F2({Start=CallID,Scheduler=Scheduler})
|
|
if CallID then
|
|
local Schedule=self.Schedule[Scheduler]
|
|
if not Schedule[CallID].ScheduleID then
|
|
Schedule[CallID].StartTime=timer.getTime()
|
|
Schedule[CallID].ScheduleID=timer.scheduleFunction(
|
|
Schedule[CallID].CallHandler,
|
|
CallID,
|
|
timer.getTime()+Schedule[CallID].Start
|
|
)
|
|
end
|
|
else
|
|
for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do
|
|
self:Start(Scheduler,CallID)
|
|
end
|
|
end
|
|
end
|
|
function SCHEDULEDISPATCHER:Stop(Scheduler,CallID)
|
|
self:F2({Stop=CallID,Scheduler=Scheduler})
|
|
if CallID then
|
|
local Schedule=self.Schedule[Scheduler]
|
|
if Schedule[CallID].ScheduleID then
|
|
timer.removeFunction(Schedule[CallID].ScheduleID)
|
|
Schedule[CallID].ScheduleID=nil
|
|
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
|
|
EVENT={
|
|
ClassName="EVENT",
|
|
ClassID=0,
|
|
}
|
|
world.event.S_EVENT_NEW_CARGO=world.event.S_EVENT_MAX+1000
|
|
world.event.S_EVENT_DELETE_CARGO=world.event.S_EVENT_MAX+1001
|
|
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,
|
|
NewCargo=world.event.S_EVENT_NEW_CARGO,
|
|
DeleteCargo=world.event.S_EVENT_DELETE_CARGO,
|
|
}
|
|
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"
|
|
},
|
|
[EVENTS.NewCargo]={
|
|
Order=1,
|
|
Event="OnEventNewCargo",
|
|
Text="S_EVENT_NEW_CARGO"
|
|
},
|
|
[EVENTS.DeleteCargo]={
|
|
Order=1,
|
|
Event="OnEventDeleteCargo",
|
|
Text="S_EVENT_DELETE_CARGO"
|
|
},
|
|
}
|
|
function EVENT:New()
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self:F2()
|
|
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]=self.Events[EventID][EventPriority][EventClass]
|
|
self.Events[EventID][EventPriority][EventClass]=nil
|
|
end
|
|
function EVENT:Reset(EventObject)
|
|
self:E({"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(EventObject)
|
|
self:F3({EventObject:GetClassNameAndID()})
|
|
local EventClass=EventObject:GetClassNameAndID()
|
|
local EventPriority=EventClass:GetEventPriority()
|
|
for EventID,EventData in pairs(self.Events)do
|
|
self.Events[EventID][EventPriority][EventClass]=nil
|
|
end
|
|
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})
|
|
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,...)
|
|
self:E(GroupName)
|
|
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:CreateEventPlayerEnterUnit(PlayerUnit)
|
|
self:F({PlayerUnit})
|
|
local Event={
|
|
id=EVENTS.PlayerEnterUnit,
|
|
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 debug~=nil then
|
|
env.info(debug.traceback())
|
|
end
|
|
return errmsg
|
|
end
|
|
local EventMeta=_EVENTMETA[Event.id]
|
|
if self and
|
|
self.Events and
|
|
self.Events[Event.id]and
|
|
(Event.initiator~=nil or(Event.initiator==nil and Event.id~=EVENTS.PlayerLeaveUnit))then
|
|
if Event.initiator then
|
|
Event.IniObjectCategory=Event.initiator:getCategory()
|
|
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=""
|
|
if Event.IniDCSGroup and Event.IniDCSGroup:isExist()then
|
|
Event.IniDCSGroupName=Event.IniDCSGroup:getName()
|
|
Event.IniGroup=GROUP:FindByName(Event.IniDCSGroupName)
|
|
if Event.IniGroup then
|
|
Event.IniGroupName=Event.IniDCSGroupName
|
|
end
|
|
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.STATIC then
|
|
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
|
|
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
|
|
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)
|
|
if Event.TgtGroup then
|
|
Event.TgtGroupName=Event.TgtDCSGroupName
|
|
end
|
|
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
|
|
Event.TgtDCSUnitName=Event.TgtDCSUnit:getName()
|
|
Event.TgtUnitName=Event.TgtDCSUnitName
|
|
Event.TgtUnit=STATIC:FindByName(Event.TgtDCSUnitName)
|
|
Event.TgtCoalition=Event.TgtDCSUnit:getCoalition()
|
|
Event.TgtCategory=Event.TgtDCSUnit:getDesc().category
|
|
Event.TgtTypeName=Event.TgtDCSUnit:getTypeName()
|
|
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.cargo then
|
|
Event.Cargo=Event.cargo
|
|
Event.CargoName=Event.cargo.Name
|
|
end
|
|
local PriorityOrder=EventMeta.Order
|
|
local PriorityBegin=PriorityOrder==-1 and 5 or 1
|
|
local PriorityEnd=PriorityOrder==-1 and 1 or 5
|
|
if Event.IniObjectCategory~=Object.Category.STATIC then
|
|
self:E({EventMeta.Text,Event,Event.IniDCSUnitName,Event.TgtDCSUnitName,PriorityOrder})
|
|
end
|
|
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=GROUP:FindByName(Event.IniDCSGroupName)
|
|
Event.TgtGroup=GROUP:FindByName(Event.TgtDCSGroupName)
|
|
if EventData.EventUnit then
|
|
if EventClass:IsAlive()or
|
|
Event.id==EVENTS.Crash or
|
|
Event.id==EVENTS.Dead 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
|
|
if Event.IniObjectCategory~=3 then
|
|
self:E({"Calling EventFunction for UNIT ",EventClass:GetClassNameAndID(),", Unit ",Event.IniUnitName,EventPriority})
|
|
end
|
|
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
|
|
if Event.IniObjectCategory~=3 then
|
|
self:E({"Calling "..EventMeta.Event.." for Class ",EventClass:GetClassNameAndID(),EventPriority})
|
|
end
|
|
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.Crash or
|
|
Event.id==EVENTS.Dead 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
|
|
if Event.IniObjectCategory~=3 then
|
|
self:E({"Calling EventFunction for GROUP ",EventClass:GetClassNameAndID(),", Unit ",Event.IniUnitName,EventPriority})
|
|
end
|
|
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
|
|
if Event.IniObjectCategory~=3 then
|
|
self:E({"Calling "..EventMeta.Event.." for GROUP ",EventClass:GetClassNameAndID(),EventPriority})
|
|
end
|
|
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
|
|
if Event.IniObjectCategory~=3 then
|
|
self:F2({"Calling EventFunction for Class ",EventClass:GetClassNameAndID(),EventPriority})
|
|
end
|
|
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
|
|
if Event.IniObjectCategory~=3 then
|
|
self:F2({"Calling "..EventMeta.Event.." for Class ",EventClass:GetClassNameAndID(),EventPriority})
|
|
end
|
|
local Result,Value=xpcall(
|
|
function()
|
|
local Result,Value=EventFunction(EventClass,Event)
|
|
return Result,Value
|
|
end,ErrorHandler)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
self:E({EventMeta.Text,Event})
|
|
end
|
|
Event=nil
|
|
end
|
|
EVENTHANDLER={
|
|
ClassName="EVENTHANDLER",
|
|
ClassID=0,
|
|
}
|
|
function EVENTHANDLER:New()
|
|
self=BASE:Inherit(self,BASE:New())
|
|
return self
|
|
end
|
|
SETTINGS={
|
|
ClassName="SETTINGS",
|
|
}
|
|
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)
|
|
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:SetMetric()
|
|
self.Metric=true
|
|
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:IsMetric())
|
|
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()
|
|
self:E({BRA=(self.A2ASystem and self.A2ASystem=="BRAA")or(not self.A2ASystem and _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 A2GCoordinateMenu=MENU_GROUP:New(MenuGroup,"A2G Coordinate System",SettingsMenu):SetTime(MenuTime)
|
|
if not self:IsA2G_LL_DMS()then
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"Lat/Lon Degree Min Sec (LL DMS)",A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime)
|
|
end
|
|
if not self:IsA2G_LL_DDM()then
|
|
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
|
|
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
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"Bearing, Range (BR)",A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"BR"):SetTime(MenuTime)
|
|
end
|
|
if not self:IsA2G_MGRS()then
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"Military Grid (MGRS)",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 A2ACoordinateMenu=MENU_GROUP:New(MenuGroup,"A2A Coordinate System",SettingsMenu):SetTime(MenuTime)
|
|
if not self:IsA2A_LL_DMS()then
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"Lat/Lon Degree Min Sec (LL DMS)",A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime)
|
|
end
|
|
if not self:IsA2A_LL_DDM()then
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"Lat/Lon Degree Dec Min (LL DDM)",A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime)
|
|
end
|
|
if self:IsA2A_LL_DDM()then
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 1",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 2",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 3",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
|
|
end
|
|
if not self:IsA2A_BULLS()then
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"Bullseye (BULLS)",A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BULLS"):SetTime(MenuTime)
|
|
end
|
|
if not self:IsA2A_BRAA()then
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"Bearing Range Altitude Aspect (BRAA)",A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BRAA"):SetTime(MenuTime)
|
|
end
|
|
if not self:IsA2A_MGRS()then
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"Military Grid (MGRS)",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 MetricsMenu=MENU_GROUP:New(MenuGroup,"Measures and Weights System",SettingsMenu):SetTime(MenuTime)
|
|
if self:IsMetric()then
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"Imperial (Miles,Feet)",MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,false):SetTime(MenuTime)
|
|
end
|
|
if self:IsImperial()then
|
|
MENU_GROUP_COMMAND:New(MenuGroup,"Metric (Kilometers,Meters)",MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,true):SetTime(MenuTime)
|
|
end
|
|
local MessagesMenu=MENU_GROUP:New(MenuGroup,"Messages and Reports",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:SetPlayerMenu(PlayerUnit)
|
|
local PlayerGroup=PlayerUnit:GetGroup()
|
|
local PlayerName=PlayerUnit:GetPlayerName()
|
|
local PlayerNames=PlayerGroup:GetPlayerNames()
|
|
local PlayerMenu=MENU_GROUP:New(PlayerGroup,'Settings "'..PlayerName..'"')
|
|
self.PlayerMenu=PlayerMenu
|
|
local A2GCoordinateMenu=MENU_GROUP:New(PlayerGroup,"A2G Coordinate System",PlayerMenu)
|
|
if not self:IsA2G_LL_DMS()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Lat/Lon Degree Min Sec (LL DMS)",A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DMS")
|
|
end
|
|
if not self:IsA2G_LL_DDM()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Lat/Lon Degree Dec Min (LL DDM)",A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DDM")
|
|
end
|
|
if self:IsA2G_LL_DDM()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"LL DDM Accuracy 1",A2GCoordinateMenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"LL DDM Accuracy 2",A2GCoordinateMenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"LL DDM Accuracy 3",A2GCoordinateMenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3)
|
|
end
|
|
if not self:IsA2G_BR()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Bearing, Range (BR)",A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"BR")
|
|
end
|
|
if not self:IsA2G_MGRS()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS)",A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"MGRS")
|
|
end
|
|
if self:IsA2G_MGRS()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"MGRS Accuracy 1",A2GCoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"MGRS Accuracy 2",A2GCoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"MGRS Accuracy 3",A2GCoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"MGRS Accuracy 4",A2GCoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,4)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"MGRS Accuracy 5",A2GCoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,5)
|
|
end
|
|
local A2ACoordinateMenu=MENU_GROUP:New(PlayerGroup,"A2A Coordinate System",PlayerMenu)
|
|
if not self:IsA2A_LL_DMS()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Lat/Lon Degree Min Sec (LL DMS)",A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DMS")
|
|
end
|
|
if not self:IsA2A_LL_DDM()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Lat/Lon Degree Dec Min (LL DDM)",A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DDM")
|
|
end
|
|
if self:IsA2A_LL_DDM()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"LL DDM Accuracy 1",A2GCoordinateMenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"LL DDM Accuracy 2",A2GCoordinateMenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"LL DDM Accuracy 3",A2GCoordinateMenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3)
|
|
end
|
|
if not self:IsA2A_BULLS()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Bullseye (BULLS)",A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"BULLS")
|
|
end
|
|
if not self:IsA2A_BRAA()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Bearing Range Altitude Aspect (BRAA)",A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"BRAA")
|
|
end
|
|
if not self:IsA2A_MGRS()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS)",A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"MGRS")
|
|
end
|
|
if self:IsA2A_MGRS()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS) Accuracy 1",A2ACoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS) Accuracy 2",A2ACoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS) Accuracy 3",A2ACoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS) Accuracy 4",A2ACoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,4)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Military Grid (MGRS) Accuracy 5",A2ACoordinateMenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,5)
|
|
end
|
|
local MetricsMenu=MENU_GROUP:New(PlayerGroup,"Measures and Weights System",PlayerMenu)
|
|
if self:IsMetric()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Imperial (Miles,Feet)",MetricsMenu,self.MenuGroupMWSystem,self,PlayerUnit,PlayerGroup,PlayerName,false)
|
|
end
|
|
if self:IsImperial()then
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Metric (Kilometers,Meters)",MetricsMenu,self.MenuGroupMWSystem,self,PlayerUnit,PlayerGroup,PlayerName,true)
|
|
end
|
|
local MessagesMenu=MENU_GROUP:New(PlayerGroup,"Messages and Reports",PlayerMenu)
|
|
local UpdateMessagesMenu=MENU_GROUP:New(PlayerGroup,"Update Messages",MessagesMenu)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"Off",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,0)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"5 seconds",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,5)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"10 seconds",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,10)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"15 seconds",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,15)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"30 seconds",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,30)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"1 minute",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,60)
|
|
local InformationMessagesMenu=MENU_GROUP:New(PlayerGroup,"Information Messages",MessagesMenu)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"5 seconds",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,5)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"10 seconds",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,10)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"15 seconds",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,15)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"30 seconds",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,30)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"1 minute",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,60)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"2 minutes",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,"15 seconds",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,15)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"30 seconds",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,30)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"1 minute",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,60)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"2 minutes",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,120)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"3 minutes",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,"15 seconds",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,15)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"30 seconds",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,30)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"1 minute",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,60)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"2 minutes",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,120)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"3 minutes",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,"15 seconds",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,15)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"30 seconds",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,30)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"1 minute",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,60)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"2 minutes",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,120)
|
|
MENU_GROUP_COMMAND:New(PlayerGroup,"3 minutes",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,180)
|
|
return self
|
|
end
|
|
function SETTINGS:RemovePlayerMenu(PlayerUnit)
|
|
if self.PlayerMenu then
|
|
self.PlayerMenu:Remove()
|
|
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)
|
|
self:RemovePlayerMenu(PlayerUnit)
|
|
self:SetPlayerMenu(PlayerUnit)
|
|
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)
|
|
self:RemovePlayerMenu(PlayerUnit)
|
|
self:SetPlayerMenu(PlayerUnit)
|
|
end
|
|
function SETTINGS:MenuGroupLL_DDM_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,LL_Accuracy)
|
|
self.LL_Accuracy=LL_Accuracy
|
|
MESSAGE:New(string.format("Settings: A2G LL format accuracy set to %d for player %s.",LL_Accuracy,PlayerName),5):ToGroup(PlayerGroup)
|
|
self:RemovePlayerMenu(PlayerUnit)
|
|
self:SetPlayerMenu(PlayerUnit)
|
|
end
|
|
function SETTINGS:MenuGroupMGRS_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,MGRS_Accuracy)
|
|
self.MGRS_Accuracy=MGRS_Accuracy
|
|
MESSAGE:New(string.format("Settings: A2G MGRS format accuracy set to %d for player %s.",MGRS_Accuracy,PlayerName),5):ToGroup(PlayerGroup)
|
|
self:RemovePlayerMenu(PlayerUnit)
|
|
self:SetPlayerMenu(PlayerUnit)
|
|
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)
|
|
self:RemovePlayerMenu(PlayerUnit)
|
|
self:SetPlayerMenu(PlayerUnit)
|
|
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
|
|
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.MenuParentPath=MenuParentPath
|
|
self.Menus={}
|
|
self.MenuCount=0
|
|
self.MenuRemoveParent=false
|
|
self.MenuTime=timer.getTime()
|
|
return self
|
|
end
|
|
function MENU_BASE:GetMenu(MenuText)
|
|
self:F2({Menu=self.Menus[MenuText]})
|
|
return self.Menus[MenuText]
|
|
end
|
|
function MENU_BASE:SetRemoveParent(RemoveParent)
|
|
self:F2({RemoveParent})
|
|
self.MenuRemoveParent=RemoveParent
|
|
return self
|
|
end
|
|
function MENU_BASE:SetTime(MenuTime)
|
|
self.MenuTime=MenuTime
|
|
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 debug~=nil then
|
|
env.info(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)
|
|
local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
|
|
self:F({MenuText,ParentMenu})
|
|
self.MenuText=MenuText
|
|
self.ParentMenu=ParentMenu
|
|
self.Menus={}
|
|
self:T({MenuText})
|
|
self.MenuPath=missionCommands.addSubMenu(MenuText,self.MenuParentPath)
|
|
self:T({self.MenuPath})
|
|
if ParentMenu and ParentMenu.Menus then
|
|
ParentMenu.Menus[self.MenuPath]=self
|
|
end
|
|
return self
|
|
end
|
|
function MENU_MISSION:RemoveSubMenus()
|
|
self:F(self.MenuPath)
|
|
for MenuID,Menu in pairs(self.Menus)do
|
|
Menu:Remove()
|
|
end
|
|
end
|
|
function MENU_MISSION:Remove()
|
|
self:F(self.MenuPath)
|
|
self:RemoveSubMenus()
|
|
missionCommands.removeItem(self.MenuPath)
|
|
if self.ParentMenu then
|
|
self.ParentMenu.Menus[self.MenuPath]=nil
|
|
end
|
|
return nil
|
|
end
|
|
end
|
|
do
|
|
MENU_MISSION_COMMAND={
|
|
ClassName="MENU_MISSION_COMMAND"
|
|
}
|
|
function MENU_MISSION_COMMAND:New(MenuText,ParentMenu,CommandMenuFunction,...)
|
|
local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
|
|
self.MenuText=MenuText
|
|
self.ParentMenu=ParentMenu
|
|
self:T({MenuText,CommandMenuFunction,arg})
|
|
self.MenuPath=missionCommands.addCommand(MenuText,self.MenuParentPath,self.MenuCallHandler)
|
|
ParentMenu.Menus[self.MenuPath]=self
|
|
return self
|
|
end
|
|
function MENU_MISSION_COMMAND:Remove()
|
|
self:F(self.MenuPath)
|
|
missionCommands.removeItem(self.MenuPath)
|
|
if self.ParentMenu then
|
|
self.ParentMenu.Menus[self.MenuPath]=nil
|
|
end
|
|
return nil
|
|
end
|
|
end
|
|
do
|
|
MENU_COALITION={
|
|
ClassName="MENU_COALITION"
|
|
}
|
|
function MENU_COALITION:New(Coalition,MenuText,ParentMenu)
|
|
local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
|
|
self:F({Coalition,MenuText,ParentMenu})
|
|
self.Coalition=Coalition
|
|
self.MenuText=MenuText
|
|
self.ParentMenu=ParentMenu
|
|
self.Menus={}
|
|
self:T({MenuText})
|
|
self.MenuPath=missionCommands.addSubMenuForCoalition(Coalition,MenuText,self.MenuParentPath)
|
|
self:T({self.MenuPath})
|
|
if ParentMenu and ParentMenu.Menus then
|
|
ParentMenu.Menus[self.MenuPath]=self
|
|
end
|
|
return self
|
|
end
|
|
function MENU_COALITION:RemoveSubMenus()
|
|
self:F(self.MenuPath)
|
|
for MenuID,Menu in pairs(self.Menus)do
|
|
Menu:Remove()
|
|
end
|
|
end
|
|
function MENU_COALITION:Remove()
|
|
self:F(self.MenuPath)
|
|
self:RemoveSubMenus()
|
|
missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath)
|
|
if self.ParentMenu then
|
|
self.ParentMenu.Menus[self.MenuPath]=nil
|
|
end
|
|
return nil
|
|
end
|
|
end
|
|
do
|
|
MENU_COALITION_COMMAND={
|
|
ClassName="MENU_COALITION_COMMAND"
|
|
}
|
|
function MENU_COALITION_COMMAND:New(Coalition,MenuText,ParentMenu,CommandMenuFunction,...)
|
|
local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
|
|
self.MenuCoalition=Coalition
|
|
self.MenuText=MenuText
|
|
self.ParentMenu=ParentMenu
|
|
self:T({MenuText,CommandMenuFunction,arg})
|
|
self.MenuPath=missionCommands.addCommandForCoalition(self.MenuCoalition,MenuText,self.MenuParentPath,self.MenuCallHandler)
|
|
ParentMenu.Menus[self.MenuPath]=self
|
|
return self
|
|
end
|
|
function MENU_COALITION_COMMAND:Remove()
|
|
self:F(self.MenuPath)
|
|
missionCommands.removeItemForCoalition(self.MenuCoalition,self.MenuPath)
|
|
if self.ParentMenu then
|
|
self.ParentMenu.Menus[self.MenuPath]=nil
|
|
end
|
|
return nil
|
|
end
|
|
end
|
|
do
|
|
local _MENUCLIENTS={}
|
|
MENU_CLIENT={
|
|
ClassName="MENU_CLIENT"
|
|
}
|
|
function MENU_CLIENT:New(Client,MenuText,ParentMenu)
|
|
local MenuParentPath={}
|
|
if ParentMenu~=nil then
|
|
MenuParentPath=ParentMenu.MenuPath
|
|
end
|
|
local self=BASE:Inherit(self,MENU_BASE:New(MenuText,MenuParentPath))
|
|
self:F({Client,MenuText,ParentMenu})
|
|
self.MenuClient=Client
|
|
self.MenuClientGroupID=Client:GetClientGroupID()
|
|
self.MenuParentPath=MenuParentPath
|
|
self.MenuText=MenuText
|
|
self.ParentMenu=ParentMenu
|
|
self.Menus={}
|
|
if not _MENUCLIENTS[self.MenuClientGroupID]then
|
|
_MENUCLIENTS[self.MenuClientGroupID]={}
|
|
end
|
|
local MenuPath=_MENUCLIENTS[self.MenuClientGroupID]
|
|
self:T({Client:GetClientGroupName(),MenuPath[table.concat(MenuParentPath)],MenuParentPath,MenuText})
|
|
local MenuPathID=table.concat(MenuParentPath).."/"..MenuText
|
|
if MenuPath[MenuPathID]then
|
|
missionCommands.removeItemForGroup(self.MenuClient:GetClientGroupID(),MenuPath[MenuPathID])
|
|
end
|
|
self.MenuPath=missionCommands.addSubMenuForGroup(self.MenuClient:GetClientGroupID(),MenuText,MenuParentPath)
|
|
MenuPath[MenuPathID]=self.MenuPath
|
|
self:T({Client:GetClientGroupName(),self.MenuPath})
|
|
if ParentMenu and ParentMenu.Menus then
|
|
ParentMenu.Menus[self.MenuPath]=self
|
|
end
|
|
return self
|
|
end
|
|
function MENU_CLIENT:RemoveSubMenus()
|
|
self:F(self.MenuPath)
|
|
for MenuID,Menu in pairs(self.Menus)do
|
|
Menu:Remove()
|
|
end
|
|
end
|
|
function MENU_CLIENT:Remove()
|
|
self:F(self.MenuPath)
|
|
self:RemoveSubMenus()
|
|
if not _MENUCLIENTS[self.MenuClientGroupID]then
|
|
_MENUCLIENTS[self.MenuClientGroupID]={}
|
|
end
|
|
local MenuPath=_MENUCLIENTS[self.MenuClientGroupID]
|
|
if MenuPath[table.concat(self.MenuParentPath).."/"..self.MenuText]then
|
|
MenuPath[table.concat(self.MenuParentPath).."/"..self.MenuText]=nil
|
|
end
|
|
missionCommands.removeItemForGroup(self.MenuClient:GetClientGroupID(),self.MenuPath)
|
|
self.ParentMenu.Menus[self.MenuPath]=nil
|
|
return nil
|
|
end
|
|
MENU_CLIENT_COMMAND={
|
|
ClassName="MENU_CLIENT_COMMAND"
|
|
}
|
|
function MENU_CLIENT_COMMAND:New(Client,MenuText,ParentMenu,CommandMenuFunction,...)
|
|
local MenuParentPath={}
|
|
if ParentMenu~=nil then
|
|
MenuParentPath=ParentMenu.MenuPath
|
|
end
|
|
local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,MenuParentPath,CommandMenuFunction,arg))
|
|
self.MenuClient=Client
|
|
self.MenuClientGroupID=Client:GetClientGroupID()
|
|
self.MenuParentPath=MenuParentPath
|
|
self.MenuText=MenuText
|
|
self.ParentMenu=ParentMenu
|
|
if not _MENUCLIENTS[self.MenuClientGroupID]then
|
|
_MENUCLIENTS[self.MenuClientGroupID]={}
|
|
end
|
|
local MenuPath=_MENUCLIENTS[self.MenuClientGroupID]
|
|
self:T({Client:GetClientGroupName(),MenuPath[table.concat(MenuParentPath)],MenuParentPath,MenuText,CommandMenuFunction,arg})
|
|
local MenuPathID=table.concat(MenuParentPath).."/"..MenuText
|
|
if MenuPath[MenuPathID]then
|
|
missionCommands.removeItemForGroup(self.MenuClient:GetClientGroupID(),MenuPath[MenuPathID])
|
|
end
|
|
self.MenuPath=missionCommands.addCommandForGroup(self.MenuClient:GetClientGroupID(),MenuText,MenuParentPath,self.MenuCallHandler)
|
|
MenuPath[MenuPathID]=self.MenuPath
|
|
if ParentMenu and ParentMenu.Menus then
|
|
ParentMenu.Menus[self.MenuPath]=self
|
|
end
|
|
return self
|
|
end
|
|
function MENU_CLIENT_COMMAND:Remove()
|
|
self:F(self.MenuPath)
|
|
if not _MENUCLIENTS[self.MenuClientGroupID]then
|
|
_MENUCLIENTS[self.MenuClientGroupID]={}
|
|
end
|
|
local MenuPath=_MENUCLIENTS[self.MenuClientGroupID]
|
|
if MenuPath[table.concat(self.MenuParentPath).."/"..self.MenuText]then
|
|
MenuPath[table.concat(self.MenuParentPath).."/"..self.MenuText]=nil
|
|
end
|
|
missionCommands.removeItemForGroup(self.MenuClient:GetClientGroupID(),self.MenuPath)
|
|
self.ParentMenu.Menus[self.MenuPath]=nil
|
|
return nil
|
|
end
|
|
end
|
|
do
|
|
local _MENUGROUPS={}
|
|
MENU_GROUP={
|
|
ClassName="MENU_GROUP"
|
|
}
|
|
function MENU_GROUP:New(MenuGroup,MenuText,ParentMenu)
|
|
MenuGroup._Menus=MenuGroup._Menus or{}
|
|
local Path=(ParentMenu and(table.concat(ParentMenu.MenuPath or{},"@").."@"..MenuText))or MenuText
|
|
if MenuGroup._Menus[Path]then
|
|
self=MenuGroup._Menus[Path]
|
|
else
|
|
self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
|
|
MenuGroup._Menus[Path]=self
|
|
self.MenuGroup=MenuGroup
|
|
self.Path=Path
|
|
self.MenuGroupID=MenuGroup:GetID()
|
|
self.MenuText=MenuText
|
|
self.ParentMenu=ParentMenu
|
|
self:T({"Adding Menu ",MenuText,self.MenuParentPath})
|
|
self.MenuPath=missionCommands.addSubMenuForGroup(self.MenuGroupID,MenuText,self.MenuParentPath)
|
|
if self.ParentMenu and self.ParentMenu.Menus then
|
|
self.ParentMenu.Menus[MenuText]=self
|
|
self:F({self.ParentMenu.Menus,MenuText})
|
|
self.ParentMenu.MenuCount=self.ParentMenu.MenuCount+1
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
function MENU_GROUP:RemoveSubMenus(MenuTime,MenuTag)
|
|
self:T({"Removing Group SubMenus:",MenuTime,MenuTag,self.MenuGroup:GetName(),self.MenuPath})
|
|
for MenuText,Menu in pairs(self.Menus)do
|
|
Menu:Remove(MenuTime,MenuTag)
|
|
end
|
|
end
|
|
function MENU_GROUP:Remove(MenuTime,MenuTag)
|
|
self:RemoveSubMenus(MenuTime,MenuTag)
|
|
if not MenuTime or self.MenuTime~=MenuTime then
|
|
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
|
|
if self.MenuGroup._Menus[self.Path]then
|
|
self=self.MenuGroup._Menus[self.Path]
|
|
missionCommands.removeItemForGroup(self.MenuGroupID,self.MenuPath)
|
|
if self.ParentMenu then
|
|
self.ParentMenu.Menus[self.MenuText]=nil
|
|
self.ParentMenu.MenuCount=self.ParentMenu.MenuCount-1
|
|
if self.ParentMenu.MenuCount==0 then
|
|
if self.MenuRemoveParent==true then
|
|
self:T2("Removing Parent Menu ")
|
|
self.ParentMenu:Remove()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self:T({"Removing Group Menu:",MenuGroup=self.MenuGroup:GetName()})
|
|
self.MenuGroup._Menus[self.Path]=nil
|
|
self=nil
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
MENU_GROUP_COMMAND={
|
|
ClassName="MENU_GROUP_COMMAND"
|
|
}
|
|
function MENU_GROUP_COMMAND:New(MenuGroup,MenuText,ParentMenu,CommandMenuFunction,...)
|
|
MenuGroup._Menus=MenuGroup._Menus or{}
|
|
local Path=(ParentMenu and(table.concat(ParentMenu.MenuPath or{},"@").."@"..MenuText))or MenuText
|
|
if MenuGroup._Menus[Path]then
|
|
self=MenuGroup._Menus[Path]
|
|
self:SetCommandMenuFunction(CommandMenuFunction)
|
|
self:SetCommandMenuArguments(arg)
|
|
return self
|
|
end
|
|
self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
|
|
MenuGroup._Menus[Path]=self
|
|
self.Path=Path
|
|
self.MenuGroup=MenuGroup
|
|
self.MenuGroupID=MenuGroup:GetID()
|
|
self.MenuText=MenuText
|
|
self.ParentMenu=ParentMenu
|
|
self:F({"Adding Group Command Menu:",MenuGroup=MenuGroup:GetName(),MenuText=MenuText,MenuPath=self.MenuParentPath})
|
|
self.MenuPath=missionCommands.addCommandForGroup(self.MenuGroupID,MenuText,self.MenuParentPath,self.MenuCallHandler)
|
|
if self.ParentMenu and self.ParentMenu.Menus then
|
|
self.ParentMenu.Menus[MenuText]=self
|
|
self.ParentMenu.MenuCount=self.ParentMenu.MenuCount+1
|
|
self:F2({ParentMenu.Menus,MenuText})
|
|
end
|
|
return self
|
|
end
|
|
function MENU_GROUP_COMMAND:Remove(MenuTime,MenuTag)
|
|
if not MenuTime or self.MenuTime~=MenuTime then
|
|
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
|
|
if self.MenuGroup._Menus[self.Path]then
|
|
self=self.MenuGroup._Menus[self.Path]
|
|
missionCommands.removeItemForGroup(self.MenuGroupID,self.MenuPath)
|
|
self.ParentMenu.Menus[self.MenuText]=nil
|
|
self.ParentMenu.MenuCount=self.ParentMenu.MenuCount-1
|
|
if self.ParentMenu.MenuCount==0 then
|
|
if self.MenuRemoveParent==true then
|
|
self:T2("Removing Parent Menu ")
|
|
self.ParentMenu:Remove()
|
|
end
|
|
end
|
|
self.MenuGroup._Menus[self.Path]=nil
|
|
self=nil
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
end
|
|
ZONE_BASE={
|
|
ClassName="ZONE_BASE",
|
|
ZoneName="",
|
|
ZoneProbability=1,
|
|
}
|
|
function ZONE_BASE:New(ZoneName)
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self:F(ZoneName)
|
|
self.ZoneName=ZoneName
|
|
return self
|
|
end
|
|
function ZONE_BASE:GetName()
|
|
self:F2()
|
|
return self.ZoneName
|
|
end
|
|
function ZONE_BASE:IsVec2InZone(Vec2)
|
|
self:F2(Vec2)
|
|
return false
|
|
end
|
|
function ZONE_BASE:IsVec3InZone(Vec3)
|
|
self:F2(Vec3)
|
|
local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
|
|
return InZone
|
|
end
|
|
function ZONE_BASE:IsPointVec2InZone(PointVec2)
|
|
self:F2(PointVec2)
|
|
local InZone=self:IsVec2InZone(PointVec2:GetVec2())
|
|
return InZone
|
|
end
|
|
function ZONE_BASE:IsPointVec3InZone(PointVec3)
|
|
self:F2(PointVec3)
|
|
local InZone=self:IsPointVec2InZone(PointVec3)
|
|
return InZone
|
|
end
|
|
function ZONE_BASE:GetVec2()
|
|
self:F2(self.ZoneName)
|
|
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:GetCoordinate()
|
|
self:F2(self.ZoneName)
|
|
local Vec2=self:GetVec2()
|
|
local Coordinate=COORDINATE:NewFromVec2(Vec2)
|
|
self:T2({Coordinate})
|
|
return Coordinate
|
|
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)
|
|
local PointVec3=COORDINATE:NewFromVec3(Vec3)
|
|
self:T2({PointVec3})
|
|
return PointVec3
|
|
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:BoundZone()
|
|
self:F2()
|
|
end
|
|
function ZONE_BASE:SmokeZone(SmokeColor)
|
|
self:F2(SmokeColor)
|
|
end
|
|
function ZONE_BASE:SetZoneProbability(ZoneProbability)
|
|
self:F2(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
|
|
ZONE_RADIUS={
|
|
ClassName="ZONE_RADIUS",
|
|
}
|
|
function ZONE_RADIUS:New(ZoneName,Vec2,Radius)
|
|
local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName))
|
|
self:F({ZoneName,Vec2,Radius})
|
|
self.Radius=Radius
|
|
self.Vec2=Vec2
|
|
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)
|
|
self:F2(SmokeColor)
|
|
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()
|
|
POINT_VEC2:New(Point.x,Point.y):Smoke(SmokeColor)
|
|
end
|
|
return self
|
|
end
|
|
function ZONE_RADIUS:FlareZone(FlareColor,Points,Azimuth)
|
|
self:F2({FlareColor,Azimuth})
|
|
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()
|
|
POINT_VEC2:New(Point.x,Point.y):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()
|
|
self.Coalitions={}
|
|
local ZoneCoord=self:GetCoordinate()
|
|
local ZoneRadius=self:GetRadius()
|
|
self:E({ZoneCoord=ZoneCoord,ZoneRadius=ZoneRadius,ZoneCoordLL=ZoneCoord:ToStringLLDMS()})
|
|
local SphereSearch={
|
|
id=world.VolumeType.SPHERE,
|
|
params={
|
|
point=ZoneCoord:GetVec3(),
|
|
radius=ZoneRadius,
|
|
}
|
|
}
|
|
local function EvaluateZone(ZoneDCSUnit)
|
|
if ZoneDCSUnit:isExist()then
|
|
local CategoryDCSUnit=ZoneDCSUnit:getCategory()
|
|
if(CategoryDCSUnit==Object.Category.UNIT and ZoneDCSUnit:isActive())or
|
|
CategoryDCSUnit==Object.Category.STATIC then
|
|
local CoalitionDCSUnit=ZoneDCSUnit:getCoalition()
|
|
self.Coalitions[CoalitionDCSUnit]=true
|
|
self:E({Name=ZoneDCSUnit:getName(),Coalition=CoalitionDCSUnit})
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
world.searchObjects({Object.Category.UNIT,Object.Category.STATIC},SphereSearch,EvaluateZone)
|
|
end
|
|
function ZONE_RADIUS:CountCoalitions()
|
|
local Count=0
|
|
for CoalitionID,Coalition in pairs(self.Coalitions)do
|
|
Count=Count+1
|
|
end
|
|
return Count
|
|
end
|
|
function ZONE_RADIUS:IsAllInZoneOfCoalition(Coalition)
|
|
return self:CountCoalitions()==1 and self.Coalitions[Coalition]==true
|
|
end
|
|
function ZONE_RADIUS:IsAllInZoneOfOtherCoalition(Coalition)
|
|
self:E({Coalitions=self.Coalitions,Count=self:CountCoalitions()})
|
|
return self:CountCoalitions()==1 and self.Coalitions[Coalition]==nil
|
|
end
|
|
function ZONE_RADIUS:IsSomeInZoneOfCoalition(Coalition)
|
|
return self:CountCoalitions()>1 and self.Coalitions[Coalition]==true
|
|
end
|
|
function ZONE_RADIUS:IsNoneInZoneOfCoalition(Coalition)
|
|
return self.Coalitions[Coalition]==nil
|
|
end
|
|
function ZONE_RADIUS:GetCoalition()
|
|
local Count=0
|
|
local ReturnCoalition=nil
|
|
for CoalitionID,Coalition in pairs(self.Coalitions)do
|
|
Count=Count+1
|
|
ReturnCoalition=CoalitionID
|
|
end
|
|
if Count~=1 then
|
|
ReturnCoalition=nil
|
|
end
|
|
return ReturnCoalition
|
|
end
|
|
function ZONE_RADIUS:SearchZone(EvaluateFunction)
|
|
local SearchZoneResult=true
|
|
local ZoneCoord=self:GetCoordinate()
|
|
local ZoneRadius=self:GetRadius()
|
|
self:E({ZoneCoord=ZoneCoord,ZoneRadius=ZoneRadius,ZoneCoordLL=ZoneCoord:ToStringLLDMS()})
|
|
local SphereSearch={
|
|
id=world.VolumeType.SPHERE,
|
|
params={
|
|
point=ZoneCoord:GetVec3(),
|
|
radius=ZoneRadius/2,
|
|
}
|
|
}
|
|
local function EvaluateZone(ZoneDCSUnit)
|
|
env.info(ZoneDCSUnit:getName())
|
|
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)
|
|
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)
|
|
local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
|
|
return InZone
|
|
end
|
|
function ZONE_RADIUS:GetRandomVec2(inner,outer)
|
|
self:F(self.ZoneName,inner,outer)
|
|
local Point={}
|
|
local Vec2=self:GetVec2()
|
|
local _inner=inner or 0
|
|
local _outer=outer or self:GetRadius()
|
|
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);
|
|
self:T({Point})
|
|
return Point
|
|
end
|
|
function ZONE_RADIUS:GetRandomPointVec2(inner,outer)
|
|
self:F(self.ZoneName,inner,outer)
|
|
local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2())
|
|
self:T3({PointVec2})
|
|
return PointVec2
|
|
end
|
|
function ZONE_RADIUS:GetRandomPointVec3(inner,outer)
|
|
self:F(self.ZoneName,inner,outer)
|
|
local PointVec3=POINT_VEC3:NewFromVec2(self:GetRandomVec2())
|
|
self:T3({PointVec3})
|
|
return PointVec3
|
|
end
|
|
function ZONE_RADIUS:GetRandomCoordinate(inner,outer)
|
|
self:F(self.ZoneName,inner,outer)
|
|
local Coordinate=COORDINATE:NewFromVec2(self:GetRandomVec2())
|
|
self:T3({Coordinate=Coordinate})
|
|
return Coordinate
|
|
end
|
|
ZONE={
|
|
ClassName="ZONE",
|
|
}
|
|
function ZONE:New(ZoneName)
|
|
local Zone=trigger.misc.getZone(ZoneName)
|
|
if not Zone then
|
|
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))
|
|
self:F(ZoneName)
|
|
self.Zone=Zone
|
|
return self
|
|
end
|
|
ZONE_UNIT={
|
|
ClassName="ZONE_UNIT",
|
|
}
|
|
function ZONE_UNIT:New(ZoneName,ZoneUNIT,Radius)
|
|
local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,ZoneUNIT:GetVec2(),Radius))
|
|
self:F({ZoneName,ZoneUNIT:GetVec2(),Radius})
|
|
self.ZoneUNIT=ZoneUNIT
|
|
self.LastVec2=ZoneUNIT:GetVec2()
|
|
return self
|
|
end
|
|
function ZONE_UNIT:GetVec2()
|
|
self:F2(self.ZoneName)
|
|
local ZoneVec2=self.ZoneUNIT:GetVec2()
|
|
if ZoneVec2 then
|
|
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.ZoneUNIT: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))
|
|
self:F({ZoneName,ZoneGROUP:GetVec2(),Radius})
|
|
self._.ZoneGROUP=ZoneGROUP
|
|
return self
|
|
end
|
|
function ZONE_GROUP:GetVec2()
|
|
self:F(self.ZoneName)
|
|
local ZoneVec2=self._.ZoneGROUP:GetVec2()
|
|
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})
|
|
local i=0
|
|
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
|
|
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:Flush()
|
|
self:F2()
|
|
self:E({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:SmokeZone(SmokeColor)
|
|
self:F2(SmokeColor)
|
|
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)
|
|
POINT_VEC2:New(PointX,PointY):Smoke(SmokeColor)
|
|
end
|
|
j=i
|
|
i=i+1
|
|
end
|
|
return self
|
|
end
|
|
function ZONE_POLYGON_BASE:IsVec2InZone(Vec2)
|
|
self:F2(Vec2)
|
|
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:GetRandomVec2()
|
|
self:F2()
|
|
local Vec2Found=false
|
|
local Vec2
|
|
local BS=self:GetBoundingSquare()
|
|
self:T2(BS)
|
|
while Vec2Found==false do
|
|
Vec2={x=math.random(BS.x1,BS.x2),y=math.random(BS.y1,BS.y2)}
|
|
self:T2(Vec2)
|
|
if self:IsVec2InZone(Vec2)then
|
|
Vec2Found=true
|
|
end
|
|
end
|
|
self:T2(Vec2)
|
|
return Vec2
|
|
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
|
|
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})
|
|
return self
|
|
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={},
|
|
}
|
|
local _DATABASECoalition=
|
|
{
|
|
[1]="Red",
|
|
[2]="Blue",
|
|
}
|
|
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.NewCargo)
|
|
self:HandleEvent(EVENTS.DeleteCargo)
|
|
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventOnPlayerEnterUnit)
|
|
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventOnPlayerLeaveUnit)
|
|
self:_RegisterTemplates()
|
|
self:_RegisterGroupsAndUnits()
|
|
self:_RegisterClients()
|
|
self:_RegisterStatics()
|
|
self:_RegisterAirbases()
|
|
self.UNITS_Position=0
|
|
local function CheckPlayers(self)
|
|
local CoalitionsData={AlivePlayersRed=coalition.getPlayers(coalition.side.RED),AlivePlayersBlue=coalition.getPlayers(coalition.side.BLUE)}
|
|
for CoalitionId,CoalitionData in pairs(CoalitionsData)do
|
|
for UnitId,UnitData in pairs(CoalitionData)do
|
|
if UnitData and UnitData:isExist()then
|
|
local UnitName=UnitData:getName()
|
|
local PlayerName=UnitData:getPlayerName()
|
|
local PlayerUnit=UNIT:Find(UnitData)
|
|
if PlayerName and PlayerName~=""then
|
|
if self.PLAYERS[PlayerName]==nil or self.PLAYERS[PlayerName]~=UnitName then
|
|
self:AddPlayer(UnitName,PlayerName)
|
|
local Settings=SETTINGS:Set(PlayerName)
|
|
Settings:SetPlayerMenu(PlayerUnit)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self:E("Scheduling")
|
|
PlayerCheckSchedule=SCHEDULER:New(nil,CheckPlayers,{self},1,1)
|
|
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
|
|
local UnitRegister=UNIT:Register(DCSUnitName)
|
|
self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName)
|
|
table.insert(self.UNITS_Index,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)
|
|
end
|
|
end
|
|
function DATABASE:DeleteStatic(DCSStaticName)
|
|
end
|
|
function DATABASE:FindStatic(StaticName)
|
|
local StaticFound=self.STATICS[StaticName]
|
|
return StaticFound
|
|
end
|
|
function DATABASE:FindAirbase(AirbaseName)
|
|
local AirbaseFound=self.AIRBASES[AirbaseName]
|
|
return AirbaseFound
|
|
end
|
|
function DATABASE:AddAirbase(AirbaseName)
|
|
if not self.AIRBASES[AirbaseName]then
|
|
self.AIRBASES[AirbaseName]=AIRBASE:Register(AirbaseName)
|
|
end
|
|
end
|
|
function DATABASE:DeleteAirbase(AirbaseName)
|
|
self.AIRBASES[AirbaseName]=nil
|
|
end
|
|
function DATABASE:FindAirbase(AirbaseName)
|
|
local AirbaseFound=self.AIRBASES[AirbaseName]
|
|
return AirbaseFound
|
|
end
|
|
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: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:E({"Add GROUP:",GroupName})
|
|
self.GROUPS[GroupName]=GROUP:Register(GroupName)
|
|
end
|
|
return self.GROUPS[GroupName]
|
|
end
|
|
function DATABASE:AddPlayer(UnitName,PlayerName)
|
|
if PlayerName then
|
|
self:E({"Add player for unit:",UnitName,PlayerName})
|
|
self.PLAYERS[PlayerName]=UnitName
|
|
self.PLAYERUNITS[UnitName]=PlayerName
|
|
self.PLAYERSJOINED[PlayerName]=PlayerName
|
|
end
|
|
end
|
|
function DATABASE:DeletePlayer(UnitName,PlayerName)
|
|
if PlayerName then
|
|
self:E({"Clean player:",PlayerName})
|
|
self.PLAYERS[PlayerName]=nil
|
|
self.PLAYERUNITS[UnitName]=PlayerName
|
|
end
|
|
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(Status)
|
|
if self.Templates.Groups[GroupName]then
|
|
return self.Templates.Groups[GroupName].Status
|
|
else
|
|
return""
|
|
end
|
|
end
|
|
function DATABASE:_RegisterGroupTemplate(GroupTemplate,CoalitionID,CategoryID,CountryID)
|
|
local GroupTemplateName=env.getValueDictByKey(GroupTemplate.name)
|
|
local TraceTable={}
|
|
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=CoalitionID
|
|
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=CoalitionID
|
|
self.Templates.Groups[GroupTemplateName].CountryID=CountryID
|
|
TraceTable[#TraceTable+1]="Group"
|
|
TraceTable[#TraceTable+1]=self.Templates.Groups[GroupTemplateName].GroupName
|
|
TraceTable[#TraceTable+1]="Coalition"
|
|
TraceTable[#TraceTable+1]=self.Templates.Groups[GroupTemplateName].CoalitionID
|
|
TraceTable[#TraceTable+1]="Category"
|
|
TraceTable[#TraceTable+1]=self.Templates.Groups[GroupTemplateName].CategoryID
|
|
TraceTable[#TraceTable+1]="Country"
|
|
TraceTable[#TraceTable+1]=self.Templates.Groups[GroupTemplateName].CountryID
|
|
TraceTable[#TraceTable+1]="Units"
|
|
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=CoalitionID
|
|
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=CoalitionID
|
|
self.Templates.ClientsByName[UnitTemplate.name].CountryID=CountryID
|
|
self.Templates.ClientsByID[UnitTemplate.unitId]=UnitTemplate
|
|
end
|
|
TraceTable[#TraceTable+1]=self.Templates.Units[UnitTemplate.name].UnitName
|
|
end
|
|
self:E(TraceTable)
|
|
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 TraceTable={}
|
|
local StaticTemplateName=env.getValueDictByKey(StaticTemplate.name)
|
|
self.Templates.Statics[StaticTemplateName]=self.Templates.Statics[StaticTemplateName]or{}
|
|
StaticTemplate.CategoryID=CategoryID
|
|
StaticTemplate.CoalitionID=CoalitionID
|
|
StaticTemplate.CountryID=CountryID
|
|
self.Templates.Statics[StaticTemplateName].StaticName=StaticTemplateName
|
|
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
|
|
TraceTable[#TraceTable+1]="Static"
|
|
TraceTable[#TraceTable+1]=self.Templates.Statics[StaticTemplateName].GroupName
|
|
TraceTable[#TraceTable+1]="Coalition"
|
|
TraceTable[#TraceTable+1]=self.Templates.Statics[StaticTemplateName].CoalitionID
|
|
TraceTable[#TraceTable+1]="Category"
|
|
TraceTable[#TraceTable+1]=self.Templates.Statics[StaticTemplateName].CategoryID
|
|
TraceTable[#TraceTable+1]="Country"
|
|
TraceTable[#TraceTable+1]=self.Templates.Statics[StaticTemplateName].CountryID
|
|
self:E(TraceTable)
|
|
end
|
|
function DATABASE:GetStaticUnitTemplate(StaticName)
|
|
local StaticTemplate=self.Templates.Statics[StaticName].UnitTemplate
|
|
StaticTemplate.SpawnCoalitionID=self.Templates.Statics[StaticName].CoalitionID
|
|
StaticTemplate.SpawnCategoryID=self.Templates.Statics[StaticName].CategoryID
|
|
StaticTemplate.SpawnCountryID=self.Templates.Statics[StaticName].CountryID
|
|
return StaticTemplate
|
|
end
|
|
function DATABASE:GetGroupNameFromUnitName(UnitName)
|
|
return self.Templates.Units[UnitName].GroupName
|
|
end
|
|
function DATABASE:GetGroupTemplateFromUnitName(UnitName)
|
|
return self.Templates.Units[UnitName].GroupTemplate
|
|
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)}
|
|
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:E({"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)}
|
|
for CoalitionId,CoalitionData in pairs(CoalitionsData)do
|
|
for DCSGroupId,DCSGroup in pairs(CoalitionData)do
|
|
if DCSGroup:isExist()then
|
|
local DCSGroupName=DCSGroup:getName()
|
|
self:E({"Register Group:",DCSGroupName})
|
|
self:AddGroup(DCSGroupName)
|
|
for DCSUnitId,DCSUnit in pairs(DCSGroup:getUnits())do
|
|
local DCSUnitName=DCSUnit:getName()
|
|
self:E({"Register Unit:",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:E({"Register Client:",ClientName})
|
|
self:AddClient(ClientName)
|
|
end
|
|
return self
|
|
end
|
|
function DATABASE:_RegisterStatics()
|
|
local CoalitionsData={GroupsRed=coalition.getStaticObjects(coalition.side.RED),GroupsBlue=coalition.getStaticObjects(coalition.side.BLUE)}
|
|
for CoalitionId,CoalitionData in pairs(CoalitionsData)do
|
|
for DCSStaticId,DCSStatic in pairs(CoalitionData)do
|
|
if DCSStatic:isExist()then
|
|
local DCSStaticName=DCSStatic:getName()
|
|
self:E({"Register Static:",DCSStaticName})
|
|
self:AddStatic(DCSStaticName)
|
|
else
|
|
self:E({"Static does not exist: ",DCSStatic})
|
|
end
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
function DATABASE:_RegisterAirbases()
|
|
local CoalitionsData={AirbasesRed=coalition.getAirbases(coalition.side.RED),AirbasesBlue=coalition.getAirbases(coalition.side.BLUE),AirbasesNeutral=coalition.getAirbases(coalition.side.NEUTRAL)}
|
|
for CoalitionId,CoalitionData in pairs(CoalitionsData)do
|
|
for DCSAirbaseId,DCSAirbase in pairs(CoalitionData)do
|
|
local DCSAirbaseName=DCSAirbase:getName()
|
|
self:E({"Register Airbase:",DCSAirbaseName})
|
|
self:AddAirbase(DCSAirbaseName)
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
function DATABASE:_EventOnBirth(Event)
|
|
self:F2({Event})
|
|
if Event.IniDCSUnit then
|
|
if Event.IniObjectCategory==3 then
|
|
self:AddStatic(Event.IniDCSUnitName)
|
|
else
|
|
if Event.IniObjectCategory==1 then
|
|
self:AddUnit(Event.IniDCSUnitName)
|
|
self:AddGroup(Event.IniDCSGroupName)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function DATABASE:_EventOnDeadOrCrash(Event)
|
|
self:F2({Event})
|
|
if Event.IniDCSUnit then
|
|
if Event.IniObjectCategory==3 then
|
|
if self.STATICS[Event.IniDCSUnitName]then
|
|
self:DeleteStatic(Event.IniDCSUnitName)
|
|
end
|
|
else
|
|
if Event.IniObjectCategory==1 then
|
|
if self.UNITS[Event.IniDCSUnitName]then
|
|
self:DeleteUnit(Event.IniDCSUnitName)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function DATABASE:_EventOnPlayerEnterUnit(Event)
|
|
self:F2({Event})
|
|
if Event.IniUnit then
|
|
if Event.IniObjectCategory==1 then
|
|
self:AddUnit(Event.IniDCSUnitName)
|
|
self:AddGroup(Event.IniDCSGroupName)
|
|
local PlayerName=Event.IniUnit:GetPlayerName()
|
|
if not self.PLAYERS[PlayerName]then
|
|
self:AddPlayer(Event.IniUnitName,PlayerName)
|
|
end
|
|
local Settings=SETTINGS:Set(PlayerName)
|
|
Settings:SetPlayerMenu(Event.IniUnit)
|
|
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 self.PLAYERS[PlayerName]then
|
|
local Settings=SETTINGS:Set(PlayerName)
|
|
Settings:RemovePlayerMenu(Event.IniUnit)
|
|
self:DeletePlayer(Event.IniUnit,PlayerName)
|
|
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
|
|
local Scheduler=SCHEDULER:New(self,Schedule,{},0.001,0.001,0)
|
|
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: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:_RegisterTemplates()
|
|
self:F2()
|
|
self.Navpoints={}
|
|
self.UNITS={}
|
|
for CoalitionName,coa_data in pairs(env.mission.coalition)do
|
|
if(CoalitionName=='red'or CoalitionName=='blue')and type(coa_data)=='table'then
|
|
local CoalitionSide=coalition.side[string.upper(CoalitionName)]
|
|
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
|
|
for ZoneID,ZoneData in pairs(env.mission.triggers.zones)do
|
|
local ZoneName=ZoneData.name
|
|
self.ZONENAMES[ZoneName]=ZoneName
|
|
end
|
|
return self
|
|
end
|
|
SET_BASE={
|
|
ClassName="SET_BASE",
|
|
Filter={},
|
|
Set={},
|
|
List={},
|
|
Index={},
|
|
}
|
|
function SET_BASE:New(Database)
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self.Database=Database
|
|
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:_Find(ObjectName)
|
|
local ObjectFound=self.Set[ObjectName]
|
|
return ObjectFound
|
|
end
|
|
function SET_BASE:GetSet()
|
|
self:F2()
|
|
return self.Set
|
|
end
|
|
function SET_BASE:Add(ObjectName,Object)
|
|
self:F(ObjectName)
|
|
self.Set[ObjectName]=Object
|
|
table.insert(self.Index,ObjectName)
|
|
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:Remove(ObjectName)
|
|
local Object=self.Set[ObjectName]
|
|
self:F3({ObjectName,Object})
|
|
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
|
|
end
|
|
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 ObjectName=self.Index[#self.Index]
|
|
local LastObject=self.Set[ObjectName]
|
|
self:T3({LastObject})
|
|
return LastObject
|
|
end
|
|
function SET_BASE:GetRandom()
|
|
local RandomItem=self.Set[self.Index[math.random(#self.Index)]]
|
|
self:T3({RandomItem})
|
|
return RandomItem
|
|
end
|
|
function SET_BASE:Count()
|
|
return self.Index and#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:FilterOnce()
|
|
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:_FilterStart()
|
|
for ObjectName,Object in pairs(self.Database)do
|
|
if self:IsIncludeObject(Object)then
|
|
self:E({"Adding Object:",ObjectName})
|
|
self:Add(ObjectName,Object)
|
|
end
|
|
end
|
|
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
|
|
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
|
|
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
|
|
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventOnPlayerEnterUnit)
|
|
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventOnPlayerLeaveUnit)
|
|
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:DistanceFromVec2(ObjectData:GetVec2())
|
|
else
|
|
local Distance=PointVec2:DistanceFromVec2(ObjectData:GetVec2())
|
|
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:F3({Event})
|
|
if Event.IniDCSUnit then
|
|
local ObjectName,Object=self:FindInDatabase(Event)
|
|
if ObjectName then
|
|
self:Remove(ObjectName)
|
|
end
|
|
end
|
|
end
|
|
function SET_BASE:_EventOnPlayerEnterUnit(Event)
|
|
self:F3({Event})
|
|
if Event.IniDCSUnit then
|
|
local ObjectName,Object=self:AddInDatabase(Event)
|
|
self:T3(ObjectName,Object)
|
|
if self:IsIncludeObject(Object)then
|
|
self:Add(ObjectName,Object)
|
|
end
|
|
end
|
|
end
|
|
function SET_BASE:_EventOnPlayerLeaveUnit(Event)
|
|
self:F3({Event})
|
|
local ObjectName=Event.IniDCSUnit
|
|
if Event.IniDCSUnit then
|
|
if Event.IniDCSGroup then
|
|
local GroupUnits=Event.IniDCSGroup:getUnits()
|
|
local PlayerCount=0
|
|
for _,DCSUnit in pairs(GroupUnits)do
|
|
if DCSUnit~=Event.IniDCSUnit then
|
|
if DCSUnit:getPlayerName()~=nil then
|
|
PlayerCount=PlayerCount+1
|
|
end
|
|
end
|
|
end
|
|
self:E(PlayerCount)
|
|
if PlayerCount==0 then
|
|
self:Remove(Event.IniDCSGroupName)
|
|
end
|
|
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),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:IsIncludeObject(Object)
|
|
self:F3(Object)
|
|
return true
|
|
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()
|
|
self:F3()
|
|
local ObjectNames=""
|
|
for ObjectName,Object in pairs(self.Set)do
|
|
ObjectNames=ObjectNames..ObjectName..", "
|
|
end
|
|
self:E({"Objects in Set:",ObjectNames})
|
|
return ObjectNames
|
|
end
|
|
SET_GROUP={
|
|
ClassName="SET_GROUP",
|
|
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,
|
|
structure=Group.Category.STRUCTURE,
|
|
},
|
|
},
|
|
}
|
|
function SET_GROUP:New()
|
|
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.GROUPS))
|
|
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.GroupName)
|
|
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
|
|
for ObjectID,ObjectData in pairs(self.Set)do
|
|
if NearestGroup==nil then
|
|
NearestGroup=ObjectData
|
|
ClosestDistance=PointVec2:DistanceFromVec2(ObjectData:GetVec2())
|
|
else
|
|
local Distance=PointVec2:DistanceFromVec2(ObjectData:GetVec2())
|
|
if Distance<ClosestDistance then
|
|
NearestGroup=ObjectData
|
|
ClosestDistance=Distance
|
|
end
|
|
end
|
|
end
|
|
return NearestGroup
|
|
end
|
|
function SET_GROUP: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_GROUP: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_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:FilterStart()
|
|
if _DATABASE then
|
|
self:_FilterStart()
|
|
end
|
|
return self
|
|
end
|
|
function SET_GROUP:_EventOnDeadOrCrash(Event)
|
|
self:F3({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_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.Set)
|
|
return self
|
|
end
|
|
function SET_GROUP:ForEachGroupCompletelyInZone(ZoneObject,IteratorFunction,...)
|
|
self:F2(arg)
|
|
self:ForEach(IteratorFunction,arg,self.Set,
|
|
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.Set,
|
|
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.Set,
|
|
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: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:IsIncludeObject(MooseGroup)
|
|
self:F2(MooseGroup)
|
|
local MooseGroupInclude=true
|
|
if self.Filter.Coalitions then
|
|
local MooseGroupCoalition=false
|
|
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
|
|
self:T3({"Coalition:",MooseGroup:GetCoalition(),self.FilterMeta.Coalitions[CoalitionName],CoalitionName})
|
|
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MooseGroup:GetCoalition()then
|
|
MooseGroupCoalition=true
|
|
end
|
|
end
|
|
MooseGroupInclude=MooseGroupInclude and MooseGroupCoalition
|
|
end
|
|
if self.Filter.Categories then
|
|
local MooseGroupCategory=false
|
|
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
|
|
self:T3({"Category:",MooseGroup:GetCategory(),self.FilterMeta.Categories[CategoryName],CategoryName})
|
|
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MooseGroup:GetCategory()then
|
|
MooseGroupCategory=true
|
|
end
|
|
end
|
|
MooseGroupInclude=MooseGroupInclude and MooseGroupCategory
|
|
end
|
|
if self.Filter.Countries then
|
|
local MooseGroupCountry=false
|
|
for CountryID,CountryName in pairs(self.Filter.Countries)do
|
|
self:T3({"Country:",MooseGroup:GetCountry(),CountryName})
|
|
if country.id[CountryName]==MooseGroup:GetCountry()then
|
|
MooseGroupCountry=true
|
|
end
|
|
end
|
|
MooseGroupInclude=MooseGroupInclude and MooseGroupCountry
|
|
end
|
|
if self.Filter.GroupPrefixes then
|
|
local MooseGroupPrefix=false
|
|
for GroupPrefixId,GroupPrefix in pairs(self.Filter.GroupPrefixes)do
|
|
self:T3({"Prefix:",string.find(MooseGroup:GetName(),GroupPrefix,1),GroupPrefix})
|
|
if string.find(MooseGroup:GetName(),GroupPrefix:gsub("-","%%-"),1)then
|
|
MooseGroupPrefix=true
|
|
end
|
|
end
|
|
MooseGroupInclude=MooseGroupInclude and MooseGroupPrefix
|
|
end
|
|
self:T2(MooseGroupInclude)
|
|
return MooseGroupInclude
|
|
end
|
|
do
|
|
SET_UNIT={
|
|
ClassName="SET_UNIT",
|
|
Units={},
|
|
Filter={
|
|
Coalitions=nil,
|
|
Categories=nil,
|
|
Types=nil,
|
|
Countries=nil,
|
|
UnitPrefixes=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))
|
|
return self
|
|
end
|
|
function SET_UNIT:AddUnit(AddUnit)
|
|
self:F2(AddUnit:GetName())
|
|
self:Add(AddUnit:GetName(),AddUnit)
|
|
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: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:FilterStart()
|
|
if _DATABASE then
|
|
self:_FilterStart()
|
|
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:E({ZoneUnitName=ZoneUnitName})
|
|
if self:FindUnit(ZoneUnitName)then
|
|
IsPartiallyInZone=true
|
|
self:E({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
|
|
function SET_UNIT:ForEachUnitInZone(IteratorFunction,...)
|
|
self:F2(arg)
|
|
self:ForEach(IteratorFunction,arg,self.Set)
|
|
return self
|
|
end
|
|
end
|
|
function SET_UNIT:ForEachUnit(IteratorFunction,...)
|
|
self:F2(arg)
|
|
self:ForEach(IteratorFunction,arg,self.Set)
|
|
return self
|
|
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:E({ThreatLevel=ThreatLevel,ThreatLevelSet=ThreatLevelSet[ThreatLevel].Set})
|
|
end
|
|
local ThreatLevelIncrement=FromThreatLevel<=ToThreatLevel and 1 or-1
|
|
for ThreatLevel=FromThreatLevel,ToThreatLevel,ThreatLevelIncrement do
|
|
self:E({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.Set,
|
|
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.Set,
|
|
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: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 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: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=true
|
|
if self.Filter.Coalitions then
|
|
local MUnitCoalition=false
|
|
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
|
|
self:E({"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
|
|
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
|
|
end
|
|
do
|
|
SET_STATIC={
|
|
ClassName="SET_STATIC",
|
|
Statics={},
|
|
Filter={
|
|
Coalitions=nil,
|
|
Categories=nil,
|
|
Types=nil,
|
|
Countries=nil,
|
|
StaticPrefixes=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: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()
|
|
end
|
|
return self
|
|
end
|
|
function SET_STATIC:AddInDatabase(Event)
|
|
self:F3({Event})
|
|
if Event.IniObjectCategory==Object.Category.STATIC then
|
|
if not self.Database[Event.IniDCSStaticName]then
|
|
self.Database[Event.IniDCSStaticName]=STATIC:Register(Event.IniDCSStaticName)
|
|
self:T3(self.Database[Event.IniDCSStaticName])
|
|
end
|
|
end
|
|
return Event.IniDCSStaticName,self.Database[Event.IniDCSStaticName]
|
|
end
|
|
function SET_STATIC:FindInDatabase(Event)
|
|
self:F2({Event.IniDCSStaticName,self.Set[Event.IniDCSStaticName],Event})
|
|
return Event.IniDCSStaticName,self.Set[Event.IniDCSStaticName]
|
|
end
|
|
do
|
|
function SET_STATIC:IsPatriallyInZone(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.Set)
|
|
return self
|
|
end
|
|
end
|
|
function SET_STATIC:ForEachStatic(IteratorFunction,...)
|
|
self:F2(arg)
|
|
self:ForEach(IteratorFunction,arg,self.Set)
|
|
return self
|
|
end
|
|
function SET_STATIC:ForEachStaticCompletelyInZone(ZoneObject,IteratorFunction,...)
|
|
self:F2(arg)
|
|
self:ForEach(IteratorFunction,arg,self.Set,
|
|
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.Set,
|
|
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: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
|
|
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
|
|
SET_CLIENT={
|
|
ClassName="SET_CLIENT",
|
|
Clients={},
|
|
Filter={
|
|
Coalitions=nil,
|
|
Categories=nil,
|
|
Types=nil,
|
|
Countries=nil,
|
|
ClientPrefixes=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))
|
|
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: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:FilterStart()
|
|
if _DATABASE then
|
|
self:_FilterStart()
|
|
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.Set)
|
|
return self
|
|
end
|
|
function SET_CLIENT:ForEachClientInZone(ZoneObject,IteratorFunction,...)
|
|
self:F2(arg)
|
|
self:ForEach(IteratorFunction,arg,self.Set,
|
|
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.Set,
|
|
function(ZoneObject,ClientObject)
|
|
if ClientObject:IsNotInZone(ZoneObject)then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end,{ZoneObject})
|
|
return self
|
|
end
|
|
function SET_CLIENT: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
|
|
self:T2(MClientInclude)
|
|
return MClientInclude
|
|
end
|
|
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: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.AirbaseName)
|
|
end
|
|
return self
|
|
end
|
|
function SET_AIRBASE:FindAirbase(AirbaseName)
|
|
local AirbaseFound=self.Set[AirbaseName]
|
|
return AirbaseFound
|
|
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:_FilterStart()
|
|
end
|
|
return self
|
|
end
|
|
function SET_AIRBASE:AddInDatabase(Event)
|
|
self:F3({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.Set)
|
|
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
|
|
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: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()
|
|
end
|
|
self:HandleEvent(EVENTS.NewCargo)
|
|
self:HandleEvent(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.Set)
|
|
return self
|
|
end
|
|
function SET_CARGO:FindNearestCargoFromPointVec2(PointVec2)
|
|
self:F2(PointVec2)
|
|
local NearestCargo=self:FindNearestObjectFromPointVec2(PointVec2)
|
|
return NearestCargo
|
|
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:T({"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:T({"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:T({"Evaluated Prefix",MCargoPrefix})
|
|
MCargoInclude=MCargoInclude and MCargoPrefix
|
|
end
|
|
end
|
|
self:T2(MCargoInclude)
|
|
return MCargoInclude
|
|
end
|
|
function SET_CARGO:OnEventNewCargo(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:Remove(Cargo.Name)
|
|
end
|
|
end
|
|
end
|
|
do
|
|
COORDINATE={
|
|
ClassName="COORDINATE",
|
|
}
|
|
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:NewFromVec2(Vec2,LandHeightAdd)
|
|
local LandHeight=land.getHeight(Vec2)
|
|
LandHeightAdd=LandHeightAdd or 0
|
|
LandHeight=LandHeight+LandHeightAdd
|
|
local self=self:New(Vec2.x,LandHeight,Vec2.y)
|
|
self:F2(self)
|
|
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: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:DistanceFromVec2(Vec2Reference)
|
|
self:F2(Vec2Reference)
|
|
local Distance=((Vec2Reference.x-self.x)^2+(Vec2Reference.y-self.z)^2)^0.5
|
|
self:T2(Distance)
|
|
return Distance
|
|
end
|
|
function COORDINATE:Translate(Distance,Angle)
|
|
local SX=self.x
|
|
local SY=self.z
|
|
local Radians=Angle/180*math.pi
|
|
local TX=Distance*math.cos(Radians)+SX
|
|
local TY=Distance*math.sin(Radians)+SY
|
|
return COORDINATE:NewFromVec2({x=TX,y=TY})
|
|
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: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:GetMovingText(Settings)
|
|
return self:GetVelocityText(Settings)..", "..self:GetHeadingText(Settings)
|
|
end
|
|
function COORDINATE:GetDirectionVec3(TargetCoordinate)
|
|
return{x=TargetCoordinate.x-self.x,y=TargetCoordinate.y-self.y,z=TargetCoordinate.z-self.z}
|
|
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:Get2DDistance(TargetCoordinate)
|
|
local TargetVec3=TargetCoordinate:GetVec3()
|
|
local SourceVec3=self:GetVec3()
|
|
return((TargetVec3.x-SourceVec3.x)^2+(TargetVec3.z-SourceVec3.z)^2)^0.5
|
|
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)
|
|
local Settings=Settings or _SETTINGS
|
|
local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),Precision)
|
|
local s=string.format('%03d°',AngleDegrees)
|
|
return s
|
|
end
|
|
function COORDINATE:GetDistanceText(Distance,Settings)
|
|
local Settings=Settings or _SETTINGS
|
|
local DistanceText
|
|
if Settings:IsMetric()then
|
|
DistanceText=" for "..UTILS.Round(Distance/1000,2).." km"
|
|
else
|
|
DistanceText=" for "..UTILS.Round(UTILS.MetersToNM(Distance),2).." miles"
|
|
end
|
|
return DistanceText
|
|
end
|
|
function COORDINATE:GetAltitudeText(Settings)
|
|
local Altitude=self.y
|
|
local Settings=Settings or _SETTINGS
|
|
if Altitude~=0 then
|
|
if Settings:IsMetric()then
|
|
return" at "..UTILS.Round(self.y,-3).." meters"
|
|
else
|
|
return" at "..UTILS.Round(UTILS.MetersToFeet(self.y),-3).." feet"
|
|
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)
|
|
local Settings=Settings or _SETTINGS
|
|
local BearingText=self:GetBearingText(AngleRadians,0,Settings)
|
|
local DistanceText=self:GetDistanceText(Distance,Settings)
|
|
local BRText=BearingText..DistanceText
|
|
return BRText
|
|
end
|
|
function COORDINATE:GetBRAText(AngleRadians,Distance,Settings)
|
|
local Settings=Settings or _SETTINGS
|
|
local BearingText=self:GetBearingText(AngleRadians,0,Settings)
|
|
local DistanceText=self:GetDistanceText(Distance,Settings)
|
|
local AltitudeText=self:GetAltitudeText(Settings)
|
|
local BRAText=BearingText..DistanceText..AltitudeText
|
|
return BRAText
|
|
end
|
|
function COORDINATE:Translate(Distance,Angle)
|
|
local SX=self.x
|
|
local SZ=self.z
|
|
local Radians=Angle/180*math.pi
|
|
local TX=Distance*math.cos(Radians)+SX
|
|
local TZ=Distance*math.sin(Radians)+SZ
|
|
return COORDINATE:New(TX,self.y,TZ)
|
|
end
|
|
function COORDINATE:WaypointAir(AltType,Type,Action,Speed,SpeedLocked)
|
|
self:F2({AltType,Type,Action,Speed,SpeedLocked})
|
|
local RoutePoint={}
|
|
RoutePoint.x=self.x
|
|
RoutePoint.y=self.z
|
|
RoutePoint.alt=self.y
|
|
RoutePoint.alt_type=AltType or"RADIO"
|
|
RoutePoint.type=Type or nil
|
|
RoutePoint.action=Action or nil
|
|
RoutePoint.speed=(Speed and Speed/3.6)or(500/3.6)
|
|
RoutePoint.speed_locked=true
|
|
RoutePoint.task={}
|
|
RoutePoint.task.id="ComboTask"
|
|
RoutePoint.task.params={}
|
|
RoutePoint.task.params.tasks={}
|
|
return RoutePoint
|
|
end
|
|
function COORDINATE:WaypointGround(Speed,Formation)
|
|
self:F2({Formation,Speed})
|
|
local RoutePoint={}
|
|
RoutePoint.x=self.x
|
|
RoutePoint.y=self.z
|
|
RoutePoint.action=Formation or""
|
|
RoutePoint.speed=(Speed or 999)/3.6
|
|
RoutePoint.speed_locked=true
|
|
RoutePoint.task={}
|
|
RoutePoint.task.id="ComboTask"
|
|
RoutePoint.task.params={}
|
|
RoutePoint.task.params.tasks={}
|
|
return RoutePoint
|
|
end
|
|
function COORDINATE:Explosion(ExplosionIntensity)
|
|
self:F2({ExplosionIntensity})
|
|
trigger.action.explosion(self:GetVec3(),ExplosionIntensity)
|
|
end
|
|
function COORDINATE:IlluminationBomb()
|
|
self:F2()
|
|
trigger.action.illuminationBomb(self:GetVec3())
|
|
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: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)
|
|
local MarkID=UTILS.GetMarkID()
|
|
trigger.action.markToAll(MarkID,MarkText,self:GetVec3())
|
|
return MarkID
|
|
end
|
|
function COORDINATE:MarkToCoalition(MarkText,Coalition)
|
|
local MarkID=UTILS.GetMarkID()
|
|
trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),Coalition)
|
|
return MarkID
|
|
end
|
|
function COORDINATE:MarkToCoalitionRed(MarkText)
|
|
return self:MarkToCoalition(MarkText,coalition.side.RED)
|
|
end
|
|
function COORDINATE:MarkToCoalitionBlue(MarkText)
|
|
return self:MarkToCoalition(MarkText,coalition.side.BLUE)
|
|
end
|
|
function COORDINATE:MarkToGroup(MarkText,MarkGroup)
|
|
local MarkID=UTILS.GetMarkID()
|
|
trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),MarkGroup:GetID())
|
|
return MarkID
|
|
end
|
|
function COORDINATE:RemoveMark(MarkID)
|
|
trigger.action.removeMark(MarkID)
|
|
end
|
|
end
|
|
function COORDINATE:IsLOS(ToCoordinate)
|
|
local FromVec3=self:GetVec3()
|
|
FromVec3.y=FromVec3.y+2
|
|
local ToVec3=ToCoordinate:GetVec3()
|
|
ToVec3.y=ToVec3.y+2
|
|
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:ToStringBR(FromCoordinate,Settings)
|
|
local DirectionVec3=FromCoordinate:GetDirectionVec3(self)
|
|
local AngleRadians=self:GetAngleRadians(DirectionVec3)
|
|
local Distance=self:Get2DDistance(FromCoordinate)
|
|
return"BR, "..self:GetBRText(AngleRadians,Distance,Settings)
|
|
end
|
|
function COORDINATE:ToStringBRA(FromCoordinate,Settings)
|
|
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)
|
|
end
|
|
function COORDINATE:ToStringBULLS(Coalition,Settings)
|
|
local TargetCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition))
|
|
local DirectionVec3=self:GetDirectionVec3(TargetCoordinate)
|
|
local AngleRadians=self:GetAngleRadians(DirectionVec3)
|
|
local Distance=self:Get2DDistance(TargetCoordinate)
|
|
local Altitude=self:GetAltitudeText()
|
|
return"BULLS, "..self:GetBRText(AngleRadians,Distance,Settings)
|
|
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: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)
|
|
self:E({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).." 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).." from "..ReferenceName
|
|
end
|
|
return nil
|
|
end
|
|
function COORDINATE:ToStringA2G(Controllable,Settings)
|
|
self:F({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)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)
|
|
self:F({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)
|
|
else
|
|
return self:ToStringMGRS(Settings)
|
|
end
|
|
end
|
|
if Settings:IsA2A_BULLS()then
|
|
local Coalition=Controllable:GetCoalition()
|
|
return self:ToStringBULLS(Coalition,Settings)
|
|
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)
|
|
self:F({Controllable=Controllable and Controllable:GetName()})
|
|
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
|
|
local ModeA2A=true
|
|
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
|
|
end
|
|
end
|
|
else
|
|
local IsAir=Controllable and Controllable:IsAirPlane()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
|
|
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
|
|
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)
|
|
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.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)
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self:F({MessageText})
|
|
self.MessageType=MessageType
|
|
self.MessageTime=timer.getTime()
|
|
self.MessageText=MessageText:gsub("^\n","",1):gsub("\n$","",1)
|
|
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=self.MessageType..": "
|
|
end
|
|
if self.MessageDuration~=0 then
|
|
local ClientGroupID=Client:GetClientGroupID()
|
|
self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
|
|
trigger.action.outTextForGroup(ClientGroupID,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration)
|
|
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=self.MessageType..": "
|
|
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)
|
|
end
|
|
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=self.MessageType..": "
|
|
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.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration)
|
|
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()
|
|
self:F()
|
|
if self.MessageType then
|
|
local Settings=Settings or _SETTINGS
|
|
self.MessageDuration=Settings:GetMessageTime(self.MessageType)
|
|
self.MessageCategory=self.MessageType..": "
|
|
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)
|
|
end
|
|
return self
|
|
end
|
|
function MESSAGE:ToAllIf(Condition)
|
|
if Condition and Condition==true then
|
|
self:ToAll()
|
|
end
|
|
return self
|
|
end
|
|
do
|
|
FSM={
|
|
ClassName="FSM",
|
|
}
|
|
function FSM:New(FsmT)
|
|
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:T2(Transition)
|
|
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)
|
|
self:T({From,Event})
|
|
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()
|
|
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: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
|
|
self:T(Process._Scores)
|
|
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)
|
|
self:T2("Added methods: "..Event..", "..__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(handler,params,EventName)
|
|
local ErrorHandler=function(errmsg)
|
|
env.info("Error in SCHEDULER function:"..errmsg)
|
|
if debug~=nil then
|
|
env.info(debug.traceback())
|
|
end
|
|
return errmsg
|
|
end
|
|
if self[handler]then
|
|
self:T2("Calling "..handler)
|
|
self._EventSchedules[EventName]=nil
|
|
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.Controllable then
|
|
self:T("FSM Transition for "..self.Controllable.ControllableName.." :"..self.current.." --> "..EventName.." --> "..to)
|
|
else
|
|
self:T("FSM Transition:"..self.current.." --> "..EventName.." --> "..to)
|
|
end
|
|
if(self:_call_handler("onbefore"..EventName,params,EventName)==false)
|
|
or(self:_call_handler("OnBefore"..EventName,params,EventName)==false)
|
|
or(self:_call_handler("onleave"..from,params,EventName)==false)
|
|
or(self:_call_handler("OnLeave"..from,params,EventName)==false)then
|
|
self:T("Cancel Transition")
|
|
return false
|
|
end
|
|
self.current=to
|
|
local execute=true
|
|
local subtable=self:_gosub(from,EventName)
|
|
for _,sub in pairs(subtable)do
|
|
self:T("calling sub start event: "..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:F2({"end state: ",fsmparent,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("onstatechange",params,EventName)
|
|
fsmparent[Event](fsmparent)
|
|
execute=false
|
|
end
|
|
if execute then
|
|
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("onstatechange",params,EventName)
|
|
end
|
|
else
|
|
self:T("Cannot execute transition.")
|
|
self:T({From=self.current,Event=EventName,To=to,Can=Can})
|
|
end
|
|
return nil
|
|
end
|
|
function FSM:_delayed_transition(EventName)
|
|
return function(self,DelaySeconds,...)
|
|
self:T2("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)
|
|
self._EventSchedules[EventName]=CallID
|
|
else
|
|
end
|
|
else
|
|
CallID=self.CallScheduler:Schedule(self,self._handler,{EventName,...},DelaySeconds or 1)
|
|
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
|
|
self:T2({CallID=CallID})
|
|
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
|
|
self:T({ParentFrom,ParentEvent,self.subs[ParentFrom],self.subs[ParentFrom][ParentEvent]})
|
|
return self.subs[ParentFrom][ParentEvent]
|
|
else
|
|
return{}
|
|
end
|
|
end
|
|
function FSM:_isendstate(Current)
|
|
local FSMParent=self.fsmparent
|
|
if FSMParent and self.endstates[Current]then
|
|
self:T({state=Current,endstates=self.endstates,endstate=self.endstates[Current]})
|
|
FSMParent.current=Current
|
|
local ParentFrom=FSMParent.current
|
|
self:T(ParentFrom)
|
|
self:T(self.ReturnEvents)
|
|
local Event=self.ReturnEvents[Current]
|
|
self:T({ParentFrom,Event,self.ReturnEvents})
|
|
if Event then
|
|
return FSMParent,Event
|
|
else
|
|
self:T({"Could not find parent event name for state ",ParentFrom})
|
|
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
|
|
self:T3({Map,Event})
|
|
end
|
|
function FSM:GetState()
|
|
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]
|
|
self:F3({self.current,Event})
|
|
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(FSMT,Controllable)
|
|
local self=BASE:Inherit(self,FSM:New(FSMT))
|
|
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(handler,params,EventName)
|
|
local ErrorHandler=function(errmsg)
|
|
env.info("Error in SCHEDULER function:"..errmsg)
|
|
if debug~=nil then
|
|
env.info(debug.traceback())
|
|
end
|
|
return errmsg
|
|
end
|
|
if self[handler]then
|
|
self:F3("Calling "..handler)
|
|
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(handler,params,EventName)
|
|
local ErrorHandler=function(errmsg)
|
|
env.info("Error in FSM_PROCESS call handler:"..errmsg)
|
|
if debug~=nil then
|
|
env.info(debug.traceback())
|
|
end
|
|
return errmsg
|
|
end
|
|
if self[handler]then
|
|
self:F3("Calling "..handler)
|
|
self._EventSchedules[EventName]=nil
|
|
local Result,Value=xpcall(function()return self[handler](self,self.Controllable,self.Task,unpack(params))end,ErrorHandler)
|
|
return Value
|
|
end
|
|
end
|
|
function FSM_PROCESS:Copy(Controllable,Task)
|
|
self:T({self:GetClassNameAndID()})
|
|
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
|
|
self:T(EndState)
|
|
NewFsm:AddEndState(EndState)
|
|
end
|
|
for ScoreID,Score in pairs(self:GetScores())do
|
|
self:T(Score)
|
|
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:onenterAssigned(ProcessUnit)
|
|
self:T("Assign")
|
|
self.Task:Assign()
|
|
end
|
|
function FSM_PROCESS:onenterFailed(ProcessUnit)
|
|
self:T("Failed")
|
|
self.Task:Fail()
|
|
end
|
|
function FSM_PROCESS:onstatechange(ProcessUnit,Task,From,Event,To,Dummy)
|
|
self:T({ProcessUnit:GetName(),From,Event,To,Dummy,self:IsTrace()})
|
|
if self:IsTrace()then
|
|
end
|
|
self:T({Scores=self._Scores,To=To})
|
|
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(FSMT)
|
|
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New(FSMT))
|
|
self["onstatechange"]=self.OnStateChange
|
|
return self
|
|
end
|
|
function FSM_TASK:_call_handler(handler,params,EventName)
|
|
if self[handler]then
|
|
self:T("Calling "..handler)
|
|
self._EventSchedules[EventName]=nil
|
|
return self[handler](self,unpack(params))
|
|
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.Controllable
|
|
end
|
|
function FSM_SET:_call_handler(handler,params,EventName)
|
|
if self[handler]then
|
|
self:T("Calling "..handler)
|
|
self._EventSchedules[EventName]=nil
|
|
return self[handler](self,self.Set,unpack(params))
|
|
end
|
|
end
|
|
end
|
|
RADIO={
|
|
ClassName="RADIO",
|
|
FileName="",
|
|
Frequency=0,
|
|
Modulation=radio.modulation.AM,
|
|
Subtitle="",
|
|
SubtitleDuration=0,
|
|
Power=100,
|
|
Loop=true,
|
|
}
|
|
function RADIO:New(Positionable)
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self.Loop=true
|
|
self:F(Positionable)
|
|
if Positionable:GetPointVec2()then
|
|
self.Positionable=Positionable
|
|
return self
|
|
end
|
|
self:E({"The passed positionable is invalid, no RADIO created",Positionable})
|
|
return nil
|
|
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 ?",self.FileName})
|
|
return self
|
|
end
|
|
function RADIO:SetFrequency(Frequency)
|
|
self:F2(Frequency)
|
|
if type(Frequency)=="number"then
|
|
if(Frequency>=30 and Frequency<88)or(Frequency>=108 and Frequency<152)or(Frequency>=225 and Frequency<400)then
|
|
self.Frequency=Frequency*1000000
|
|
if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then
|
|
self.Positionable:SetCommand({
|
|
id="SetFrequency",
|
|
params={
|
|
frequency=self.Frequency,
|
|
modulation=self.Modulation,
|
|
}
|
|
})
|
|
end
|
|
return self
|
|
end
|
|
end
|
|
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.",self.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))
|
|
return self
|
|
end
|
|
self:E({"Power is invalid. Power unchanged.",self.Power})
|
|
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
|
|
if math.floor(math.abs(SubtitleDuration))==SubtitleDuration then
|
|
self.SubtitleDuration=SubtitleDuration
|
|
return self
|
|
end
|
|
end
|
|
self.SubtitleDuration=0
|
|
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.",self.SubtitleDuration})
|
|
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 Subtitle then self:SetSubtitle(Subtitle)end
|
|
if SubtitleDuration then self:SetSubtitleDuration(SubtitleDuration)end
|
|
if Frequency then self:SetFrequency(Frequency)end
|
|
if Modulation then self:SetModulation(Modulation)end
|
|
if Loop then self:SetLoop(Loop)end
|
|
return self
|
|
end
|
|
function RADIO:Broadcast()
|
|
self:F()
|
|
if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then
|
|
self:T2("Broadcasting from a UNIT or a GROUP")
|
|
self.Positionable:SetCommand({
|
|
id="TransmitMessage",
|
|
params={
|
|
file=self.FileName,
|
|
duration=self.SubtitleDuration,
|
|
subtitle=self.Subtitle,
|
|
loop=self.Loop,
|
|
}
|
|
})
|
|
else
|
|
self:T2("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
|
|
self.Positionable:SetCommand({
|
|
id="StopTransmission",
|
|
params={}
|
|
})
|
|
else
|
|
trigger.action.stopRadioTransmission(tostring(self.ID))
|
|
end
|
|
return self
|
|
end
|
|
BEACON={
|
|
ClassName="BEACON",
|
|
}
|
|
function BEACON:New(Positionable)
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self:F(Positionable)
|
|
if Positionable:GetPointVec2()then
|
|
self.Positionable=Positionable
|
|
return self
|
|
end
|
|
self:E({"The passed positionable is invalid, no BEACON created",Positionable})
|
|
return nil
|
|
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
|
|
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=5
|
|
else
|
|
System=14
|
|
end
|
|
if IsValid then
|
|
self:T2({"AA TACAN BEACON started !"})
|
|
self.Positionable:SetCommand({
|
|
id="ActivateBeacon",
|
|
params={
|
|
type=4,
|
|
system=System,
|
|
callsign=Message,
|
|
frequency=Frequency,
|
|
}
|
|
})
|
|
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))
|
|
end
|
|
SPAWNSTATIC={
|
|
ClassName="SPAWNSTATIC",
|
|
}
|
|
function SPAWNSTATIC:NewFromStatic(SpawnTemplatePrefix,CountryID)
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self:F({SpawnTemplatePrefix})
|
|
local TemplateStatic=StaticObject.getByName(SpawnTemplatePrefix)
|
|
if TemplateStatic then
|
|
self.SpawnTemplatePrefix=SpawnTemplatePrefix
|
|
self.CountryID=CountryID
|
|
self.SpawnIndex=0
|
|
else
|
|
error("SPAWNSTATIC:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'")
|
|
end
|
|
self:SetEventPriority(5)
|
|
return self
|
|
end
|
|
function SPAWNSTATIC:NewFromType(SpawnTypeName,SpawnShapeName,SpawnCategory,CountryID)
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self:F({SpawnTypeName})
|
|
self.SpawnTypeName=SpawnTypeName
|
|
self.CountryID=CountryID
|
|
self.SpawnIndex=0
|
|
self:SetEventPriority(5)
|
|
return self
|
|
end
|
|
function SPAWNSTATIC:SpawnFromPointVec2(PointVec2,Heading,NewName)
|
|
self:F({PointVec2,Heading,NewName})
|
|
local CountryName=_DATABASE.COUNTRY_NAME[self.CountryID]
|
|
local StaticTemplate=_DATABASE:GetStaticUnitTemplate(self.SpawnTemplatePrefix)
|
|
StaticTemplate.x=PointVec2:GetLat()
|
|
StaticTemplate.y=PointVec2:GetLon()
|
|
StaticTemplate.name=NewName or string.format("%s#%05d",self.SpawnTemplatePrefix,self.SpawnIndex)
|
|
StaticTemplate.heading=(Heading/180)*math.pi
|
|
StaticTemplate.CountryID=nil
|
|
StaticTemplate.CoalitionID=nil
|
|
StaticTemplate.CategoryID=nil
|
|
local Static=coalition.addStaticObject(self.CountryID,StaticTemplate)
|
|
self.SpawnIndex=self.SpawnIndex+1
|
|
return Static
|
|
end
|
|
function SPAWNSTATIC:SpawnFromZone(Zone,Heading,NewName)
|
|
self:F({Zone,Heading,NewName})
|
|
local Static=self:SpawnFromPointVec2(Zone:GetPointVec2(),Heading,NewName)
|
|
return Static
|
|
end
|
|
CARGOS={}
|
|
do
|
|
CARGO={
|
|
ClassName="CARGO",
|
|
Type=nil,
|
|
Name=nil,
|
|
Weight=nil,
|
|
CargoObject=nil,
|
|
CargoCarrier=nil,
|
|
Representable=false,
|
|
Slingloadable=false,
|
|
Moveable=false,
|
|
Containable=false,
|
|
}
|
|
function CARGO:New(Type,Name,Weight)
|
|
local self=BASE:Inherit(self,FSM:New())
|
|
self:F({Type,Name,Weight})
|
|
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.Type=Type
|
|
self.Name=Name
|
|
self.Weight=Weight
|
|
self.CargoObject=nil
|
|
self.CargoCarrier=nil
|
|
self.Representable=false
|
|
self.Slingloadable=false
|
|
self.Moveable=false
|
|
self.Containable=false
|
|
self:SetDeployed(false)
|
|
self.CargoScheduler=SCHEDULER:New()
|
|
CARGOS[self.Name]=self
|
|
return self
|
|
end
|
|
function CARGO:Destroy()
|
|
if self.CargoObject then
|
|
self.CargoObject:Destroy()
|
|
end
|
|
self:Destroyed()
|
|
end
|
|
function CARGO:GetName()
|
|
return self.Name
|
|
end
|
|
function CARGO:GetObjectName()
|
|
if self:IsLoaded()then
|
|
return self.CargoCarrier:GetName()
|
|
else
|
|
return self.CargoObject:GetName()
|
|
end
|
|
end
|
|
function CARGO:GetType()
|
|
return self.Type
|
|
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:IsUnLoaded()
|
|
return self:Is("UnLoaded")
|
|
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,Range)
|
|
self:F2()
|
|
if self:IsUnLoaded()then
|
|
if Range then
|
|
trigger.action.smoke(self.CargoObject:GetRandomVec3(Range),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:IsInZone(Zone)
|
|
self:F({Zone})
|
|
if self:IsLoaded()then
|
|
return Zone:IsPointVec2InZone(self.CargoCarrier:GetPointVec2())
|
|
else
|
|
self:F({Size=self.CargoObject:GetSize(),Units=self.CargoObject:GetUnits()})
|
|
if self.CargoObject:GetSize()~=0 then
|
|
return Zone:IsPointVec2InZone(self.CargoObject:GetPointVec2())
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
function CARGO:IsNear(PointVec2,NearRadius)
|
|
self:F({PointVec2,NearRadius})
|
|
local Distance=PointVec2:DistanceFromPointVec2(self.CargoObject:GetPointVec2())
|
|
self:T(Distance)
|
|
if Distance<=NearRadius then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
function CARGO:GetPointVec2()
|
|
return self.CargoObject:GetPointVec2()
|
|
end
|
|
function CARGO:GetCoordinate()
|
|
return self.CargoObject:GetCoordinate()
|
|
end
|
|
function CARGO:SetWeight(Weight)
|
|
self.Weight=Weight
|
|
return self
|
|
end
|
|
end
|
|
do
|
|
CARGO_REPRESENTABLE={
|
|
ClassName="CARGO_REPRESENTABLE"
|
|
}
|
|
function CARGO_REPRESENTABLE:New(CargoObject,Type,Name,Weight,ReportRadius,NearRadius)
|
|
local self=BASE:Inherit(self,CARGO:New(Type,Name,Weight,ReportRadius,NearRadius))
|
|
self:F({Type,Name,Weight,ReportRadius,NearRadius})
|
|
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
|
|
end
|
|
do
|
|
CARGO_REPORTABLE={
|
|
ClassName="CARGO_REPORTABLE"
|
|
}
|
|
function CARGO_REPORTABLE:New(CargoObject,Type,Name,Weight,ReportRadius)
|
|
local self=BASE:Inherit(self,CARGO:New(Type,Name,Weight))
|
|
self:F({Type,Name,Weight,ReportRadius})
|
|
self.CargoSet=SET_CARGO:New()
|
|
self.ReportRadius=ReportRadius or 1000
|
|
self.CargoObject=CargoObject
|
|
return self
|
|
end
|
|
function CARGO_REPORTABLE:IsInRadius(PointVec2)
|
|
self:F({PointVec2})
|
|
local Distance=0
|
|
if self:IsLoaded()then
|
|
Distance=PointVec2:DistanceFromPointVec2(self.CargoCarrier:GetPointVec2())
|
|
else
|
|
Distance=PointVec2:DistanceFromPointVec2(self.CargoObject:GetPointVec2())
|
|
end
|
|
self:T(Distance)
|
|
if Distance<=self.ReportRadius then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
function CARGO_REPORTABLE:MessageToGroup(Message,TaskGroup,Name)
|
|
local Prefix=Name and"@ "..Name..": "or"@ "..TaskGroup:GetCallsign()..": "
|
|
Message=Prefix..Message
|
|
MESSAGE:New(Message,20,"Cargo: "..self:GetName()):ToGroup(TaskGroup)
|
|
end
|
|
function CARGO_REPORTABLE:GetBoardingRange()
|
|
return self.ReportRadius
|
|
end
|
|
function CARGO_REPORTABLE:Respawn()
|
|
self:F({"Respawning"})
|
|
for CargoID,CargoData in pairs(self.CargoSet:GetSet())do
|
|
local Cargo=CargoData
|
|
Cargo:Destroy()
|
|
Cargo:SetStartState("UnLoaded")
|
|
end
|
|
local CargoObject=self.CargoObject
|
|
CargoObject:Destroy()
|
|
local Template=CargoObject:GetTemplate()
|
|
CargoObject:Respawn(Template)
|
|
self:SetDeployed(false)
|
|
local WeightGroup=0
|
|
self:SetStartState("UnLoaded")
|
|
end
|
|
end
|
|
do
|
|
CARGO_UNIT={
|
|
ClassName="CARGO_UNIT"
|
|
}
|
|
function CARGO_UNIT:New(CargoUnit,Type,Name,Weight,NearRadius)
|
|
local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoUnit,Type,Name,Weight,NearRadius))
|
|
self:F({Type,Name,Weight,NearRadius})
|
|
self:T(CargoUnit)
|
|
self.CargoObject=CargoUnit
|
|
self:T(self.ClassName)
|
|
self:SetEventPriority(5)
|
|
return self
|
|
end
|
|
function CARGO_UNIT:Destroy()
|
|
self:F({CargoName=self:GetName()})
|
|
_EVENTDISPATCHER:CreateEventDeleteCargo(self)
|
|
return self
|
|
end
|
|
function CARGO_UNIT:onenterUnBoarding(From,Event,To,ToPointVec2,NearRadius)
|
|
self:F({From,Event,To,ToPointVec2,NearRadius})
|
|
NearRadius=NearRadius or 25
|
|
local Angle=180
|
|
local Speed=60
|
|
local DeployDistance=9
|
|
local RouteDistance=60
|
|
if From=="Loaded"then
|
|
local CargoCarrier=self.CargoCarrier
|
|
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)
|
|
ToPointVec2=ToPointVec2 or CargoRoutePointVec2
|
|
local DirectionVec3=CargoCarrierPointVec2:GetDirectionVec3(ToPointVec2)
|
|
local Angle=CargoCarrierPointVec2:GetAngleDegrees(DirectionVec3)
|
|
local CargoDeployPointVec2=CargoCarrierPointVec2:Translate(DeployDistance,Angle)
|
|
local FromPointVec2=CargoCarrierPointVec2
|
|
if self.CargoObject then
|
|
self.CargoObject:ReSpawn(CargoDeployPointVec2:GetVec3(),CargoDeployHeading)
|
|
self:F({"CargoUnits:",self.CargoObject:GetGroup():GetName()})
|
|
self.CargoCarrier=nil
|
|
local Points={}
|
|
Points[#Points+1]=CargoCarrierPointVec2:WaypointGround(Speed)
|
|
Points[#Points+1]=ToPointVec2:WaypointGround(Speed)
|
|
local TaskRoute=self.CargoObject:TaskRoute(Points)
|
|
self.CargoObject:SetTask(TaskRoute,1)
|
|
self:__UnBoarding(1,ToPointVec2,NearRadius)
|
|
end
|
|
end
|
|
end
|
|
function CARGO_UNIT:onleaveUnBoarding(From,Event,To,ToPointVec2,NearRadius)
|
|
self:F({From,Event,To,ToPointVec2,NearRadius})
|
|
NearRadius=NearRadius or 25
|
|
local Angle=180
|
|
local Speed=10
|
|
local Distance=5
|
|
if From=="UnBoarding"then
|
|
if self:IsNear(ToPointVec2,NearRadius)then
|
|
return true
|
|
else
|
|
self:__UnBoarding(1,ToPointVec2,NearRadius)
|
|
end
|
|
return false
|
|
end
|
|
end
|
|
function CARGO_UNIT:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius)
|
|
self:F({From,Event,To,ToPointVec2,NearRadius})
|
|
NearRadius=NearRadius or 25
|
|
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 CargoDeployPointVec2=StartPointVec2:Translate(Distance,CargoDeployHeading)
|
|
ToPointVec2=ToPointVec2 or POINT_VEC2:New(CargoDeployPointVec2:GetX(),CargoDeployPointVec2:GetY())
|
|
if self.CargoObject then
|
|
self.CargoObject:ReSpawn(ToPointVec2:GetVec3(),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})
|
|
local NearRadius=NearRadius or 25
|
|
self.CargoInAir=self.CargoObject:InAir()
|
|
self:T(self.CargoInAir)
|
|
if not self.CargoInAir then
|
|
if self:IsNear(CargoCarrier:GetPointVec2(),NearRadius)then
|
|
self:Load(CargoCarrier,NearRadius,...)
|
|
else
|
|
local Speed=90
|
|
local Angle=180
|
|
local Distance=5
|
|
NearRadius=NearRadius or 25
|
|
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)
|
|
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(-1,CargoCarrier,NearRadius)
|
|
self.RunCount=0
|
|
end
|
|
end
|
|
end
|
|
function CARGO_UNIT:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...)
|
|
self:F({From,Event,To,CargoCarrier.UnitName,NearRadius})
|
|
if CargoCarrier and CargoCarrier:IsAlive()then
|
|
if CargoCarrier:InAir()==false then
|
|
if self:IsNear(CargoCarrier:GetPointVec2(),NearRadius)then
|
|
self:__Load(1,CargoCarrier,...)
|
|
else
|
|
self:__Boarding(-1,CargoCarrier,NearRadius,...)
|
|
self.RunCount=self.RunCount+1
|
|
if self.RunCount>=20 then
|
|
self.RunCount=0
|
|
local Speed=90
|
|
local Angle=180
|
|
local Distance=5
|
|
NearRadius=NearRadius or 25
|
|
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)
|
|
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,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:onenterBoarding(From,Event,To,CargoCarrier,NearRadius,...)
|
|
self:F({From,Event,To,CargoCarrier.UnitName,NearRadius})
|
|
local Speed=90
|
|
local Angle=180
|
|
local Distance=5
|
|
local NearRadius=NearRadius or 25
|
|
if From=="UnLoaded"or From=="Boarding"then
|
|
end
|
|
end
|
|
function CARGO_UNIT:onenterLoaded(From,Event,To,CargoCarrier)
|
|
self:F({From,Event,To,CargoCarrier})
|
|
self.CargoCarrier=CargoCarrier
|
|
if self.CargoObject then
|
|
self:T("Destroying")
|
|
self.CargoObject:Destroy()
|
|
end
|
|
end
|
|
end
|
|
do
|
|
CARGO_GROUP={
|
|
ClassName="CARGO_GROUP",
|
|
}
|
|
function CARGO_GROUP:New(CargoGroup,Type,Name,ReportRadius)
|
|
local self=BASE:Inherit(self,CARGO_REPORTABLE:New(CargoGroup,Type,Name,0,ReportRadius))
|
|
self:F({Type,Name,ReportRadius})
|
|
self.CargoObject=CargoGroup
|
|
self:SetDeployed(false)
|
|
self.CargoGroup=CargoGroup
|
|
local WeightGroup=0
|
|
for UnitID,UnitData in pairs(CargoGroup:GetUnits())do
|
|
local Unit=UnitData
|
|
local WeightUnit=Unit:GetDesc().massEmpty
|
|
WeightGroup=WeightGroup+WeightUnit
|
|
local CargoUnit=CARGO_UNIT:New(Unit,Type,Unit:GetName(),WeightUnit)
|
|
self.CargoSet:Add(CargoUnit:GetName(),CargoUnit)
|
|
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:OnEventCargoDead(EventData)
|
|
local Destroyed=false
|
|
if self:IsDestroyed()or self:IsUnLoaded()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:onenterBoarding(From,Event,To,CargoCarrier,NearRadius,...)
|
|
self:F({CargoCarrier.UnitName,From,Event,To})
|
|
local NearRadius=NearRadius or 25
|
|
if From=="UnLoaded"then
|
|
self.CargoSet:ForEach(
|
|
function(Cargo,...)
|
|
Cargo:__Board(1,CargoCarrier,NearRadius,...)
|
|
end,...
|
|
)
|
|
self:__Boarding(1,CargoCarrier,NearRadius,...)
|
|
end
|
|
end
|
|
function CARGO_GROUP:onenterLoaded(From,Event,To,CargoCarrier,...)
|
|
self:F({From,Event,To,CargoCarrier,...})
|
|
if From=="UnLoaded"then
|
|
for CargoID,Cargo in pairs(self.CargoSet:GetSet())do
|
|
Cargo:Load(CargoCarrier)
|
|
end
|
|
end
|
|
self.CargoCarrier=CargoCarrier
|
|
end
|
|
function CARGO_GROUP:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...)
|
|
self:F({CargoCarrier.UnitName,From,Event,To})
|
|
local NearRadius=NearRadius or 25
|
|
local Boarded=true
|
|
local Cancelled=false
|
|
local Dead=true
|
|
self.CargoSet:Flush()
|
|
for CargoID,Cargo in pairs(self.CargoSet:GetSet())do
|
|
self:T({Cargo:GetName(),Cargo.current})
|
|
if not Cargo:is("Loaded")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(1,CargoCarrier,NearRadius,...)
|
|
else
|
|
self:__Load(1,CargoCarrier,...)
|
|
end
|
|
else
|
|
self:__CancelBoarding(1,CargoCarrier,NearRadius,...)
|
|
end
|
|
else
|
|
self:__Destroyed(1,CargoCarrier,NearRadius,...)
|
|
end
|
|
end
|
|
function CARGO_GROUP:GetCount()
|
|
return self.CargoSet:Count()
|
|
end
|
|
function CARGO_GROUP:onenterUnBoarding(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)
|
|
Cargo:__UnBoard(Timer,ToPointVec2,NearRadius)
|
|
Timer=Timer+10
|
|
end,{NearRadius}
|
|
)
|
|
self:__UnBoarding(1,ToPointVec2,NearRadius,...)
|
|
end
|
|
end
|
|
function CARGO_GROUP: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
|
|
local UnBoarded=true
|
|
for CargoID,Cargo in pairs(self.CargoSet:GetSet())do
|
|
self:T(Cargo.current)
|
|
if not Cargo:is("UnLoaded")then
|
|
UnBoarded=false
|
|
end
|
|
end
|
|
if UnBoarded then
|
|
return true
|
|
else
|
|
self:__UnBoarding(1,ToPointVec2,NearRadius,...)
|
|
end
|
|
return false
|
|
end
|
|
end
|
|
function CARGO_GROUP:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius,...)
|
|
self:F({From,Event,To,ToPointVec2,NearRadius})
|
|
self:__UnLoad(1,ToPointVec2,...)
|
|
end
|
|
function CARGO_GROUP:onenterUnLoaded(From,Event,To,ToPointVec2,...)
|
|
self:F({From,Event,To,ToPointVec2})
|
|
if From=="Loaded"then
|
|
self.CargoSet:ForEach(
|
|
function(Cargo)
|
|
Cargo:UnLoad(ToPointVec2)
|
|
end
|
|
)
|
|
end
|
|
end
|
|
function CARGO_GROUP:RespawnOnDestroyed(RespawnDestroyed)
|
|
self:F({"In function RespawnOnDestroyed"})
|
|
if RespawnDestroyed then
|
|
self.onenterDestroyed=function(self)
|
|
self:F("IN FUNCTION")
|
|
self:Respawn()
|
|
end
|
|
else
|
|
self.onenterDestroyed=nil
|
|
end
|
|
end
|
|
end
|
|
do
|
|
CARGO_PACKAGE={
|
|
ClassName="CARGO_PACKAGE"
|
|
}
|
|
function CARGO_PACKAGE:New(CargoCarrier,Type,Name,Weight,ReportRadius,NearRadius)
|
|
local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoCarrier,Type,Name,Weight,ReportRadius,NearRadius))
|
|
self:F({Type,Name,Weight,ReportRadius,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:GetPointVec2()
|
|
local Distance=CargoCarrierPoint:DistanceFromPointVec2(self.CargoCarrier:GetPointVec2())
|
|
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
|
|
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("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:E({"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()
|
|
self.SpotIR=Spot.createInfraRed(RecceDcsUnit,{x=0,y=2,z=0},Target:GetPointVec3():AddY(1):GetVec3())
|
|
self.SpotLaser=Spot.createLaser(RecceDcsUnit,{x=0,y=2,z=0},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:OnEventDead(EventData)
|
|
self:E({Dead=EventData.IniDCSUnitName,Target=self.Target})
|
|
if self.Target then
|
|
if EventData.IniDCSUnitName==self.Target:GetName()then
|
|
self:E({"Target dead ",self.Target:GetName()})
|
|
self:Destroyed()
|
|
self:LaseOff()
|
|
end
|
|
end
|
|
end
|
|
function SPOT:onafterLasing(From,Event,To)
|
|
if 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)
|
|
else
|
|
self:E({"Target is not alive",self.Target:IsAlive()})
|
|
end
|
|
end
|
|
function SPOT:onafterLaseOff(From,Event,To)
|
|
self:E({"Stopped lasing for ",self.Target:GetName(),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
|
|
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()
|
|
self:F2(self.ObjectName)
|
|
local DCSObject=self:GetDCSObject()
|
|
if DCSObject then
|
|
local ObjectID=DCSObject:getID()
|
|
return ObjectID
|
|
end
|
|
return nil
|
|
end
|
|
function OBJECT:Destroy()
|
|
self:F2(self.ObjectName)
|
|
local DCSObject=self:GetDCSObject()
|
|
if DCSObject then
|
|
DCSObject:destroy()
|
|
end
|
|
return nil
|
|
end
|
|
IDENTIFIABLE={
|
|
ClassName="IDENTIFIABLE",
|
|
IdentifiableName="",
|
|
}
|
|
local _CategoryName={
|
|
[Unit.Category.AIRPLANE]="Airplane",
|
|
[Unit.Category.HELICOPTER]="Helicoper",
|
|
[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()
|
|
self:F2(self.IdentifiableName)
|
|
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:E(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:E(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:E(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:E(self.ClassName.." "..self.IdentifiableName.." not found!")
|
|
return nil
|
|
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:E(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="",
|
|
}
|
|
POSITIONABLE.__={}
|
|
POSITIONABLE.__.Cargo={}
|
|
function POSITIONABLE:New(PositionableName)
|
|
local self=BASE:Inherit(self,IDENTIFIABLE:New(PositionableName))
|
|
self.PositionableName=PositionableName
|
|
return self
|
|
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
|
|
return nil
|
|
end
|
|
function POSITIONABLE:GetVec2()
|
|
self:F2(self.PositionableName)
|
|
local DCSPositionable=self:GetDCSObject()
|
|
if DCSPositionable then
|
|
local PositionableVec3=DCSPositionable:getPosition().p
|
|
local PositionableVec2={}
|
|
PositionableVec2.x=PositionableVec3.x
|
|
PositionableVec2.y=PositionableVec3.z
|
|
self:T2(PositionableVec2)
|
|
return PositionableVec2
|
|
end
|
|
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)
|
|
self:T2(PositionablePointVec2)
|
|
return PositionablePointVec2
|
|
end
|
|
return nil
|
|
end
|
|
function POSITIONABLE:GetPointVec3()
|
|
self:F2(self.PositionableName)
|
|
local DCSPositionable=self:GetDCSObject()
|
|
if DCSPositionable then
|
|
local PositionableVec3=self:GetPositionVec3()
|
|
local PositionablePointVec3=POINT_VEC3:NewFromVec3(PositionableVec3)
|
|
self:T2(PositionablePointVec3)
|
|
return PositionablePointVec3
|
|
end
|
|
return nil
|
|
end
|
|
function POSITIONABLE:GetCoordinate()
|
|
self:F2(self.PositionableName)
|
|
local DCSPositionable=self:GetDCSObject()
|
|
if DCSPositionable then
|
|
local PositionableVec3=self:GetPositionVec3()
|
|
local PositionableCoordinate=COORDINATE:NewFromVec3(PositionableVec3)
|
|
PositionableCoordinate:SetHeading(self:GetHeading())
|
|
PositionableCoordinate:SetVelocity(self:GetVelocityMPS())
|
|
self:T2(PositionableCoordinate)
|
|
return PositionableCoordinate
|
|
end
|
|
return nil
|
|
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:E("Radius is nil, returning the PointVec3 of the POSITIONABLE",PositionablePointVec3)
|
|
return PositionablePointVec3
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
function POSITIONABLE:GetVec3()
|
|
self:F2(self.PositionableName)
|
|
local DCSPositionable=self:GetDCSObject()
|
|
if DCSPositionable then
|
|
local PositionableVec3=DCSPositionable:getPosition().p
|
|
self:T3(PositionableVec3)
|
|
return PositionableVec3
|
|
end
|
|
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
|
|
return nil
|
|
end
|
|
function POSITIONABLE:GetAltitude()
|
|
self:F2()
|
|
local DCSPositionable=self:GetDCSObject()
|
|
if DCSPositionable then
|
|
local PositionablePointVec3=DCSPositionable:getPoint()
|
|
return PositionablePointVec3.y
|
|
end
|
|
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
|
|
return nil
|
|
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
|
|
self:T2(PositionableHeading)
|
|
return PositionableHeading
|
|
end
|
|
end
|
|
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 PositionableVelocityVec3=DCSPositionable:getVelocity()
|
|
self:T3(PositionableVelocityVec3)
|
|
return PositionableVelocityVec3
|
|
end
|
|
return nil
|
|
end
|
|
function POSITIONABLE:GetHeight()
|
|
self:F2(self.PositionableName)
|
|
local DCSPositionable=self:GetDCSObject()
|
|
if DCSPositionable 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 then
|
|
local VelocityVec3=self:GetVelocity()
|
|
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 then
|
|
local VelocityVec3=self:GetVelocity()
|
|
local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5
|
|
self:T3(Velocity)
|
|
return Velocity
|
|
end
|
|
return 0
|
|
end
|
|
function POSITIONABLE:GetMessageText(Message,Name)
|
|
local DCSObject=self:GetDCSObject()
|
|
if DCSObject then
|
|
Name=Name and(" ("..Name..")")or""
|
|
local Callsign=string.format("[%s]",self:GetCallsign()~=""and self:GetCallsign()or self:GetName())
|
|
local MessageText=Callsign..Name..": "..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)
|
|
self:F2({Message,Duration})
|
|
local Name=""
|
|
local DCSObject=self:GetDCSObject()
|
|
if DCSObject then
|
|
if MessageCoalition==coalition.side.BLUE then
|
|
Name="Blue coalition"
|
|
end
|
|
if MessageCoalition==coalition.side.RED then
|
|
Name="Red coalition"
|
|
end
|
|
self:GetMessage(Message,Duration,Name):ToCoalition(MessageCoalition)
|
|
end
|
|
return nil
|
|
end
|
|
function POSITIONABLE:MessageTypeToCoalition(Message,MessageType,MessageCoalition)
|
|
self:F2({Message,MessageType})
|
|
local Name=""
|
|
local DCSObject=self:GetDCSObject()
|
|
if DCSObject then
|
|
if MessageCoalition==coalition.side.BLUE then
|
|
Name="Blue coalition"
|
|
end
|
|
if MessageCoalition==coalition.side.RED then
|
|
Name="Red coalition"
|
|
end
|
|
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:MessageToGroup(Message,Duration,MessageGroup,Name)
|
|
self:F2({Message,Duration})
|
|
local DCSObject=self:GetDCSObject()
|
|
if DCSObject then
|
|
if DCSObject:isExist()then
|
|
self:GetMessage(Message,Duration,Name):ToGroup(MessageGroup)
|
|
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:ForEachGroup(
|
|
function(MessageGroup)
|
|
self:GetMessage(Message,Duration,Name):ToGroup(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:E("bulding spot")
|
|
self.Spot=SPOT:New(self)
|
|
self.Spot:LaseOn(Target,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
|
|
function POSITIONABLE:AddCargo(Cargo)
|
|
self.__.Cargo[Cargo]=Cargo
|
|
return self
|
|
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:CargoItemCount()
|
|
local ItemCount=0
|
|
for CargoName,Cargo in pairs(self.__.Cargo)do
|
|
ItemCount=ItemCount+Cargo:GetCount()
|
|
end
|
|
return ItemCount
|
|
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
|
|
CONTROLLABLE={
|
|
ClassName="CONTROLLABLE",
|
|
ControllableName="",
|
|
WayPointFunctions={},
|
|
}
|
|
function CONTROLLABLE:New(ControllableName)
|
|
local self=BASE:Inherit(self,POSITIONABLE:New(ControllableName))
|
|
self:F2(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:GetUnits()
|
|
self:F2({self.ControllableName})
|
|
local DCSControllable=self:GetDCSObject()
|
|
if DCSControllable then
|
|
local DCSUnits=DCSControllable:getUnits()
|
|
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 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:GetFuel()
|
|
self:F(self.ControllableName)
|
|
return nil
|
|
end
|
|
function CONTROLLABLE:ClearTasks()
|
|
self:F2()
|
|
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 Controller=self:_GetController()
|
|
if WaitTime then
|
|
self.TaskScheduler:Schedule(Controller,Controller.pushTask,{DCSTask},WaitTime)
|
|
else
|
|
Controller:pushTask(DCSTask)
|
|
end
|
|
return self
|
|
end
|
|
return nil
|
|
end
|
|
function CONTROLLABLE:SetTask(DCSTask,WaitTime)
|
|
self:F2({DCSTask=DCSTask})
|
|
local DCSControllable=self:GetDCSObject()
|
|
if DCSControllable then
|
|
local DCSControllableName=self:GetName()
|
|
local function SetTask(Controller,DCSTask)
|
|
if self and self:IsAlive()then
|
|
local Controller=self:_GetController()
|
|
Controller:setTask(DCSTask)
|
|
else
|
|
BASE:E(DCSControllableName.." is not alive anymore. Cannot set DCSTask "..DCSTask)
|
|
end
|
|
end
|
|
if not WaitTime or WaitTime==0 then
|
|
SetTask(self,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)
|
|
self:F2({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
|
|
self:T3({DCSStopCondition})
|
|
return DCSStopCondition
|
|
end
|
|
function CONTROLLABLE:TaskControlled(DCSTask,DCSStopCondition)
|
|
self:F2({DCSTask,DCSStopCondition})
|
|
local DCSTaskControlled
|
|
DCSTaskControlled={
|
|
id='ControlledTask',
|
|
params={
|
|
task=DCSTask,
|
|
stopCondition=DCSStopCondition
|
|
}
|
|
}
|
|
self:T3({DCSTaskControlled})
|
|
return DCSTaskControlled
|
|
end
|
|
function CONTROLLABLE:TaskCombo(DCSTasks)
|
|
self:F2({DCSTasks})
|
|
local DCSTaskCombo
|
|
DCSTaskCombo={
|
|
id='ComboTask',
|
|
params={
|
|
tasks=DCSTasks
|
|
}
|
|
}
|
|
for TaskID,Task in ipairs(DCSTasks)do
|
|
self:T(Task)
|
|
end
|
|
self:T3({DCSTaskCombo})
|
|
return DCSTaskCombo
|
|
end
|
|
function CONTROLLABLE:TaskWrappedAction(DCSCommand,Index)
|
|
self:F2({DCSCommand})
|
|
local DCSTaskWrappedAction
|
|
DCSTaskWrappedAction={
|
|
id="WrappedAction",
|
|
enabled=true,
|
|
number=Index or 1,
|
|
auto=false,
|
|
params={
|
|
action=DCSCommand,
|
|
},
|
|
}
|
|
self:T3({DCSTaskWrappedAction})
|
|
return DCSTaskWrappedAction
|
|
end
|
|
function CONTROLLABLE:SetTaskWaypoint(Waypoint,Task)
|
|
Waypoint.task=self:TaskCombo({Task})
|
|
self:T3({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:TaskAttackGroup(AttackGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit)
|
|
self:F2({self.ControllableName,AttackGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit})
|
|
local DirectionEnabled=nil
|
|
if Direction then
|
|
DirectionEnabled=true
|
|
end
|
|
local AltitudeEnabled=nil
|
|
if Altitude then
|
|
AltitudeEnabled=true
|
|
end
|
|
local DCSTask
|
|
DCSTask={id='AttackGroup',
|
|
params={
|
|
groupId=AttackGroup:GetID(),
|
|
weaponType=WeaponType,
|
|
expend=WeaponExpend,
|
|
attackQty=AttackQty,
|
|
directionEnabled=DirectionEnabled,
|
|
direction=Direction,
|
|
altitudeEnabled=AltitudeEnabled,
|
|
altitude=Altitude,
|
|
attackQtyLimit=AttackQtyLimit,
|
|
},
|
|
},
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskAttackUnit(AttackUnit,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,Visible,WeaponType)
|
|
self:F2({self.ControllableName,AttackUnit,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,Visible,WeaponType})
|
|
local DCSTask
|
|
DCSTask={
|
|
id='AttackUnit',
|
|
params={
|
|
unitId=AttackUnit:GetID(),
|
|
groupAttack=GroupAttack or false,
|
|
visible=Visible or false,
|
|
expend=WeaponExpend or"Auto",
|
|
directionEnabled=Direction and true or false,
|
|
direction=Direction,
|
|
altitudeEnabled=Altitude and true or false,
|
|
altitude=Altitude or 30,
|
|
attackQtyLimit=AttackQty and true or false,
|
|
attackQty=AttackQty,
|
|
weaponType=WeaponType
|
|
}
|
|
}
|
|
self:T3(DCSTask)
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskBombing(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType)
|
|
self:F2({self.ControllableName,Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType})
|
|
local DCSTask
|
|
DCSTask={
|
|
id='Bombing',
|
|
params={
|
|
point=Vec2,
|
|
groupAttack=GroupAttack or false,
|
|
expend=WeaponExpend or"Auto",
|
|
attackQtyLimit=AttackQty and true or false,
|
|
attackQty=AttackQty,
|
|
directionEnabled=Direction and true or false,
|
|
direction=Direction,
|
|
altitudeEnabled=Altitude and true or false,
|
|
altitude=Altitude or 30,
|
|
weaponType=WeaponType,
|
|
},
|
|
},
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskAttackMapObject(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType)
|
|
self:F2({self.ControllableName,Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType})
|
|
local DCSTask
|
|
DCSTask={
|
|
id='AttackMapObject',
|
|
params={
|
|
point=Vec2,
|
|
groupAttack=GroupAttack or false,
|
|
expend=WeaponExpend or"Auto",
|
|
attackQtyLimit=AttackQty and true or false,
|
|
attackQty=AttackQty,
|
|
directionEnabled=Direction and true or false,
|
|
direction=Direction,
|
|
altitudeEnabled=Altitude and true or false,
|
|
altitude=Altitude or 30,
|
|
weaponType=WeaponType,
|
|
},
|
|
},
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskOrbitCircleAtVec2(Point,Altitude,Speed)
|
|
self:F2({self.ControllableName,Point,Altitude,Speed})
|
|
local LandHeight=land.getHeight(Point)
|
|
self:T3({LandHeight})
|
|
local DCSTask={id='Orbit',
|
|
params={pattern=AI.Task.OrbitPattern.CIRCLE,
|
|
point=Point,
|
|
speed=Speed,
|
|
altitude=Altitude+LandHeight
|
|
}
|
|
}
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskOrbitCircle(Altitude,Speed)
|
|
self:F2({self.ControllableName,Altitude,Speed})
|
|
local DCSControllable=self:GetDCSObject()
|
|
if DCSControllable then
|
|
local ControllablePoint=self:GetVec2()
|
|
return self:TaskOrbitCircleAtVec2(ControllablePoint,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,ControllableAttack)
|
|
self:F2({self.ControllableName,Airbase,WeaponType,WeaponExpend,AttackQty,Direction,ControllableAttack})
|
|
local DCSTask
|
|
DCSTask={id='BombingRunway',
|
|
params={
|
|
point=Airbase:GetID(),
|
|
weaponType=WeaponType,
|
|
expend=WeaponExpend,
|
|
attackQty=AttackQty,
|
|
direction=Direction,
|
|
controllableAttack=ControllableAttack,
|
|
},
|
|
},
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskRefueling()
|
|
self:F2({self.ControllableName})
|
|
local DCSTask
|
|
DCSTask={id='Refueling',
|
|
params={
|
|
},
|
|
},
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskLandAtVec2(Point,Duration)
|
|
self:F2({self.ControllableName,Point,Duration})
|
|
local DCSTask
|
|
if Duration and Duration>0 then
|
|
DCSTask={id='Land',
|
|
params={
|
|
point=Point,
|
|
durationFlag=true,
|
|
duration=Duration,
|
|
},
|
|
}
|
|
else
|
|
DCSTask={id='Land',
|
|
params={
|
|
point=Point,
|
|
durationFlag=false,
|
|
},
|
|
}
|
|
end
|
|
self:T3(DCSTask)
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskLandAtZone(Zone,Duration,RandomPoint)
|
|
self:F2({self.ControllableName,Zone,Duration,RandomPoint})
|
|
local Point
|
|
if RandomPoint then
|
|
Point=Zone:GetRandomVec2()
|
|
else
|
|
Point=Zone:GetVec2()
|
|
end
|
|
local DCSTask=self:TaskLandAtVec2(Point,Duration)
|
|
self:T3(DCSTask)
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskFollow(FollowControllable,Vec3,LastWaypointIndex)
|
|
self:F2({self.ControllableName,FollowControllable,Vec3,LastWaypointIndex})
|
|
local LastWaypointIndexFlag=false
|
|
if LastWaypointIndex then
|
|
LastWaypointIndexFlag=true
|
|
end
|
|
local DCSTask
|
|
DCSTask={
|
|
id='Follow',
|
|
params={
|
|
groupId=FollowControllable:GetID(),
|
|
pos=Vec3,
|
|
lastWptIndexFlag=LastWaypointIndexFlag,
|
|
lastWptIndex=LastWaypointIndex
|
|
}
|
|
}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskEscort(FollowControllable,Vec3,LastWaypointIndex,EngagementDistance,TargetTypes)
|
|
self:F2({self.ControllableName,FollowControllable,Vec3,LastWaypointIndex,EngagementDistance,TargetTypes})
|
|
local LastWaypointIndexFlag=false
|
|
if LastWaypointIndex then
|
|
LastWaypointIndexFlag=true
|
|
end
|
|
local DCSTask
|
|
DCSTask={id='Escort',
|
|
params={
|
|
groupId=FollowControllable:GetID(),
|
|
pos=Vec3,
|
|
lastWptIndexFlag=LastWaypointIndexFlag,
|
|
lastWptIndex=LastWaypointIndex,
|
|
engagementDistMax=EngagementDistance,
|
|
targetTypes=TargetTypes,
|
|
},
|
|
},
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskFireAtPoint(Vec2,Radius,AmmoCount)
|
|
self:F2({self.ControllableName,Vec2,Radius,AmmoCount})
|
|
local DCSTask
|
|
DCSTask={id='FireAtPoint',
|
|
params={
|
|
point=Vec2,
|
|
radius=Radius,
|
|
expendQty=100,
|
|
expendQtyEnabled=false,
|
|
}
|
|
}
|
|
if AmmoCount then
|
|
DCSTask.params.expendQty=AmmoCount
|
|
DCSTask.params.expendQtyEnabled=true
|
|
end
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskHold()
|
|
self:F2({self.ControllableName})
|
|
local DCSTask
|
|
DCSTask={id='Hold',
|
|
params={
|
|
}
|
|
}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskFAC_AttackGroup(AttackGroup,WeaponType,Designation,Datalink)
|
|
self:F2({self.ControllableName,AttackGroup,WeaponType,Designation,Datalink})
|
|
local DCSTask
|
|
DCSTask={id='FAC_AttackGroup',
|
|
params={
|
|
groupId=AttackGroup:GetID(),
|
|
weaponType=WeaponType,
|
|
designation=Designation,
|
|
datalink=Datalink,
|
|
}
|
|
}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:EnRouteTaskEngageTargets(Distance,TargetTypes,Priority)
|
|
self:F2({self.ControllableName,Distance,TargetTypes,Priority})
|
|
local DCSTask
|
|
DCSTask={id='EngageTargets',
|
|
params={
|
|
maxDist=Distance,
|
|
targetTypes=TargetTypes,
|
|
priority=Priority
|
|
}
|
|
}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:EnRouteTaskEngageTargetsInZone(Vec2,Radius,TargetTypes,Priority)
|
|
self:F2({self.ControllableName,Vec2,Radius,TargetTypes,Priority})
|
|
local DCSTask
|
|
DCSTask={id='EngageTargetsInZone',
|
|
params={
|
|
point=Vec2,
|
|
zoneRadius=Radius,
|
|
targetTypes=TargetTypes,
|
|
priority=Priority
|
|
}
|
|
}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:EnRouteTaskEngageGroup(AttackGroup,Priority,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit)
|
|
self:F2({self.ControllableName,AttackGroup,Priority,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit})
|
|
local DirectionEnabled=nil
|
|
if Direction then
|
|
DirectionEnabled=true
|
|
end
|
|
local AltitudeEnabled=nil
|
|
if Altitude then
|
|
AltitudeEnabled=true
|
|
end
|
|
local DCSTask
|
|
DCSTask={id='EngageControllable',
|
|
params={
|
|
groupId=AttackGroup:GetID(),
|
|
weaponType=WeaponType,
|
|
expend=WeaponExpend,
|
|
attackQty=AttackQty,
|
|
directionEnabled=DirectionEnabled,
|
|
direction=Direction,
|
|
altitudeEnabled=AltitudeEnabled,
|
|
altitude=Altitude,
|
|
attackQtyLimit=AttackQtyLimit,
|
|
priority=Priority,
|
|
},
|
|
},
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:EnRouteTaskEngageUnit(EngageUnit,Priority,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,Visible,ControllableAttack)
|
|
self:F2({self.ControllableName,EngageUnit,Priority,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,Visible,ControllableAttack})
|
|
local DCSTask
|
|
DCSTask={id='EngageUnit',
|
|
params={
|
|
unitId=EngageUnit:GetID(),
|
|
priority=Priority or 1,
|
|
groupAttack=GroupAttack or false,
|
|
visible=Visible or false,
|
|
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,
|
|
controllableAttack=ControllableAttack,
|
|
},
|
|
},
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:EnRouteTaskAWACS()
|
|
self:F2({self.ControllableName})
|
|
local DCSTask
|
|
DCSTask={id='AWACS',
|
|
params={
|
|
}
|
|
}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:EnRouteTaskTanker()
|
|
self:F2({self.ControllableName})
|
|
local DCSTask
|
|
DCSTask={id='Tanker',
|
|
params={
|
|
}
|
|
}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:EnRouteTaskEWR()
|
|
self:F2({self.ControllableName})
|
|
local DCSTask
|
|
DCSTask={id='EWR',
|
|
params={
|
|
}
|
|
}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:EnRouteTaskFAC_EngageGroup(AttackGroup,Priority,WeaponType,Designation,Datalink)
|
|
self:F2({self.ControllableName,AttackGroup,WeaponType,Priority,Designation,Datalink})
|
|
local DCSTask
|
|
DCSTask={id='FAC_EngageControllable',
|
|
params={
|
|
groupId=AttackGroup:GetID(),
|
|
weaponType=WeaponType,
|
|
designation=Designation,
|
|
datalink=Datalink,
|
|
priority=Priority,
|
|
}
|
|
}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:EnRouteTaskFAC(Radius,Priority)
|
|
self:F2({self.ControllableName,Radius,Priority})
|
|
local DCSTask
|
|
DCSTask={id='FAC',
|
|
params={
|
|
radius=Radius,
|
|
priority=Priority
|
|
}
|
|
}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskEmbarking(Point,Duration,EmbarkingControllable)
|
|
self:F2({self.ControllableName,Point,Duration,EmbarkingControllable.DCSControllable})
|
|
local DCSTask
|
|
DCSTask={id='Embarking',
|
|
params={x=Point.x,
|
|
y=Point.y,
|
|
duration=Duration,
|
|
controllablesForEmbarking={EmbarkingControllable.ControllableID},
|
|
durationFlag=true,
|
|
distributionFlag=false,
|
|
distribution={},
|
|
}
|
|
}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskEmbarkToTransport(Point,Radius)
|
|
self:F2({self.ControllableName,Point,Radius})
|
|
local DCSTask
|
|
DCSTask={id='EmbarkToTransport',
|
|
params={x=Point.x,
|
|
y=Point.y,
|
|
zoneRadius=Radius,
|
|
}
|
|
}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskFunction(FunctionString,...)
|
|
self:F2({FunctionString,arg})
|
|
local DCSTask
|
|
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
|
|
DCSTask=self:TaskWrappedAction(
|
|
self:CommandDoScript(
|
|
table.concat(DCSScript)
|
|
)
|
|
)
|
|
self:T(DCSTask)
|
|
return DCSTask
|
|
end
|
|
function CONTROLLABLE:TaskMission(TaskMission)
|
|
self:F2(Points)
|
|
local DCSTask
|
|
DCSTask={id='Mission',params={TaskMission,},}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
do
|
|
function CONTROLLABLE:PatrolRoute()
|
|
local PatrolGroup=self
|
|
if not self:IsInstanceOf("GROUP")then
|
|
PatrolGroup=self:GetGroup()
|
|
end
|
|
self:E({PatrolGroup=PatrolGroup:GetName()})
|
|
if PatrolGroup:IsGround()or PatrolGroup:IsShip()then
|
|
local Waypoints=PatrolGroup:GetTemplateRoutePoints()
|
|
local FromCoord=PatrolGroup:GetCoordinate()
|
|
local From=FromCoord:WaypointGround(120)
|
|
table.insert(Waypoints,1,From)
|
|
local TaskRoute=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRoute")
|
|
self:E({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:E({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 ToWaypoint
|
|
repeat
|
|
ToWaypoint=math.random(1,#Waypoints)
|
|
until(ToWaypoint~=FromWaypoint)
|
|
self:E({FromWaypoint=FromWaypoint,ToWaypoint=ToWaypoint})
|
|
local Waypoint=Waypoints[ToWaypoint]
|
|
local ToCoord=COORDINATE:NewFromVec2({x=Waypoint.x,y=Waypoint.y})
|
|
local Route={}
|
|
Route[#Route+1]=FromCoord:WaypointGround(0)
|
|
Route[#Route+1]=ToCoord:WaypointGround(Speed,Formation)
|
|
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)
|
|
if not type(ZoneList)=="table"then
|
|
ZoneList={ZoneList}
|
|
end
|
|
local PatrolGroup=self
|
|
if not self:IsInstanceOf("GROUP")then
|
|
PatrolGroup=self:GetGroup()
|
|
end
|
|
self:E({PatrolGroup=PatrolGroup:GetName()})
|
|
if PatrolGroup:IsGround()or PatrolGroup:IsShip()then
|
|
local Waypoints=PatrolGroup:GetTemplateRoutePoints()
|
|
local Waypoint=Waypoints[math.random(1,#Waypoints)]
|
|
local FromCoord=PatrolGroup:GetCoordinate()
|
|
local RandomZone=ZoneList[math.random(1,#ZoneList)]
|
|
local ToCoord=RandomZone:GetRandomCoordinate(10)
|
|
local Route={}
|
|
Route[#Route+1]=FromCoord:WaypointGround(120)
|
|
Route[#Route+1]=ToCoord:WaypointGround(Speed,Formation)
|
|
local TaskRouteToZone=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolZones",ZoneList,Speed,Formation)
|
|
PatrolGroup:SetTaskWaypoint(Route[#Route],TaskRouteToZone)
|
|
PatrolGroup:Route(Route,1)
|
|
end
|
|
end
|
|
end
|
|
function CONTROLLABLE:TaskRoute(Points)
|
|
self:F2(Points)
|
|
local DCSTask
|
|
DCSTask={id='Mission',params={route={points=Points,},},}
|
|
self:T3({DCSTask})
|
|
return DCSTask
|
|
end
|
|
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:RouteGroundTo(ToCoordinate,Speed,Formation,DelaySeconds)
|
|
local FromCoordinate=self:GetCoordinate()
|
|
local FromWP=FromCoordinate:WaypointGround()
|
|
local ToWP=ToCoordinate:WaypointGround(Speed,Formation)
|
|
self:Route({FromWP,ToWP},DelaySeconds)
|
|
return self
|
|
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/1.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/1.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/1.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=60/3.6
|
|
end
|
|
local Points={PointFrom,PointTo}
|
|
self:T3(Points)
|
|
self:Route(Points)
|
|
return self
|
|
end
|
|
return nil
|
|
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
|
|
self:T({DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK})
|
|
return self:_GetController():getDetectedTargets(DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK)
|
|
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: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: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: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:OptionRTBBingoFuel(RTB)
|
|
self:F2({self.ControllableName})
|
|
RTB=RTB or true
|
|
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.GROUND.id.RTB_ON_OUT_OF_AMMO,WeaponsFlag)
|
|
end
|
|
return self
|
|
end
|
|
return nil
|
|
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:GetSize()
|
|
local DCSObject=self:GetDCSObject()
|
|
if DCSObject then
|
|
return 1
|
|
else
|
|
return 0
|
|
end
|
|
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"}
|
|
}
|
|
function GROUP:Register(GroupName)
|
|
self=BASE:Inherit(self,CONTROLLABLE:New(GroupName))
|
|
self:F2(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 PositionablePosition=DCSPositionable:getUnits()[1]:getPosition().p
|
|
self:T3(PositionablePosition)
|
|
return PositionablePosition
|
|
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:Destroy()
|
|
self:F2(self.GroupName)
|
|
local DCSGroup=self:GetDCSObject()
|
|
if DCSGroup then
|
|
for Index,UnitData in pairs(DCSGroup:getUnits())do
|
|
self:CreateEventCrash(timer.getTime(),UnitData)
|
|
end
|
|
DCSGroup:destroy()
|
|
DCSGroup=nil
|
|
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",
|
|
}
|
|
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:GetUnit(UnitNumber)
|
|
self:F2({self.GroupName,UnitNumber})
|
|
local DCSGroup=self:GetDCSObject()
|
|
if DCSGroup then
|
|
local DCSUnit=DCSGroup:getUnit(UnitNumber)
|
|
local UnitFound=UNIT:Find(DCSGroup:getUnit(UnitNumber))
|
|
self:T2(UnitFound)
|
|
return UnitFound
|
|
end
|
|
return nil
|
|
end
|
|
function GROUP:GetDCSUnit(UnitNumber)
|
|
self:F2({self.GroupName,UnitNumber})
|
|
local DCSGroup=self:GetDCSObject()
|
|
if DCSGroup then
|
|
local DCSUnitFound=DCSGroup:getUnit(UnitNumber)
|
|
self:T3(DCSUnitFound)
|
|
return DCSUnitFound
|
|
end
|
|
return nil
|
|
end
|
|
function GROUP:GetSize()
|
|
self:F2({self.GroupName})
|
|
local DCSGroup=self:GetDCSObject()
|
|
if DCSGroup then
|
|
local GroupSize=DCSGroup:getSize()
|
|
if GroupSize then
|
|
self:T3(GroupSize)
|
|
return GroupSize
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
function GROUP:GetInitialSize()
|
|
self:F2({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()
|
|
self:F2({self.GroupName})
|
|
trigger.action.activateGroup(self:GetDCSObject())
|
|
return self:GetDCSObject()
|
|
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: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
|
|
return nil
|
|
end
|
|
function GROUP:GetVec2()
|
|
self:F2(self.GroupName)
|
|
local UnitPoint=self:GetUnit(1)
|
|
UnitPoint:GetVec2()
|
|
local GroupPointVec2=UnitPoint:GetVec2()
|
|
self:T3(GroupPointVec2)
|
|
return GroupPointVec2
|
|
end
|
|
function GROUP:GetVec3()
|
|
self:F2(self.GroupName)
|
|
local GroupVec3=self:GetUnit(1):GetVec3()
|
|
self:T3(GroupVec3)
|
|
return GroupVec3
|
|
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
|
|
return nil
|
|
end
|
|
function GROUP:GetCoordinate()
|
|
self:F2(self.PositionableName)
|
|
local FirstUnit=self:GetUnit(1)
|
|
if FirstUnit then
|
|
local FirstUnitCoordinate=FirstUnit:GetCoordinate()
|
|
self:T3(FirstUnitCoordinate)
|
|
return FirstUnitCoordinate
|
|
end
|
|
return nil
|
|
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
|
|
return nil
|
|
end
|
|
function GROUP:GetHeading()
|
|
self:F2(self.GroupName)
|
|
local GroupSize=self:GetSize()
|
|
local HeadingAccumulator=0
|
|
if GroupSize then
|
|
for i=1,GroupSize do
|
|
HeadingAccumulator=HeadingAccumulator+self:GetUnit(i):GetHeading()
|
|
end
|
|
return math.floor(HeadingAccumulator/GroupSize)
|
|
end
|
|
return nil
|
|
end
|
|
function GROUP:GetFuel()
|
|
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()
|
|
self:F({Fuel=UnitFuel})
|
|
TotalFuel=TotalFuel+UnitFuel
|
|
end
|
|
local GroupFuel=TotalFuel/GroupSize
|
|
return GroupFuel
|
|
end
|
|
return 0
|
|
end
|
|
do
|
|
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: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: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()
|
|
end
|
|
function GROUP:GetMaxHeight()
|
|
self:F2()
|
|
end
|
|
function GROUP:Respawn(Template)
|
|
if self:IsAlive()then
|
|
local Vec3=self:GetVec3()
|
|
Template.x=Vec3.x
|
|
Template.y=Vec3.z
|
|
self:E(#Template.units)
|
|
for UnitID,UnitData in pairs(self:GetUnits())do
|
|
local GroupUnit=UnitData
|
|
self:E(GroupUnit:GetName())
|
|
if GroupUnit:IsAlive()then
|
|
local GroupUnitVec3=GroupUnit:GetVec3()
|
|
local GroupUnitHeading=GroupUnit:GetHeading()
|
|
Template.units[UnitID].alt=GroupUnitVec3.y
|
|
Template.units[UnitID].x=GroupUnitVec3.x
|
|
Template.units[UnitID].y=GroupUnitVec3.z
|
|
Template.units[UnitID].heading=GroupUnitHeading
|
|
self:E({UnitID,Template.units[UnitID],Template.units[UnitID]})
|
|
end
|
|
end
|
|
end
|
|
self:Destroy()
|
|
_DATABASE:Spawn(Template)
|
|
self:ResetEvents()
|
|
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: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: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
|
|
do
|
|
function GROUP:RouteRTB(RTBAirbase,Speed)
|
|
self:F2({RTBAirbase,Speed})
|
|
local DCSGroup=self:GetDCSObject()
|
|
if DCSGroup then
|
|
if RTBAirbase then
|
|
local GroupPoint=self:GetVec2()
|
|
local GroupVelocity=self:GetUnit(1):GetDesc().speedMax
|
|
local PointFrom={}
|
|
PointFrom.x=GroupPoint.x
|
|
PointFrom.y=GroupPoint.y
|
|
PointFrom.type="Turning Point"
|
|
PointFrom.action="Turning Point"
|
|
PointFrom.speed=GroupVelocity
|
|
local PointTo={}
|
|
local AirbasePointVec2=RTBAirbase:GetPointVec2()
|
|
local AirbaseAirPoint=AirbasePointVec2:WaypointAir(
|
|
POINT_VEC3.RoutePointAltType.BARO,
|
|
"Land",
|
|
"Landing",
|
|
Speed or self:GetUnit(1):GetDesc().speedMax
|
|
)
|
|
AirbaseAirPoint["airdromeId"]=RTBAirbase:GetID()
|
|
AirbaseAirPoint["speed_locked"]=true,
|
|
self:E(AirbaseAirPoint)
|
|
local Points={PointFrom,AirbaseAirPoint}
|
|
self:T3(Points)
|
|
local Template=self:GetTemplate()
|
|
Template.route.points=Points
|
|
self:Respawn(Template)
|
|
self:Route(Points)
|
|
self:Respawn(Template)
|
|
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 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)
|
|
end
|
|
end
|
|
self:F2(PlayerNames)
|
|
return PlayerNames
|
|
end
|
|
end
|
|
UNIT={
|
|
ClassName="UNIT",
|
|
}
|
|
function UNIT:Register(UnitName)
|
|
local self=BASE:Inherit(self,CONTROLLABLE:New(UnitName))
|
|
self.UnitName=UnitName
|
|
self:SetEventPriority(3)
|
|
return self
|
|
end
|
|
function UNIT:Find(DCSUnit)
|
|
local UnitName=DCSUnit:getName()
|
|
local UnitFound=_DATABASE:FindUnit(UnitName)
|
|
return UnitFound
|
|
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:ReSpawn(SpawnVec3,Heading)
|
|
local SpawnGroupTemplate=UTILS.DeepCopy(_DATABASE:GetGroupTemplateFromUnitName(self:Name()))
|
|
self:T(SpawnGroupTemplate)
|
|
local SpawnGroup=self:GetGroup()
|
|
if SpawnGroup then
|
|
local Vec3=SpawnGroup:GetVec3()
|
|
SpawnGroupTemplate.x=SpawnVec3.x
|
|
SpawnGroupTemplate.y=SpawnVec3.z
|
|
self:E(#SpawnGroupTemplate.units)
|
|
for UnitID,UnitData in pairs(SpawnGroup:GetUnits())do
|
|
local GroupUnit=UnitData
|
|
self:E(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:E({UnitID,SpawnGroupTemplate.units[UnitID],SpawnGroupTemplate.units[UnitID]})
|
|
end
|
|
end
|
|
end
|
|
for UnitTemplateID,UnitTemplateData in pairs(SpawnGroupTemplate.units)do
|
|
self:T(UnitTemplateData.name)
|
|
if UnitTemplateData.name==self:Name()then
|
|
self:T("Adjusting")
|
|
SpawnGroupTemplate.units[UnitTemplateID].alt=SpawnVec3.y
|
|
SpawnGroupTemplate.units[UnitTemplateID].x=SpawnVec3.x
|
|
SpawnGroupTemplate.units[UnitTemplateID].y=SpawnVec3.z
|
|
SpawnGroupTemplate.units[UnitTemplateID].heading=Heading
|
|
self:E({UnitTemplateID,SpawnGroupTemplate.units[UnitTemplateID],SpawnGroupTemplate.units[UnitTemplateID]})
|
|
else
|
|
self:E(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
|
|
_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()
|
|
return UnitCallSign
|
|
end
|
|
self:E(self.ClassName.." "..self.UnitName.." not found!")
|
|
return nil
|
|
end
|
|
function UNIT:GetPlayerName()
|
|
self:F2(self.UnitName)
|
|
local DCSUnit=self:GetDCSObject()
|
|
if DCSUnit then
|
|
local PlayerName=DCSUnit:getPlayerName()
|
|
if PlayerName==nil then
|
|
PlayerName=""
|
|
end
|
|
return PlayerName
|
|
end
|
|
return nil
|
|
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:GetGroup()
|
|
self:F2(self.UnitName)
|
|
local DCSUnit=self:GetDCSObject()
|
|
if DCSUnit then
|
|
local UnitGroup=GROUP:Find(DCSUnit:getGroup())
|
|
return UnitGroup
|
|
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: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 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:F(self.UnitName)
|
|
local DCSUnit=self:GetDCSObject()
|
|
if DCSUnit then
|
|
local UnitFuel=DCSUnit:getFuel()
|
|
return UnitFuel
|
|
end
|
|
return nil
|
|
end
|
|
function UNIT:GetUnits()
|
|
self:F2({self.UnitName})
|
|
local DCSUnit=self:GetDCSObject()
|
|
if DCSUnit then
|
|
local DCSUnits=DCSUnit:getUnits()
|
|
local Units={}
|
|
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: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
|
|
self:T(Attributes)
|
|
if self:IsGround()then
|
|
self:T("Ground")
|
|
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"]then ThreatLevel=1
|
|
end
|
|
ThreatText=ThreatLevels[ThreatLevel+1]
|
|
end
|
|
if self:IsAir()then
|
|
self:T("Air")
|
|
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
|
|
self:T("Ship")
|
|
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
|
|
self:T2(ThreatLevel)
|
|
return ThreatLevel,ThreatText
|
|
end
|
|
function UNIT:IsInZone(Zone)
|
|
self:F2({self.UnitName,Zone})
|
|
if self:IsAlive()then
|
|
local IsInZone=Zone:IsVec3InZone(self:GetVec3())
|
|
self:T2({IsInZone})
|
|
return IsInZone
|
|
end
|
|
return false
|
|
end
|
|
function UNIT:IsNotInZone(Zone)
|
|
self:F2({self.UnitName,Zone})
|
|
if self:IsAlive()then
|
|
local IsInZone=not Zone:IsVec3InZone(self:GetVec3())
|
|
self:T({IsInZone})
|
|
return IsInZone
|
|
else
|
|
return false
|
|
end
|
|
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: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
|
|
return nil
|
|
end
|
|
function UNIT: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
|
|
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:E(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()
|
|
self:F2(self.UnitName)
|
|
local DCSUnit=self:GetDCSObject()
|
|
if DCSUnit then
|
|
local UnitInAir=DCSUnit:inAir()
|
|
self:T3(UnitInAir)
|
|
return UnitInAir
|
|
end
|
|
return nil
|
|
end
|
|
do
|
|
function UNIT:HandleEvent(Event,EventFunction)
|
|
self:EventDispatcher():OnEventForUnit(self:GetName(),EventFunction,self,Event)
|
|
return self
|
|
end
|
|
function UNIT:UnHandleEvent(Event)
|
|
self:EventDispatcher():RemoveForUnit(self:GetName(),self,Event)
|
|
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
|
|
end
|
|
CLIENT={
|
|
ONBOARDSIDE={
|
|
NONE=0,
|
|
LEFT=1,
|
|
RIGHT=2,
|
|
BACK=3,
|
|
FRONT=4
|
|
},
|
|
ClassName="CLIENT",
|
|
ClientName=nil,
|
|
ClientAlive=false,
|
|
ClientTransport=false,
|
|
ClientBriefingShown=false,
|
|
_Menus={},
|
|
_Tasks={},
|
|
Messages={
|
|
}
|
|
}
|
|
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:F(ClientName)
|
|
self.ClientName=ClientName
|
|
self.MessageSwitch=true
|
|
self.ClientAlive2=false
|
|
self.AliveCheckScheduler=SCHEDULER:New(self,self._AliveCheckScheduler,{"Client Alive "..ClientName},1,5)
|
|
self:E(self)
|
|
return self
|
|
end
|
|
function CLIENT:Transport()
|
|
self:F()
|
|
self.ClientTransport=true
|
|
return self
|
|
end
|
|
function CLIENT:AddBriefing(ClientBriefing)
|
|
self:F(ClientBriefing)
|
|
self.ClientBriefing=ClientBriefing
|
|
self.ClientBriefingShown=false
|
|
return self
|
|
end
|
|
function CLIENT:ShowBriefing()
|
|
self:F({self.ClientName,self.ClientBriefingShown})
|
|
if not self.ClientBriefingShown then
|
|
self.ClientBriefingShown=true
|
|
local Briefing=""
|
|
if self.ClientBriefing then
|
|
Briefing=Briefing..self.ClientBriefing
|
|
end
|
|
Briefing=Briefing.." Press [LEFT ALT]+[B] to view the complete mission briefing."
|
|
self:Message(Briefing,60,"Briefing")
|
|
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
|
|
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.ClientGroupUnit=nil
|
|
return nil
|
|
end
|
|
function CLIENT:GetClientGroupID()
|
|
local ClientGroup=self:GetDCSGroup()
|
|
return self.ClientGroupID
|
|
end
|
|
function CLIENT:GetClientGroupName()
|
|
local ClientGroup=self:GetDCSGroup()
|
|
self:T(self.ClientGroupName)
|
|
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)
|
|
self:T2(ClientUnit)
|
|
return ClientUnit
|
|
end
|
|
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.SwitchMessages(PrmTable)
|
|
PrmTable[1].MessageSwitch=PrmTable[2]
|
|
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:FindByName(StaticName,RaiseError)
|
|
local StaticFound=_DATABASE:FindStatic(StaticName)
|
|
self.StaticName=StaticName
|
|
if StaticFound then
|
|
StaticFound:F3({StaticName})
|
|
return StaticFound
|
|
end
|
|
if RaiseError==nil or RaiseError==true then
|
|
error("STATIC not found for: "..StaticName)
|
|
end
|
|
return nil
|
|
end
|
|
function STATIC:Register(StaticName)
|
|
local self=BASE:Inherit(self,POSITIONABLE:New(StaticName))
|
|
self.StaticName=StaticName
|
|
return self
|
|
end
|
|
function STATIC:GetDCSObject()
|
|
local DCSStatic=StaticObject.getByName(self.StaticName)
|
|
if DCSStatic then
|
|
return DCSStatic
|
|
end
|
|
return nil
|
|
end
|
|
function STATIC:GetThreatLevel()
|
|
return 1,"Static"
|
|
end
|
|
AIRBASE={
|
|
ClassName="AIRBASE",
|
|
CategoryName={
|
|
[Airbase.Category.AIRDROME]="Airdrome",
|
|
[Airbase.Category.HELIPAD]="Helipad",
|
|
[Airbase.Category.SHIP]="Ship",
|
|
},
|
|
}
|
|
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 AFB",
|
|
["Groom_Lake_AFB"]="Groom Lake AFB",
|
|
["McCarran_International_Airport"]="McCarran International Airport",
|
|
["Nellis_AFB"]="Nellis AFB",
|
|
["Beatty_Airport"]="Beatty Airport",
|
|
["Boulder_City_Airport"]="Boulder City Airport",
|
|
["Echo_Bay"]="Echo Bay",
|
|
["Henderson_Executive_Airport"]="Henderson Executive Airport",
|
|
["Jean_Airport"]="Jean Airport",
|
|
["Laughlin_Airport"]="Laughlin Airport",
|
|
["Lincoln_County"]="Lincoln County",
|
|
["Mellan_Airstrip"]="Mellan Airstrip",
|
|
["Mesquite"]="Mesquite",
|
|
["Mina_Airport_3Q0"]="Mina Airport 3Q0",
|
|
["North_Las_Vegas"]="North Las Vegas",
|
|
["Pahute_Mesa_Airstrip"]="Pahute Mesa Airstrip",
|
|
["Tonopah_Airport"]="Tonopah Airport",
|
|
["Tonopah_Test_Range_Airfield"]="Tonopah Test Range Airfield",
|
|
}
|
|
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"]="Ford",
|
|
}
|
|
function AIRBASE:Register(AirbaseName)
|
|
local self=BASE:Inherit(self,POSITIONABLE:New(AirbaseName))
|
|
self.AirbaseName=AirbaseName
|
|
self.AirbaseZone=ZONE_RADIUS:New(AirbaseName,self:GetVec2(),8000)
|
|
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:GetDCSObject()
|
|
local DCSAirbase=Airbase.getByName(self.AirbaseName)
|
|
if DCSAirbase then
|
|
return DCSAirbase
|
|
end
|
|
return nil
|
|
end
|
|
function AIRBASE:GetZone()
|
|
return self.AirbaseZone
|
|
end
|
|
SCENERY={
|
|
ClassName="SCENERY",
|
|
}
|
|
function SCENERY:Register(SceneryName,SceneryObject)
|
|
local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName))
|
|
self.SceneryName=SceneryName
|
|
self.SceneryObject=SceneryObject
|
|
return self
|
|
end
|
|
function SCENERY:GetDCSObject()
|
|
return self.SceneryObject
|
|
end
|
|
function SCENERY:GetThreatLevel()
|
|
return 0,"Scenery"
|
|
end
|
|
SCORING={
|
|
ClassName="SCORING",
|
|
ClassID=0,
|
|
Players={},
|
|
}
|
|
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(true)
|
|
self:SetMessagesDestroy(true)
|
|
self:SetMessagesScore(true)
|
|
self:SetMessagesZone(true)
|
|
self:SetScaleDestroyScore(10)
|
|
self:SetScaleDestroyPenalty(30)
|
|
self:SetFratricide(self.ScaleDestroyPenalty*3)
|
|
self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty)
|
|
self:SetDisplayMessagePrefix()
|
|
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
|
|
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
|
|
self:HandleEvent(EVENTS.Hit,self._EventOnHit)
|
|
self:HandleEvent(EVENTS.PlayerEnterUnit)
|
|
self:HandleEvent(EVENTS.PlayerLeaveUnit)
|
|
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:SetCoalitionChangePenalty(CoalitionChangePenalty)
|
|
self.CoalitionChangePenalty=CoalitionChangePenalty
|
|
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 then
|
|
self.Players[PlayerName].Penalty=self.Players[PlayerName].Penalty+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). 50 Penalty points added.",
|
|
MESSAGE.Type.Information
|
|
):ToAll()
|
|
self:ScoreCSV(PlayerName,"","COALITION_PENALTY",1,-50,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 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 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:AddGoalScore(PlayerUnit,GoalTag,Text,Score)
|
|
local PlayerName=PlayerUnit:GetPlayerName()
|
|
self:E({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:E({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..MissionName.." : "..Text.." Score: "..Score,MESSAGE.Type.Information):ToAll()
|
|
self:ScoreCSV(PlayerName,"","TASK_"..MissionName:gsub(' ','_'),1,Score,PlayerUnit:GetName())
|
|
end
|
|
end
|
|
function SCORING:_AddMissionScore(Mission,Text,Score)
|
|
local MissionName=Mission:GetName()
|
|
self:E({Mission,Text,Score})
|
|
self:E(self.Players)
|
|
for PlayerName,PlayerData in pairs(self.Players)do
|
|
self:E(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 '"..MissionName.."'. "..
|
|
Score.." mission score!",
|
|
MESSAGE.Type.Information):ToAll()
|
|
self:ScoreCSV(PlayerName,"","MISSION_"..MissionName:gsub(' ','_'),1,Score)
|
|
end
|
|
end
|
|
end
|
|
function SCORING:OnEventPlayerEnterUnit(Event)
|
|
if Event.IniUnit then
|
|
self:_AddPlayerFromUnit(Event.IniUnit)
|
|
local Menu=MENU_GROUP:New(Event.IniGroup,'Scoring')
|
|
local ReportGroupSummary=MENU_GROUP_COMMAND:New(Event.IniGroup,'Summary report players in group',Menu,SCORING.ReportScoreGroupSummary,self,Event.IniGroup)
|
|
local ReportGroupDetailed=MENU_GROUP_COMMAND:New(Event.IniGroup,'Detailed report players in group',Menu,SCORING.ReportScoreGroupDetailed,self,Event.IniGroup)
|
|
local ReportToAllSummary=MENU_GROUP_COMMAND:New(Event.IniGroup,'Summary report all players',Menu,SCORING.ReportScoreAllSummary,self,Event.IniGroup)
|
|
self:SetState(Event.IniUnit,"ScoringMenu",Menu)
|
|
end
|
|
end
|
|
function SCORING:OnEventPlayerLeaveUnit(Event)
|
|
if Event.IniUnit then
|
|
local Menu=self:GetState(Event.IniUnit,"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
|
|
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:E({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
|
|
Destroyed=true
|
|
self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_PENALTY",1,ThreatPenalty,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
|
|
else
|
|
local ThreatLevelTarget=TargetThreatLevel
|
|
local ThreatTypeTarget=TargetThreatType
|
|
local ThreatLevelPlayer=Player.ThreatLevel/10+1
|
|
local ThreatScore=math.ceil((ThreatLevelTarget/ThreatLevelPlayer)*self.ScaleDestroyScore/10)
|
|
self:E({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
|
|
Destroyed=true
|
|
self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,ThreatScore,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
|
|
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:E({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:E({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())
|
|
Destroyed=true
|
|
self:ScoreCSV(PlayerName,"","DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Scenery",TargetUnitType)
|
|
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:E({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:E({ReportHits,ScoreHits,PenaltyHits})
|
|
local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName)
|
|
ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys
|
|
self:E({ReportDestroys,ScoreDestroys,PenaltyDestroys})
|
|
local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName)
|
|
ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges
|
|
self:E({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges})
|
|
local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName)
|
|
ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals
|
|
self:E({ReportGoals,ScoreGoals,PenaltyGoals})
|
|
local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName)
|
|
ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions
|
|
self:E({ReportMissions,ScoreMissions,PenaltyMissions})
|
|
local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions
|
|
local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+ScoreGoals+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:E({ReportHits,ScoreHits,PenaltyHits})
|
|
local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName)
|
|
ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys
|
|
self:E({ReportDestroys,ScoreDestroys,PenaltyDestroys})
|
|
local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName)
|
|
ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges
|
|
self:E({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges})
|
|
local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName)
|
|
ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals
|
|
self:E({ReportGoals,ScoreGoals,PenaltyGoals})
|
|
local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName)
|
|
ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions
|
|
self:E({ReportMissions,ScoreMissions,PenaltyMissions})
|
|
local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions
|
|
local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+ScoreGoals+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("Report Score All Players")
|
|
for PlayerName,PlayerData in pairs(self.Players)do
|
|
if PlayerName then
|
|
local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName)
|
|
ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits
|
|
self:E({ReportHits,ScoreHits,PenaltyHits})
|
|
local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName)
|
|
ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys
|
|
self:E({ReportDestroys,ScoreDestroys,PenaltyDestroys})
|
|
local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName)
|
|
ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges
|
|
self:E({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges})
|
|
local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName)
|
|
ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals
|
|
self:E({ReportGoals,ScoreGoals,PenaltyGoals})
|
|
local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName)
|
|
ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions
|
|
self:E({ReportMissions,ScoreMissions,PenaltyMissions})
|
|
local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions
|
|
local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+ScoreGoals+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 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","PlayerUnitCoaltion","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:E("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 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 then
|
|
self.CSVFile:close()
|
|
end
|
|
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)
|
|
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})
|
|
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
|
|
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 _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 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:T("CleanUp: Group "..CleanUpUnitName.." cannot be found in DCS RTE, removing ...")
|
|
self.CleanUpList[CleanUpUnitName]=nil
|
|
end
|
|
end
|
|
self:T(CleanUpCount)
|
|
return true
|
|
end
|
|
SPAWN={
|
|
ClassName="SPAWN",
|
|
SpawnTemplatePrefix=nil,
|
|
SpawnAliasPrefix=nil,
|
|
}
|
|
SPAWN.Takeoff=GROUP.Takeoff
|
|
function SPAWN:New(SpawnTemplatePrefix)
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self:F({SpawnTemplatePrefix})
|
|
local TemplateGroup=Group.getByName(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.Grouping=nil
|
|
self.SpawnGroups={}
|
|
else
|
|
error("SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'")
|
|
end
|
|
self:SetEventPriority(5)
|
|
return self
|
|
end
|
|
function SPAWN:NewWithAlias(SpawnTemplatePrefix,SpawnAliasPrefix)
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self:F({SpawnTemplatePrefix,SpawnAliasPrefix})
|
|
local TemplateGroup=Group.getByName(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.Grouping=nil
|
|
self.SpawnGroups={}
|
|
else
|
|
error("SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'")
|
|
end
|
|
self:SetEventPriority(5)
|
|
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()
|
|
self:F()
|
|
self.SpawnInitKeepUnitNames=true
|
|
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})
|
|
self.SpawnTemplatePrefixTable=SpawnTemplatePrefixTable
|
|
self.SpawnRandomizeTemplate=true
|
|
for SpawnGroupID=1,self.SpawnMaxGroups do
|
|
self:_RandomizeTemplate(SpawnGroupID)
|
|
end
|
|
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})
|
|
self.SpawnZoneTable=SpawnZoneTable
|
|
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)
|
|
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})
|
|
return self:SpawnWithIndex(self.SpawnIndex+1)
|
|
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:SpawnWithIndex(SpawnIndex)
|
|
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
|
|
if SpawnTemplate.CategoryID==Group.Category.HELICOPTER or SpawnTemplate.CategoryID==Group.Category.AIRPLANE then
|
|
if SpawnTemplate.route.points[1].type=="TakeOffParking"then
|
|
SpawnTemplate.uncontrolled=self.SpawnUnControlled
|
|
end
|
|
end
|
|
end
|
|
self:HandleEvent(EVENTS.Birth,self._OnBirth)
|
|
self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash)
|
|
self:HandleEvent(EVENTS.Crash,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=SCHEDULER:New()
|
|
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)
|
|
self:E({self.SpawnTemplatePrefix,SpawnAirbase,Takeoff,TakeoffAltitude})
|
|
local PointVec3=SpawnAirbase:GetPointVec3()
|
|
self:T2(PointVec3)
|
|
Takeoff=Takeoff or SPAWN.Takeoff.Hot
|
|
if self:_GetSpawnIndex(self.SpawnIndex+1)then
|
|
local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
|
|
if SpawnTemplate then
|
|
self:T({"Current point of ",self.SpawnTemplatePrefix,SpawnAirbase})
|
|
local SpawnPoint=SpawnTemplate.route.points[1]
|
|
SpawnPoint.linkUnit=nil
|
|
SpawnPoint.helipadId=nil
|
|
SpawnPoint.airdromeId=nil
|
|
local AirbaseID=SpawnAirbase:GetID()
|
|
local AirbaseCategory=SpawnAirbase:GetDesc().category
|
|
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]
|
|
for UnitID=1,#SpawnTemplate.units do
|
|
self:T('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
|
|
local UnitTemplate=SpawnTemplate.units[UnitID]
|
|
UnitTemplate.parking=nil
|
|
UnitTemplate.parking_id=nil
|
|
UnitTemplate.alt=0
|
|
local SX=UnitTemplate.x
|
|
local SY=UnitTemplate.y
|
|
local BX=SpawnPoint.x
|
|
local BY=SpawnPoint.y
|
|
local TX=PointVec3.x+(SX-BX)
|
|
local TY=PointVec3.z+(SY-BY)
|
|
UnitTemplate.x=TX
|
|
UnitTemplate.y=TY
|
|
if Takeoff==GROUP.Takeoff.Air then
|
|
UnitTemplate.alt=PointVec3.y+(TakeoffAltitude or 200)
|
|
end
|
|
self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..UnitTemplate.x..', SpawnTemplate.units['..UnitID..'].y = '..UnitTemplate.y)
|
|
end
|
|
SpawnPoint.x=PointVec3.x
|
|
SpawnPoint.y=PointVec3.z
|
|
if Takeoff==GROUP.Takeoff.Air then
|
|
SpawnPoint.alt=PointVec3.y+(TakeoffAltitude or 200)
|
|
end
|
|
SpawnTemplate.x=PointVec3.x
|
|
SpawnTemplate.y=PointVec3.z
|
|
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()},1)
|
|
end
|
|
end
|
|
return GroupSpawned
|
|
end
|
|
end
|
|
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})
|
|
for UnitID=1,#SpawnTemplate.units do
|
|
self:T('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=Vec3.x+(SX-BX)
|
|
local TY=Vec3.z+(SY-BY)
|
|
SpawnTemplate.units[UnitID].x=TX
|
|
SpawnTemplate.units[UnitID].y=TY
|
|
SpawnTemplate.units[UnitID].alt=Vec3.y
|
|
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
|
|
SpawnTemplate.route.points[1].alt=Vec3.y
|
|
SpawnTemplate.x=Vec3.x
|
|
SpawnTemplate.y=Vec3.z
|
|
return self:SpawnWithIndex(self.SpawnIndex)
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
function SPAWN:SpawnFromVec2(Vec2,SpawnIndex)
|
|
self:F({self.SpawnTemplatePrefix,Vec2,SpawnIndex})
|
|
local PointVec2=POINT_VEC2:NewFromVec2(Vec2)
|
|
return self:SpawnFromVec3(PointVec2:GetVec3(),SpawnIndex)
|
|
end
|
|
function SPAWN:SpawnFromUnit(HostUnit,SpawnIndex)
|
|
self:F({self.SpawnTemplatePrefix,HostUnit,SpawnIndex})
|
|
if HostUnit and HostUnit:IsAlive()~=nil then
|
|
return self:SpawnFromVec3(HostUnit:GetVec3(),SpawnIndex)
|
|
end
|
|
return nil
|
|
end
|
|
function SPAWN:SpawnFromStatic(HostStatic,SpawnIndex)
|
|
self:F({self.SpawnTemplatePrefix,HostStatic,SpawnIndex})
|
|
if HostStatic and HostStatic:IsAlive()then
|
|
return self:SpawnFromVec3(HostStatic:GetVec3(),SpawnIndex)
|
|
end
|
|
return nil
|
|
end
|
|
function SPAWN:SpawnInZone(Zone,RandomizeGroup,SpawnIndex)
|
|
self:F({self.SpawnTemplatePrefix,Zone,RandomizeGroup,SpawnIndex})
|
|
if Zone then
|
|
if RandomizeGroup then
|
|
return self:SpawnFromVec2(Zone:GetRandomVec2(),SpawnIndex)
|
|
else
|
|
return self:SpawnFromVec2(Zone:GetVec2(),SpawnIndex)
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
function SPAWN:InitUnControlled(UnControlled)
|
|
self:F2({self.SpawnTemplatePrefix,UnControlled})
|
|
self.SpawnUnControlled=UnControlled
|
|
for SpawnGroupID=1,self.SpawnMaxGroups do
|
|
self.SpawnGroups[SpawnGroupID].UnControlled=UnControlled
|
|
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.SpawnTemplatePrefixself.SpawnAliasPrefix})
|
|
self.SpawnIndex=self:_GetLastIndex()
|
|
for SpawnIndex=self.SpawnIndex,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)
|
|
self:F3({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnGroup})
|
|
local GroupName=SpawnGroup:GetName()
|
|
if GroupName then
|
|
local SpawnPrefix=string.match(GroupName,".*#")
|
|
if SpawnPrefix then
|
|
SpawnPrefix=SpawnPrefix:sub(1,-2)
|
|
end
|
|
return SpawnPrefix
|
|
end
|
|
return nil
|
|
end
|
|
function SPAWN:GetSpawnIndexFromGroup(SpawnGroup)
|
|
self:F({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
|
|
SpawnTemplate=routines.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=self:_GetTemplate(SpawnTemplatePrefix)
|
|
SpawnTemplate.name=self:SpawnGroupName(SpawnIndex)
|
|
SpawnTemplate.groupId=nil
|
|
SpawnTemplate.lateActivation=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
|
|
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=routines.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
|
|
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 SpawnGroup=EventData.IniGroup
|
|
if SpawnGroup then
|
|
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
|
|
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})
|
|
self:ReSpawn(SpawnGroupIndex)
|
|
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})
|
|
self:ReSpawn(SpawnGroupIndex)
|
|
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})
|
|
while SpawnGroup do
|
|
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 then
|
|
local NewVec2=SpawnUnit:GetVec2()
|
|
if Stamp.Vec2.x==NewVec2.x and Stamp.Vec2.y==NewVec2.y 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 then
|
|
Stamp.Vec2=SpawnUnit:GetVec2()
|
|
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
|
|
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=50,DelayOff={10,25},DelayOn={10,30}},
|
|
Good={Evade=30,DelayOff={8,20},DelayOn={20,40}},
|
|
High={Evade=15,DelayOff={5,17},DelayOn={30,50}},
|
|
Excellent={Evade=10,DelayOff={3,10},DelayOn={30,60}}
|
|
},
|
|
SEADGroupPrefixes={}
|
|
}
|
|
function SEAD:New(SEADGroupPrefixes)
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self:F(SEADGroupPrefixes)
|
|
if type(SEADGroupPrefixes)=='table'then
|
|
for SEADGroupPrefixID,SEADGroupPrefix in pairs(SEADGroupPrefixes)do
|
|
self.SEADGroupPrefixes[SEADGroupPrefix]=SEADGroupPrefix
|
|
end
|
|
else
|
|
self.SEADGroupNames[SEADGroupPrefixes]=SEADGroupPrefixes
|
|
end
|
|
self:HandleEvent(EVENTS.Shot)
|
|
return self
|
|
end
|
|
function SEAD:OnEventShot(EventData)
|
|
self:F({EventData})
|
|
local SEADUnit=EventData.IniDCSUnit
|
|
local SEADUnitName=EventData.IniDCSUnitName
|
|
local SEADWeapon=EventData.Weapon
|
|
local SEADWeaponName=EventData.WeaponName
|
|
self:T("Missile Launched = "..SEADWeaponName)
|
|
if SEADWeaponName=="KH-58"or SEADWeaponName=="KH-25MPU"or SEADWeaponName=="AGM-88"or SEADWeaponName=="KH-31A"or SEADWeaponName=="KH-31P"then
|
|
local _evade=math.random(1,100)
|
|
local _targetMim=EventData.Weapon:getTarget()
|
|
local _targetMimname=Unit.getName(_targetMim)
|
|
local _targetMimgroup=Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
|
local _targetMimgroupName=_targetMimgroup:getName()
|
|
local _targetMimcont=_targetMimgroup:getController()
|
|
local _targetskill=_DATABASE.Templates.Units[_targetMimname].Template.skill
|
|
self:T(self.SEADGroupPrefixes)
|
|
self:T(_targetMimgroupName)
|
|
local SEADGroupFound=false
|
|
for SEADGroupPrefixID,SEADGroupPrefix in pairs(self.SEADGroupPrefixes)do
|
|
if string.find(_targetMimgroupName,SEADGroupPrefix,1,true)then
|
|
SEADGroupFound=true
|
|
self:T('Group Found')
|
|
break
|
|
end
|
|
end
|
|
if SEADGroupFound==true then
|
|
if _targetskill=="Random"then
|
|
local Skills={"Average","Good","High","Excellent"}
|
|
_targetskill=Skills[math.random(1,4)]
|
|
end
|
|
self:T(_targetskill)
|
|
if self.TargetSkill[_targetskill]then
|
|
if(_evade>self.TargetSkill[_targetskill].Evade)then
|
|
self:T(string.format("Evading, target skill "..string.format(_targetskill)))
|
|
local _targetMim=Weapon.getTarget(SEADWeapon)
|
|
local _targetMimname=Unit.getName(_targetMim)
|
|
local _targetMimgroup=Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
|
local _targetMimcont=_targetMimgroup:getController()
|
|
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20)
|
|
local SuppressedGroups1={}
|
|
local function SuppressionEnd1(id)
|
|
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
|
SuppressedGroups1[id.groupName]=nil
|
|
end
|
|
local id={
|
|
groupName=_targetMimgroup,
|
|
ctrl=_targetMimcont
|
|
}
|
|
local delay1=math.random(self.TargetSkill[_targetskill].DelayOff[1],self.TargetSkill[_targetskill].DelayOff[2])
|
|
if SuppressedGroups1[id.groupName]==nil then
|
|
SuppressedGroups1[id.groupName]={
|
|
SuppressionEndTime1=timer.getTime()+delay1,
|
|
SuppressionEndN1=SuppressionEndCounter1
|
|
}
|
|
Controller.setOption(_targetMimcont,AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
|
timer.scheduleFunction(SuppressionEnd1,id,SuppressedGroups1[id.groupName].SuppressionEndTime1)
|
|
end
|
|
local SuppressedGroups={}
|
|
local function SuppressionEnd(id)
|
|
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
|
|
SuppressedGroups[id.groupName]=nil
|
|
end
|
|
local id={
|
|
groupName=_targetMimgroup,
|
|
ctrl=_targetMimcont
|
|
}
|
|
local delay=math.random(self.TargetSkill[_targetskill].DelayOn[1],self.TargetSkill[_targetskill].DelayOn[2])
|
|
if SuppressedGroups[id.groupName]==nil then
|
|
SuppressedGroups[id.groupName]={
|
|
SuppressionEndTime=timer.getTime()+delay,
|
|
SuppressionEndN=SuppressionEndCounter
|
|
}
|
|
timer.scheduleFunction(SuppressionEnd,id,SuppressedGroups[id.groupName].SuppressionEndTime)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
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_CLIENT:New(self.EscortClient,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_CLIENT:New(self.EscortClient,"Navigation",self.EscortMenu)
|
|
end
|
|
if not self.EscortMenuJoinUpAndFollow then
|
|
self.EscortMenuJoinUpAndFollow={}
|
|
end
|
|
self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1]=MENU_CLIENT_COMMAND:New(self.EscortClient,"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_CLIENT:New(self.EscortClient,"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_CLIENT_COMMAND
|
|
:New(
|
|
self.EscortClient,
|
|
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_CLIENT:New(self.EscortClient,"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_CLIENT_COMMAND
|
|
:New(
|
|
self.EscortClient,
|
|
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_CLIENT:New(self.EscortClient,"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_CLIENT_COMMAND
|
|
:New(
|
|
self.EscortClient,
|
|
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_CLIENT:New(self.EscortClient,"Navigation",self.EscortMenu)
|
|
end
|
|
local MenuText=""
|
|
if not MenuTextFormat then
|
|
MenuText="Flare"
|
|
else
|
|
MenuText=MenuTextFormat
|
|
end
|
|
if not self.EscortMenuFlare then
|
|
self.EscortMenuFlare=MENU_CLIENT:New(self.EscortClient,MenuText,self.EscortMenuReportNavigation,ESCORT._Flare,self)
|
|
self.EscortMenuFlareGreen=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release green flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Green,"Released a green flare!")
|
|
self.EscortMenuFlareRed=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release red flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Red,"Released a red flare!")
|
|
self.EscortMenuFlareWhite=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release white flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.White,"Released a white flare!")
|
|
self.EscortMenuFlareYellow=MENU_CLIENT_COMMAND:New(self.EscortClient,"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_CLIENT:New(self.EscortClient,"Navigation",self.EscortMenu)
|
|
end
|
|
local MenuText=""
|
|
if not MenuTextFormat then
|
|
MenuText="Smoke"
|
|
else
|
|
MenuText=MenuTextFormat
|
|
end
|
|
if not self.EscortMenuSmoke then
|
|
self.EscortMenuSmoke=MENU_CLIENT:New(self.EscortClient,"Smoke",self.EscortMenuReportNavigation,ESCORT._Smoke,self)
|
|
self.EscortMenuSmokeGreen=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release green smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Green,"Releasing green smoke!")
|
|
self.EscortMenuSmokeRed=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release red smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Red,"Releasing red smoke!")
|
|
self.EscortMenuSmokeWhite=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release white smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.White,"Releasing white smoke!")
|
|
self.EscortMenuSmokeOrange=MENU_CLIENT_COMMAND:New(self.EscortClient,"Release orange smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Orange,"Releasing orange smoke!")
|
|
self.EscortMenuSmokeBlue=MENU_CLIENT_COMMAND:New(self.EscortClient,"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_CLIENT:New(self.EscortClient,"Report targets",self.EscortMenu)
|
|
end
|
|
if not Seconds then
|
|
Seconds=30
|
|
end
|
|
self.EscortMenuReportNearbyTargetsNow=MENU_CLIENT_COMMAND:New(self.EscortClient,"Report targets now!",self.EscortMenuReportNearbyTargets,ESCORT._ReportNearbyTargetsNow,self)
|
|
self.EscortMenuReportNearbyTargetsOn=MENU_CLIENT_COMMAND:New(self.EscortClient,"Report targets on",self.EscortMenuReportNearbyTargets,ESCORT._SwitchReportNearbyTargets,self,true)
|
|
self.EscortMenuReportNearbyTargetsOff=MENU_CLIENT_COMMAND:New(self.EscortClient,"Report targets off",self.EscortMenuReportNearbyTargets,ESCORT._SwitchReportNearbyTargets,self,false)
|
|
self.EscortMenuAttackNearbyTargets=MENU_CLIENT:New(self.EscortClient,"Attack targets",self.EscortMenu)
|
|
self.ReportTargetsScheduler=SCHEDULER:New(self,self._ReportTargetsScheduler,{},1,Seconds)
|
|
return self
|
|
end
|
|
function ESCORT:MenuAssistedAttack()
|
|
self:F()
|
|
self.EscortMenuTargetAssistance=MENU_CLIENT:New(self.EscortClient,"Request assistance from",self.EscortMenu)
|
|
return self
|
|
end
|
|
function ESCORT:MenuROE(MenuTextFormat)
|
|
self:F(MenuTextFormat)
|
|
if not self.EscortMenuROE then
|
|
self.EscortMenuROE=MENU_CLIENT:New(self.EscortClient,"ROE",self.EscortMenu)
|
|
if self.EscortGroup:OptionROEHoldFirePossible()then
|
|
self.EscortMenuROEHoldFire=MENU_CLIENT_COMMAND:New(self.EscortClient,"Hold Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEHoldFire(),"Holding weapons!")
|
|
end
|
|
if self.EscortGroup:OptionROEReturnFirePossible()then
|
|
self.EscortMenuROEReturnFire=MENU_CLIENT_COMMAND:New(self.EscortClient,"Return Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEReturnFire(),"Returning fire!")
|
|
end
|
|
if self.EscortGroup:OptionROEOpenFirePossible()then
|
|
self.EscortMenuROEOpenFire=MENU_CLIENT_COMMAND:New(self.EscortClient,"Open Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEOpenFire(),"Opening fire on designated targets!!")
|
|
end
|
|
if self.EscortGroup:OptionROEWeaponFreePossible()then
|
|
self.EscortMenuROEWeaponFree=MENU_CLIENT_COMMAND:New(self.EscortClient,"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_CLIENT:New(self.EscortClient,"Evasion",self.EscortMenu)
|
|
if self.EscortGroup:OptionROTNoReactionPossible()then
|
|
self.EscortMenuEvasionNoReaction=MENU_CLIENT_COMMAND:New(self.EscortClient,"Fight until death",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTNoReaction(),"Fighting until death!")
|
|
end
|
|
if self.EscortGroup:OptionROTPassiveDefensePossible()then
|
|
self.EscortMenuEvasionPassiveDefense=MENU_CLIENT_COMMAND:New(self.EscortClient,"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_CLIENT_COMMAND:New(self.EscortClient,"Evade enemy fire",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTEvadeFire(),"Evading on enemy fire!")
|
|
end
|
|
if self.EscortGroup:OptionROTVerticalPossible()then
|
|
self.EscortMenuOptionEvasionVertical=MENU_CLIENT_COMMAND:New(self.EscortClient,"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_CLIENT:New(self.EscortClient,"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(DetectedItemID)
|
|
local EscortGroup=self.EscortGroup
|
|
self:E(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:GetDetectedSet(DetectedItemID)
|
|
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:GetDetectedSet(DetectedItemID)
|
|
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,DetectedItemID)
|
|
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:GetDetectedSet(DetectedItemID)
|
|
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:GetDetectedSet(DetectedItemID)
|
|
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:E(DetectedItems)
|
|
local DetectedTargets=false
|
|
local DetectedMsgs={}
|
|
for ClientEscortGroupName,EscortGroupData in pairs(self.EscortClient._EscortGroups)do
|
|
local ClientEscortTargets=EscortGroupData.Detection
|
|
for DetectedItemID,DetectedItem in pairs(DetectedItems)do
|
|
self:E({DetectedItemID,DetectedItem})
|
|
local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItemID,EscortGroupData.EscortGroup,_DATABASE:GetPlayerSettings(self.EscortClient:GetPlayerName()))
|
|
if ClientEscortGroupName==EscortGroupName then
|
|
local DetectedMsg=DetectedItemReportSummary:Text("\n")
|
|
DetectedMsgs[#DetectedMsgs+1]=DetectedMsg
|
|
self:T(DetectedMsg)
|
|
MENU_CLIENT_COMMAND:New(self.EscortClient,
|
|
DetectedMsg,
|
|
self.EscortMenuAttackNearbyTargets,
|
|
ESCORT._AttackTarget,
|
|
self,
|
|
DetectedItemID
|
|
)
|
|
else
|
|
if self.EscortMenuTargetAssistance then
|
|
local DetectedMsg=DetectedItemReportSummary:Text("\n")
|
|
self:T(DetectedMsg)
|
|
local MenuTargetAssistance=MENU_CLIENT:New(self.EscortClient,EscortGroupData.EscortName,self.EscortMenuTargetAssistance)
|
|
MENU_CLIENT_COMMAND:New(self.EscortClient,
|
|
DetectedMsg,
|
|
MenuTargetAssistance,
|
|
ESCORT._AssistTarget,
|
|
self,
|
|
EscortGroupData.EscortGroup,
|
|
DetectedItemID
|
|
)
|
|
end
|
|
end
|
|
DetectedTargets=true
|
|
end
|
|
end
|
|
self:E(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_CLIENT:New(Client,"Missile Trainer",nil)
|
|
Client.MenuMessages=MENU_CLIENT:New(Client,"Messages",Client.MainMenu)
|
|
Client.MenuOn=MENU_CLIENT_COMMAND:New(Client,"Messages On",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=true})
|
|
Client.MenuOff=MENU_CLIENT_COMMAND:New(Client,"Messages Off",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=false})
|
|
Client.MenuTracking=MENU_CLIENT:New(Client,"Tracking",Client.MainMenu)
|
|
Client.MenuTrackingToAll=MENU_CLIENT_COMMAND:New(Client,"To All",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=true})
|
|
Client.MenuTrackingToTarget=MENU_CLIENT_COMMAND:New(Client,"To Target",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=false})
|
|
Client.MenuTrackOn=MENU_CLIENT_COMMAND:New(Client,"Tracking On",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=true})
|
|
Client.MenuTrackOff=MENU_CLIENT_COMMAND:New(Client,"Tracking Off",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=false})
|
|
Client.MenuTrackIncrease=MENU_CLIENT_COMMAND:New(Client,"Frequency Increase",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=-1})
|
|
Client.MenuTrackDecrease=MENU_CLIENT_COMMAND:New(Client,"Frequency Decrease",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=1})
|
|
Client.MenuAlerts=MENU_CLIENT:New(Client,"Alerts",Client.MainMenu)
|
|
Client.MenuAlertsToAll=MENU_CLIENT_COMMAND:New(Client,"To All",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=true})
|
|
Client.MenuAlertsToTarget=MENU_CLIENT_COMMAND:New(Client,"To Target",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=false})
|
|
Client.MenuHitsOn=MENU_CLIENT_COMMAND:New(Client,"Hits On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=true})
|
|
Client.MenuHitsOff=MENU_CLIENT_COMMAND:New(Client,"Hits Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=false})
|
|
Client.MenuLaunchesOn=MENU_CLIENT_COMMAND:New(Client,"Launches On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=true})
|
|
Client.MenuLaunchesOff=MENU_CLIENT_COMMAND:New(Client,"Launches Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=false})
|
|
Client.MenuDetails=MENU_CLIENT:New(Client,"Details",Client.MainMenu)
|
|
Client.MenuDetailsDistanceOn=MENU_CLIENT_COMMAND:New(Client,"Range On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=true})
|
|
Client.MenuDetailsDistanceOff=MENU_CLIENT_COMMAND:New(Client,"Range Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=false})
|
|
Client.MenuDetailsBearingOn=MENU_CLIENT_COMMAND:New(Client,"Bearing On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=true})
|
|
Client.MenuDetailsBearingOff=MENU_CLIENT_COMMAND:New(Client,"Bearing Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=false})
|
|
Client.MenuDistance=MENU_CLIENT:New(Client,"Set distance to plane",Client.MainMenu)
|
|
Client.MenuDistance50=MENU_CLIENT_COMMAND:New(Client,"50 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=50/1000})
|
|
Client.MenuDistance100=MENU_CLIENT_COMMAND:New(Client,"100 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=100/1000})
|
|
Client.MenuDistance150=MENU_CLIENT_COMMAND:New(Client,"150 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=150/1000})
|
|
Client.MenuDistance200=MENU_CLIENT_COMMAND:New(Client,"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:E("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
|
|
AIRBASEPOLICE_BASE={
|
|
ClassName="AIRBASEPOLICE_BASE",
|
|
SetClient=nil,
|
|
Airbases=nil,
|
|
AirbaseNames=nil,
|
|
}
|
|
function AIRBASEPOLICE_BASE:New(SetClient,Airbases)
|
|
local self=BASE:Inherit(self,BASE:New())
|
|
self:E({self.ClassName,SetClient,Airbases})
|
|
self.SetClient=SetClient
|
|
self.Airbases=Airbases
|
|
for AirbaseID,Airbase in pairs(self.Airbases)do
|
|
Airbase.ZoneBoundary=ZONE_POLYGON_BASE:New("Boundary",Airbase.PointsBoundary):SmokeZone(SMOKECOLOR.White):Flush()
|
|
for PointsRunwayID,PointsRunway in pairs(Airbase.PointsRunways)do
|
|
Airbase.ZoneRunways[PointsRunwayID]=ZONE_POLYGON_BASE:New("Runway "..PointsRunwayID,PointsRunway):SmokeZone(SMOKECOLOR.Red):Flush()
|
|
end
|
|
end
|
|
self.SetClient:ForEachClient(
|
|
function(Client)
|
|
Client:SetState(self,"Speeding",false)
|
|
Client:SetState(self,"Warnings",0)
|
|
Client:SetState(self,"Taxi",false)
|
|
end
|
|
)
|
|
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{},0,2,0.05)
|
|
return self
|
|
end
|
|
function AIRBASEPOLICE_BASE:Monitor(AirbaseNames)
|
|
if AirbaseNames then
|
|
if type(AirbaseNames)=="table"then
|
|
self.AirbaseNames=AirbaseNames
|
|
else
|
|
self.AirbaseNames={AirbaseNames}
|
|
end
|
|
end
|
|
end
|
|
function AIRBASEPOLICE_BASE:_AirbaseMonitor()
|
|
for AirbaseID,Airbase in pairs(self.Airbases)do
|
|
if not self.AirbaseNames or self.AirbaseNames[AirbaseID]then
|
|
self:E(AirbaseID)
|
|
self.SetClient:ForEachClientInZone(Airbase.ZoneBoundary,
|
|
function(Client)
|
|
self:E(Client.UnitName)
|
|
if Client:IsAlive()then
|
|
local NotInRunwayZone=true
|
|
for ZoneRunwayID,ZoneRunway in pairs(Airbase.ZoneRunways)do
|
|
NotInRunwayZone=(Client:IsNotInZone(ZoneRunway)==true)and NotInRunwayZone or false
|
|
end
|
|
if NotInRunwayZone then
|
|
local Taxi=self:GetState(self,"Taxi")
|
|
self:E(Taxi)
|
|
if Taxi==false then
|
|
Client:Message("Welcome at "..AirbaseID..". The maximum taxiing speed is "..Airbase.MaximumSpeed" km/h.",20,"ATC")
|
|
self:SetState(self,"Taxi",true)
|
|
end
|
|
local VelocityVec3=Client:GetVelocity()
|
|
local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5
|
|
local Velocity=Velocity*3.6
|
|
local IsAboveRunway=Client:IsAboveRunway()
|
|
local IsOnGround=Client:InAir()==false
|
|
self:T(IsAboveRunway,IsOnGround)
|
|
if IsAboveRunway and IsOnGround then
|
|
if Velocity>Airbase.MaximumSpeed 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("You are speeding on the taxiway! Slow down or you will be removed from this airbase! Your current velocity is "..string.format("%2.0f km/h",Velocity),5,"Warning "..SpeedingWarnings.." / 3")
|
|
Client:SetState(self,"Warnings",SpeedingWarnings+1)
|
|
else
|
|
MESSAGE:New("Player "..Client:GetPlayerName().." is being damaged at the airbase, due to a speeding violation ...",10,"Airbase Police"):ToAll()
|
|
local function DestroyUntilHeavilyDamaged(Client)
|
|
local ClientCoord=Client:GetCoordinate()
|
|
ClientCoord:Explosion(100)
|
|
local Damage=Client:GetLife()
|
|
local InitialLife=Client:GetLife0()
|
|
MESSAGE:New("Player "..Client:GetPlayerName().." Damage ... "..Damage,5,"Airbase Police"):ToAll()
|
|
if(Damage/InitialLife)*100<80 then
|
|
Client:ScheduleStop(DestroyUntilHeavilyDamaged)
|
|
end
|
|
end
|
|
Client:ScheduleOnce(1,DestroyUntilHeavilyDamaged,Client)
|
|
trigger.action.setUserFlag("AIRCRAFT_"..Client:GetID(),100)
|
|
Client:SetState(self,"Speeding",false)
|
|
Client:SetState(self,"Warnings",0)
|
|
end
|
|
else
|
|
Client:Message("You are speeding on the taxiway, slow down now! Your current velocity is "..string.format("%2.0f km/h",Velocity),5,"Attention! ")
|
|
Client:SetState(self,"Speeding",true)
|
|
Client:SetState(self,"Warnings",1)
|
|
end
|
|
else
|
|
Client:SetState(self,"Speeding",false)
|
|
Client:SetState(self,"Warnings",0)
|
|
end
|
|
end
|
|
else
|
|
Client:SetState(self,"Speeding",false)
|
|
Client:SetState(self,"Warnings",0)
|
|
local Taxi=self:GetState(self,"Taxi")
|
|
if Taxi==true then
|
|
Client:Message("You have progressed to the runway ... Await take-off clearance ...",20,"ATC")
|
|
self:SetState(self,"Taxi",false)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
)
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
AIRBASEPOLICE_CAUCASUS={
|
|
ClassName="AIRBASEPOLICE_CAUCASUS",
|
|
Airbases={
|
|
AnapaVityazevo={
|
|
PointsBoundary={
|
|
[1]={["y"]=242234.85714287,["x"]=-6616.5714285726,},
|
|
[2]={["y"]=241060.57142858,["x"]=-5585.142857144,},
|
|
[3]={["y"]=243806.2857143,["x"]=-3962.2857142868,},
|
|
[4]={["y"]=245240.57142858,["x"]=-4816.5714285726,},
|
|
[5]={["y"]=244783.42857144,["x"]=-5630.8571428583,},
|
|
[6]={["y"]=243800.57142858,["x"]=-5065.142857144,},
|
|
[7]={["y"]=242232.00000001,["x"]=-6622.2857142868,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=242140.57142858,["x"]=-6478.8571428583,},
|
|
[2]={["y"]=242188.57142858,["x"]=-6522.0000000011,},
|
|
[3]={["y"]=244124.2857143,["x"]=-4344.0000000011,},
|
|
[4]={["y"]=244068.2857143,["x"]=-4296.5714285726,},
|
|
[5]={["y"]=242140.57142858,["x"]=-6480.0000000011,}
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Batumi={
|
|
PointsBoundary={
|
|
[1]={["y"]=617567.14285714,["x"]=-355313.14285715,},
|
|
[2]={["y"]=616181.42857142,["x"]=-354800.28571429,},
|
|
[3]={["y"]=616007.14285714,["x"]=-355128.85714286,},
|
|
[4]={["y"]=618230,["x"]=-356914.57142858,},
|
|
[5]={["y"]=618727.14285714,["x"]=-356166,},
|
|
[6]={["y"]=617572.85714285,["x"]=-355308.85714286,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=616442.28571429,["x"]=-355090.28571429,},
|
|
[2]={["y"]=618450.57142857,["x"]=-356522,},
|
|
[3]={["y"]=618407.71428571,["x"]=-356584.85714286,},
|
|
[4]={["y"]=618361.99999999,["x"]=-356554.85714286,},
|
|
[5]={["y"]=618324.85714285,["x"]=-356599.14285715,},
|
|
[6]={["y"]=618250.57142856,["x"]=-356543.42857143,},
|
|
[7]={["y"]=618257.7142857,["x"]=-356496.28571429,},
|
|
[8]={["y"]=618237.7142857,["x"]=-356459.14285715,},
|
|
[9]={["y"]=616555.71428571,["x"]=-355258.85714286,},
|
|
[10]={["y"]=616486.28571428,["x"]=-355280.57142858,},
|
|
[11]={["y"]=616410.57142856,["x"]=-355227.71428572,},
|
|
[12]={["y"]=616441.99999999,["x"]=-355179.14285715,},
|
|
[13]={["y"]=616401.99999999,["x"]=-355147.71428572,},
|
|
[14]={["y"]=616441.42857142,["x"]=-355092.57142858,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Beslan={
|
|
PointsBoundary={
|
|
[1]={["y"]=842082.57142857,["x"]=-148445.14285715,},
|
|
[2]={["y"]=845237.71428572,["x"]=-148639.71428572,},
|
|
[3]={["y"]=845232,["x"]=-148765.42857143,},
|
|
[4]={["y"]=844220.57142857,["x"]=-149168.28571429,},
|
|
[5]={["y"]=843274.85714286,["x"]=-149125.42857143,},
|
|
[6]={["y"]=842077.71428572,["x"]=-148554,},
|
|
[7]={["y"]=842083.42857143,["x"]=-148445.42857143,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=842104.57142857,["x"]=-148460.57142857,},
|
|
[2]={["y"]=845225.71428572,["x"]=-148656,},
|
|
[3]={["y"]=845220.57142858,["x"]=-148750,},
|
|
[4]={["y"]=842098.85714286,["x"]=-148556.28571429,},
|
|
[5]={["y"]=842104,["x"]=-148460.28571429,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Gelendzhik={
|
|
PointsBoundary={
|
|
[1]={["y"]=297856.00000001,["x"]=-51151.428571429,},
|
|
[2]={["y"]=299044.57142858,["x"]=-49720.000000001,},
|
|
[3]={["y"]=298861.71428572,["x"]=-49580.000000001,},
|
|
[4]={["y"]=298198.85714286,["x"]=-49842.857142858,},
|
|
[5]={["y"]=297990.28571429,["x"]=-50151.428571429,},
|
|
[6]={["y"]=297696.00000001,["x"]=-51054.285714286,},
|
|
[7]={["y"]=297850.28571429,["x"]=-51160.000000001,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=297834.00000001,["x"]=-51107.428571429,},
|
|
[2]={["y"]=297786.57142858,["x"]=-51068.857142858,},
|
|
[3]={["y"]=298946.57142858,["x"]=-49686.000000001,},
|
|
[4]={["y"]=298993.14285715,["x"]=-49725.714285715,},
|
|
[5]={["y"]=297835.14285715,["x"]=-51107.714285715,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Gudauta={
|
|
PointsBoundary={
|
|
[1]={["y"]=517246.57142857,["x"]=-197850.28571429,},
|
|
[2]={["y"]=516749.42857142,["x"]=-198070.28571429,},
|
|
[3]={["y"]=515755.14285714,["x"]=-197598.85714286,},
|
|
[4]={["y"]=515369.42857142,["x"]=-196538.85714286,},
|
|
[5]={["y"]=515623.71428571,["x"]=-195618.85714286,},
|
|
[6]={["y"]=515946.57142857,["x"]=-195510.28571429,},
|
|
[7]={["y"]=517243.71428571,["x"]=-197858.85714286,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=517096.57142857,["x"]=-197804.57142857,},
|
|
[2]={["y"]=515880.85714285,["x"]=-195590.28571429,},
|
|
[3]={["y"]=515812.28571428,["x"]=-195628.85714286,},
|
|
[4]={["y"]=517036.57142857,["x"]=-197834.57142857,},
|
|
[5]={["y"]=517097.99999999,["x"]=-197807.42857143,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Kobuleti={
|
|
PointsBoundary={
|
|
[1]={["y"]=634427.71428571,["x"]=-318290.28571429,},
|
|
[2]={["y"]=635033.42857143,["x"]=-317550.2857143,},
|
|
[3]={["y"]=635864.85714286,["x"]=-317333.14285715,},
|
|
[4]={["y"]=636967.71428571,["x"]=-317261.71428572,},
|
|
[5]={["y"]=637144.85714286,["x"]=-317913.14285715,},
|
|
[6]={["y"]=634630.57142857,["x"]=-318687.42857144,},
|
|
[7]={["y"]=634424.85714286,["x"]=-318290.2857143,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=634509.71428571,["x"]=-318339.42857144,},
|
|
[2]={["y"]=636767.42857143,["x"]=-317516.57142858,},
|
|
[3]={["y"]=636790,["x"]=-317575.71428572,},
|
|
[4]={["y"]=634531.42857143,["x"]=-318398.00000001,},
|
|
[5]={["y"]=634510.28571429,["x"]=-318339.71428572,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
KrasnodarCenter={
|
|
PointsBoundary={
|
|
[1]={["y"]=366680.28571429,["x"]=11699.142857142,},
|
|
[2]={["y"]=366654.28571429,["x"]=11225.142857142,},
|
|
[3]={["y"]=367497.14285715,["x"]=11082.285714285,},
|
|
[4]={["y"]=368025.71428572,["x"]=10396.57142857,},
|
|
[5]={["y"]=369854.28571429,["x"]=11367.999999999,},
|
|
[6]={["y"]=369840.00000001,["x"]=11910.857142856,},
|
|
[7]={["y"]=366682.57142858,["x"]=11697.999999999,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=369205.42857144,["x"]=11789.142857142,},
|
|
[2]={["y"]=369209.71428572,["x"]=11714.857142856,},
|
|
[3]={["y"]=366699.71428572,["x"]=11581.714285713,},
|
|
[4]={["y"]=366698.28571429,["x"]=11659.142857142,},
|
|
[5]={["y"]=369208.85714286,["x"]=11788.57142857,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
KrasnodarPashkovsky={
|
|
PointsBoundary={
|
|
[1]={["y"]=386754,["x"]=6476.5714285703,},
|
|
[2]={["y"]=389182.57142858,["x"]=8722.2857142846,},
|
|
[3]={["y"]=388832.57142858,["x"]=9086.5714285703,},
|
|
[4]={["y"]=386961.14285715,["x"]=7707.9999999989,},
|
|
[5]={["y"]=385404,["x"]=9179.4285714274,},
|
|
[6]={["y"]=383239.71428572,["x"]=7386.5714285703,},
|
|
[7]={["y"]=383954,["x"]=6486.5714285703,},
|
|
[8]={["y"]=385775.42857143,["x"]=8097.9999999989,},
|
|
[9]={["y"]=386804,["x"]=7319.4285714274,},
|
|
[10]={["y"]=386375.42857143,["x"]=6797.9999999989,},
|
|
[11]={["y"]=386746.85714286,["x"]=6472.2857142846,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=385891.14285715,["x"]=8416.5714285703,},
|
|
[2]={["y"]=385842.28571429,["x"]=8467.9999999989,},
|
|
[3]={["y"]=384180.85714286,["x"]=6917.1428571417,},
|
|
[4]={["y"]=384228.57142858,["x"]=6867.7142857132,},
|
|
[5]={["y"]=385891.14285715,["x"]=8416.5714285703,},
|
|
},
|
|
[2]={
|
|
[1]={["y"]=386714.85714286,["x"]=6674.857142856,},
|
|
[2]={["y"]=386757.71428572,["x"]=6627.7142857132,},
|
|
[3]={["y"]=389028.57142858,["x"]=8741.4285714275,},
|
|
[4]={["y"]=388981.71428572,["x"]=8790.5714285703,},
|
|
[5]={["y"]=386714.57142858,["x"]=6674.5714285703,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Krymsk={
|
|
PointsBoundary={
|
|
[1]={["y"]=293338.00000001,["x"]=-7575.4285714297,},
|
|
[2]={["y"]=295199.42857144,["x"]=-5434.0000000011,},
|
|
[3]={["y"]=295595.14285715,["x"]=-6239.7142857154,},
|
|
[4]={["y"]=294152.2857143,["x"]=-8325.4285714297,},
|
|
[5]={["y"]=293345.14285715,["x"]=-7596.8571428582,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=293522.00000001,["x"]=-7567.4285714297,},
|
|
[2]={["y"]=293578.57142858,["x"]=-7616.0000000011,},
|
|
[3]={["y"]=295246.00000001,["x"]=-5591.142857144,},
|
|
[4]={["y"]=295187.71428573,["x"]=-5546.0000000011,},
|
|
[5]={["y"]=293523.14285715,["x"]=-7568.2857142868,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Kutaisi={
|
|
PointsBoundary={
|
|
[1]={["y"]=682087.42857143,["x"]=-284512.85714286,},
|
|
[2]={["y"]=685387.42857143,["x"]=-283662.85714286,},
|
|
[3]={["y"]=685294.57142857,["x"]=-284977.14285715,},
|
|
[4]={["y"]=682744.57142857,["x"]=-286505.71428572,},
|
|
[5]={["y"]=682094.57142857,["x"]=-284527.14285715,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=682638,["x"]=-285202.28571429,},
|
|
[2]={["y"]=685050.28571429,["x"]=-284507.42857144,},
|
|
[3]={["y"]=685068.85714286,["x"]=-284578.85714286,},
|
|
[4]={["y"]=682657.42857143,["x"]=-285264.28571429,},
|
|
[5]={["y"]=682638.28571429,["x"]=-285202.85714286,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
MaykopKhanskaya={
|
|
PointsBoundary={
|
|
[1]={["y"]=456876.28571429,["x"]=-27665.42857143,},
|
|
[2]={["y"]=457800,["x"]=-28392.857142858,},
|
|
[3]={["y"]=459368.57142857,["x"]=-26378.571428573,},
|
|
[4]={["y"]=459425.71428572,["x"]=-25242.857142858,},
|
|
[5]={["y"]=458961.42857143,["x"]=-24964.285714287,},
|
|
[6]={["y"]=456878.57142857,["x"]=-27667.714285715,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=457005.42857143,["x"]=-27668.000000001,},
|
|
[2]={["y"]=459028.85714286,["x"]=-25168.857142858,},
|
|
[3]={["y"]=459082.57142857,["x"]=-25216.857142858,},
|
|
[4]={["y"]=457060,["x"]=-27714.285714287,},
|
|
[5]={["y"]=457004.57142857,["x"]=-27669.714285715,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
MineralnyeVody={
|
|
PointsBoundary={
|
|
[1]={["y"]=703857.14285714,["x"]=-50226.000000002,},
|
|
[2]={["y"]=707385.71428571,["x"]=-51911.714285716,},
|
|
[3]={["y"]=707595.71428571,["x"]=-51434.857142859,},
|
|
[4]={["y"]=707900,["x"]=-51568.857142859,},
|
|
[5]={["y"]=707542.85714286,["x"]=-52326.000000002,},
|
|
[6]={["y"]=706628.57142857,["x"]=-52568.857142859,},
|
|
[7]={["y"]=705142.85714286,["x"]=-51790.285714288,},
|
|
[8]={["y"]=703678.57142857,["x"]=-50611.714285716,},
|
|
[9]={["y"]=703857.42857143,["x"]=-50226.857142859,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=703904,["x"]=-50352.571428573,},
|
|
[2]={["y"]=707596.28571429,["x"]=-52094.571428573,},
|
|
[3]={["y"]=707560.57142858,["x"]=-52161.714285716,},
|
|
[4]={["y"]=703871.71428572,["x"]=-50420.571428573,},
|
|
[5]={["y"]=703902,["x"]=-50352.000000002,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Mozdok={
|
|
PointsBoundary={
|
|
[1]={["y"]=832123.42857143,["x"]=-83608.571428573,},
|
|
[2]={["y"]=835916.28571429,["x"]=-83144.285714288,},
|
|
[3]={["y"]=835474.28571429,["x"]=-84170.571428573,},
|
|
[4]={["y"]=832911.42857143,["x"]=-84470.571428573,},
|
|
[5]={["y"]=832487.71428572,["x"]=-85565.714285716,},
|
|
[6]={["y"]=831573.42857143,["x"]=-85351.42857143,},
|
|
[7]={["y"]=832123.71428572,["x"]=-83610.285714288,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=832201.14285715,["x"]=-83699.428571431,},
|
|
[2]={["y"]=832212.57142857,["x"]=-83780.571428574,},
|
|
[3]={["y"]=835730.28571429,["x"]=-83335.714285717,},
|
|
[4]={["y"]=835718.85714286,["x"]=-83246.571428574,},
|
|
[5]={["y"]=832200.57142857,["x"]=-83700.000000002,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Nalchik={
|
|
PointsBoundary={
|
|
[1]={["y"]=759370,["x"]=-125502.85714286,},
|
|
[2]={["y"]=761384.28571429,["x"]=-124177.14285714,},
|
|
[3]={["y"]=761472.85714286,["x"]=-124325.71428572,},
|
|
[4]={["y"]=761092.85714286,["x"]=-125048.57142857,},
|
|
[5]={["y"]=760295.71428572,["x"]=-125685.71428572,},
|
|
[6]={["y"]=759444.28571429,["x"]=-125734.28571429,},
|
|
[7]={["y"]=759375.71428572,["x"]=-125511.42857143,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=759454.28571429,["x"]=-125551.42857143,},
|
|
[2]={["y"]=759492.85714286,["x"]=-125610.85714286,},
|
|
[3]={["y"]=761406.28571429,["x"]=-124304.28571429,},
|
|
[4]={["y"]=761361.14285714,["x"]=-124239.71428572,},
|
|
[5]={["y"]=759456,["x"]=-125552.57142857,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Novorossiysk={
|
|
PointsBoundary={
|
|
[1]={["y"]=278677.71428573,["x"]=-41656.571428572,},
|
|
[2]={["y"]=278446.2857143,["x"]=-41453.714285715,},
|
|
[3]={["y"]=278989.14285716,["x"]=-40188.000000001,},
|
|
[4]={["y"]=279717.71428573,["x"]=-39968.000000001,},
|
|
[5]={["y"]=280020.57142859,["x"]=-40208.000000001,},
|
|
[6]={["y"]=278674.85714287,["x"]=-41660.857142858,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=278673.14285716,["x"]=-41615.142857144,},
|
|
[2]={["y"]=278625.42857144,["x"]=-41570.571428572,},
|
|
[3]={["y"]=279835.42857144,["x"]=-40226.000000001,},
|
|
[4]={["y"]=279882.2857143,["x"]=-40270.000000001,},
|
|
[5]={["y"]=278672.00000001,["x"]=-41614.857142858,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
SenakiKolkhi={
|
|
PointsBoundary={
|
|
[1]={["y"]=646036.57142857,["x"]=-281778.85714286,},
|
|
[2]={["y"]=646045.14285714,["x"]=-281191.71428571,},
|
|
[3]={["y"]=647032.28571429,["x"]=-280598.85714285,},
|
|
[4]={["y"]=647669.42857143,["x"]=-281273.14285714,},
|
|
[5]={["y"]=648323.71428571,["x"]=-281370.28571428,},
|
|
[6]={["y"]=648520.85714286,["x"]=-281978.85714285,},
|
|
[7]={["y"]=646039.42857143,["x"]=-281783.14285714,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=646060.85714285,["x"]=-281736,},
|
|
[2]={["y"]=646056.57142857,["x"]=-281631.71428571,},
|
|
[3]={["y"]=648442.28571428,["x"]=-281840.28571428,},
|
|
[4]={["y"]=648432.28571428,["x"]=-281918.85714286,},
|
|
[5]={["y"]=646063.71428571,["x"]=-281738.85714286,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
SochiAdler={
|
|
PointsBoundary={
|
|
[1]={["y"]=460642.28571428,["x"]=-164861.71428571,},
|
|
[2]={["y"]=462820.85714285,["x"]=-163368.85714286,},
|
|
[3]={["y"]=463649.42857142,["x"]=-163340.28571429,},
|
|
[4]={["y"]=463835.14285714,["x"]=-164040.28571429,},
|
|
[5]={["y"]=462535.14285714,["x"]=-165654.57142857,},
|
|
[6]={["y"]=460678,["x"]=-165247.42857143,},
|
|
[7]={["y"]=460635.14285714,["x"]=-164876,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=460831.42857143,["x"]=-165180,},
|
|
[2]={["y"]=460878.57142857,["x"]=-165257.14285714,},
|
|
[3]={["y"]=463663.71428571,["x"]=-163793.14285714,},
|
|
[4]={["y"]=463612.28571428,["x"]=-163697.42857143,},
|
|
[5]={["y"]=460831.42857143,["x"]=-165177.14285714,},
|
|
},
|
|
[2]={
|
|
[1]={["y"]=460831.42857143,["x"]=-165180,},
|
|
[2]={["y"]=460878.57142857,["x"]=-165257.14285714,},
|
|
[3]={["y"]=463663.71428571,["x"]=-163793.14285714,},
|
|
[4]={["y"]=463612.28571428,["x"]=-163697.42857143,},
|
|
[5]={["y"]=460831.42857143,["x"]=-165177.14285714,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Soganlug={
|
|
PointsBoundary={
|
|
[1]={["y"]=894530.85714286,["x"]=-316928.28571428,},
|
|
[2]={["y"]=896422.28571428,["x"]=-318622.57142857,},
|
|
[3]={["y"]=896090.85714286,["x"]=-318934,},
|
|
[4]={["y"]=894019.42857143,["x"]=-317119.71428571,},
|
|
[5]={["y"]=894533.71428571,["x"]=-316925.42857143,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=894525.71428571,["x"]=-316964,},
|
|
[2]={["y"]=896363.14285714,["x"]=-318634.28571428,},
|
|
[3]={["y"]=896299.14285714,["x"]=-318702.85714286,},
|
|
[4]={["y"]=894464,["x"]=-317031.71428571,},
|
|
[5]={["y"]=894524.57142857,["x"]=-316963.71428571,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
SukhumiBabushara={
|
|
PointsBoundary={
|
|
[1]={["y"]=562541.14285714,["x"]=-219852.28571429,},
|
|
[2]={["y"]=562691.14285714,["x"]=-219395.14285714,},
|
|
[3]={["y"]=564326.85714286,["x"]=-219523.71428571,},
|
|
[4]={["y"]=566262.57142857,["x"]=-221166.57142857,},
|
|
[5]={["y"]=566069.71428571,["x"]=-221580.85714286,},
|
|
[6]={["y"]=562534,["x"]=-219873.71428571,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=562684,["x"]=-219779.71428571,},
|
|
[2]={["y"]=562717.71428571,["x"]=-219718,},
|
|
[3]={["y"]=566046.85714286,["x"]=-221376.57142857,},
|
|
[4]={["y"]=566012.28571428,["x"]=-221446.57142857,},
|
|
[5]={["y"]=562684.57142857,["x"]=-219782.57142857,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
TbilisiLochini={
|
|
PointsBoundary={
|
|
[1]={["y"]=895172.85714286,["x"]=-314667.42857143,},
|
|
[2]={["y"]=895337.42857143,["x"]=-314143.14285714,},
|
|
[3]={["y"]=895990.28571429,["x"]=-314036,},
|
|
[4]={["y"]=897730.28571429,["x"]=-315284.57142857,},
|
|
[5]={["y"]=897901.71428571,["x"]=-316284.57142857,},
|
|
[6]={["y"]=897684.57142857,["x"]=-316618.85714286,},
|
|
[7]={["y"]=895173.14285714,["x"]=-314667.42857143,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=895261.14285715,["x"]=-314652.28571428,},
|
|
[2]={["y"]=897654.57142857,["x"]=-316523.14285714,},
|
|
[3]={["y"]=897711.71428571,["x"]=-316450.28571429,},
|
|
[4]={["y"]=895327.42857143,["x"]=-314568.85714286,},
|
|
[5]={["y"]=895261.71428572,["x"]=-314656,},
|
|
},
|
|
[2]={
|
|
[1]={["y"]=895605.71428572,["x"]=-314724.57142857,},
|
|
[2]={["y"]=897639.71428572,["x"]=-316148,},
|
|
[3]={["y"]=897683.42857143,["x"]=-316087.14285714,},
|
|
[4]={["y"]=895650,["x"]=-314660,},
|
|
[5]={["y"]=895606,["x"]=-314724.85714286,}
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Vaziani={
|
|
PointsBoundary={
|
|
[1]={["y"]=902122,["x"]=-318163.71428572,},
|
|
[2]={["y"]=902678.57142857,["x"]=-317594,},
|
|
[3]={["y"]=903275.71428571,["x"]=-317405.42857143,},
|
|
[4]={["y"]=903418.57142857,["x"]=-317891.14285714,},
|
|
[5]={["y"]=904292.85714286,["x"]=-318748.28571429,},
|
|
[6]={["y"]=904542,["x"]=-319740.85714286,},
|
|
[7]={["y"]=904042,["x"]=-320166.57142857,},
|
|
[8]={["y"]=902121.42857143,["x"]=-318164.85714286,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=902239.14285714,["x"]=-318190.85714286,},
|
|
[2]={["y"]=904014.28571428,["x"]=-319994.57142857,},
|
|
[3]={["y"]=904064.85714285,["x"]=-319945.14285715,},
|
|
[4]={["y"]=902294.57142857,["x"]=-318146,},
|
|
[5]={["y"]=902247.71428571,["x"]=-318190.85714286,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
},
|
|
}
|
|
function AIRBASEPOLICE_CAUCASUS:New(SetClient)
|
|
local self=BASE:Inherit(self,AIRBASEPOLICE_BASE:New(SetClient,self.Airbases))
|
|
return self
|
|
end
|
|
AIRBASEPOLICE_NEVADA={
|
|
ClassName="AIRBASEPOLICE_NEVADA",
|
|
Airbases={
|
|
Nellis={
|
|
PointsBoundary={
|
|
[1]={["y"]=-17814.714285714,["x"]=-399823.14285714,},
|
|
[2]={["y"]=-16875.857142857,["x"]=-398763.14285714,},
|
|
[3]={["y"]=-16251.571428571,["x"]=-398988.85714286,},
|
|
[4]={["y"]=-16163,["x"]=-398693.14285714,},
|
|
[5]={["y"]=-16328.714285714,["x"]=-398034.57142857,},
|
|
[6]={["y"]=-15943,["x"]=-397571.71428571,},
|
|
[7]={["y"]=-15711.571428571,["x"]=-397551.71428571,},
|
|
[8]={["y"]=-15748.714285714,["x"]=-396806,},
|
|
[9]={["y"]=-16288.714285714,["x"]=-396517.42857143,},
|
|
[10]={["y"]=-16751.571428571,["x"]=-396308.85714286,},
|
|
[11]={["y"]=-17263,["x"]=-396234.57142857,},
|
|
[12]={["y"]=-17577.285714286,["x"]=-396640.28571429,},
|
|
[13]={["y"]=-17614.428571429,["x"]=-397400.28571429,},
|
|
[14]={["y"]=-19405.857142857,["x"]=-399428.85714286,},
|
|
[15]={["y"]=-19234.428571429,["x"]=-399683.14285714,},
|
|
[16]={["y"]=-18708.714285714,["x"]=-399408.85714286,},
|
|
[17]={["y"]=-18397.285714286,["x"]=-399657.42857143,},
|
|
[18]={["y"]=-17814.428571429,["x"]=-399823.42857143,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=-18687,["x"]=-399380.28571429,},
|
|
[2]={["y"]=-18620.714285714,["x"]=-399436.85714286,},
|
|
[3]={["y"]=-16217.857142857,["x"]=-396596.85714286,},
|
|
[4]={["y"]=-16300.142857143,["x"]=-396530,},
|
|
[5]={["y"]=-18687,["x"]=-399380.85714286,},
|
|
},
|
|
[2]={
|
|
[1]={["y"]=-18451.571428572,["x"]=-399580.57142857,},
|
|
[2]={["y"]=-18392.142857143,["x"]=-399628.57142857,},
|
|
[3]={["y"]=-16011,["x"]=-396806.85714286,},
|
|
[4]={["y"]=-16074.714285714,["x"]=-396751.71428572,},
|
|
[5]={["y"]=-18451.571428572,["x"]=-399580.85714285,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
McCarran={
|
|
PointsBoundary={
|
|
[1]={["y"]=-29455.285714286,["x"]=-416277.42857142,},
|
|
[2]={["y"]=-28860.142857143,["x"]=-416492,},
|
|
[3]={["y"]=-25044.428571429,["x"]=-416344.85714285,},
|
|
[4]={["y"]=-24580.142857143,["x"]=-415959.14285714,},
|
|
[5]={["y"]=-25073,["x"]=-415630.57142857,},
|
|
[6]={["y"]=-25087.285714286,["x"]=-415130.57142857,},
|
|
[7]={["y"]=-25830.142857143,["x"]=-414866.28571428,},
|
|
[8]={["y"]=-26658.714285715,["x"]=-414880.57142857,},
|
|
[9]={["y"]=-26973,["x"]=-415273.42857142,},
|
|
[10]={["y"]=-27380.142857143,["x"]=-415187.71428571,},
|
|
[11]={["y"]=-27715.857142857,["x"]=-414144.85714285,},
|
|
[12]={["y"]=-27551.571428572,["x"]=-413473.42857142,},
|
|
[13]={["y"]=-28630.142857143,["x"]=-413201.99999999,},
|
|
[14]={["y"]=-29494.428571429,["x"]=-415437.71428571,},
|
|
[15]={["y"]=-29455.571428572,["x"]=-416277.71428571,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=-29408.428571429,["x"]=-416016.28571428,},
|
|
[2]={["y"]=-29408.142857144,["x"]=-416105.42857142,},
|
|
[3]={["y"]=-24680.714285715,["x"]=-416003.14285713,},
|
|
[4]={["y"]=-24681.857142858,["x"]=-415926.57142856,},
|
|
[5]={["y"]=-29408.42857143,["x"]=-416016.57142856,},
|
|
},
|
|
[2]={
|
|
[1]={["y"]=-28575.571428572,["x"]=-416303.14285713,},
|
|
[2]={["y"]=-28575.571428572,["x"]=-416382.57142856,},
|
|
[3]={["y"]=-25111.000000001,["x"]=-416309.7142857,},
|
|
[4]={["y"]=-25111.000000001,["x"]=-416249.14285713,},
|
|
[5]={["y"]=-28575.571428572,["x"]=-416303.7142857,},
|
|
},
|
|
[3]={
|
|
[1]={["y"]=-29331.000000001,["x"]=-416275.42857141,},
|
|
[2]={["y"]=-29259.000000001,["x"]=-416306.85714284,},
|
|
[3]={["y"]=-28005.571428572,["x"]=-413449.7142857,},
|
|
[4]={["y"]=-28068.714285715,["x"]=-413422.85714284,},
|
|
[5]={["y"]=-29331.000000001,["x"]=-416275.7142857,},
|
|
},
|
|
[4]={
|
|
[1]={["y"]=-29073.285714286,["x"]=-416386.57142856,},
|
|
[2]={["y"]=-28997.285714286,["x"]=-416417.42857141,},
|
|
[3]={["y"]=-27697.571428572,["x"]=-413464.57142856,},
|
|
[4]={["y"]=-27767.857142858,["x"]=-413434.28571427,},
|
|
[5]={["y"]=-29073.000000001,["x"]=-416386.85714284,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
Creech={
|
|
PointsBoundary={
|
|
[1]={["y"]=-74522.714285715,["x"]=-360887.99999998,},
|
|
[2]={["y"]=-74197,["x"]=-360556.57142855,},
|
|
[3]={["y"]=-74402.714285715,["x"]=-359639.42857141,},
|
|
[4]={["y"]=-74637,["x"]=-359279.42857141,},
|
|
[5]={["y"]=-75759.857142857,["x"]=-359005.14285712,},
|
|
[6]={["y"]=-75834.142857143,["x"]=-359045.14285712,},
|
|
[7]={["y"]=-75902.714285714,["x"]=-359782.28571427,},
|
|
[8]={["y"]=-76099.857142857,["x"]=-360399.42857141,},
|
|
[9]={["y"]=-77314.142857143,["x"]=-360219.42857141,},
|
|
[10]={["y"]=-77728.428571429,["x"]=-360445.14285713,},
|
|
[11]={["y"]=-77585.571428571,["x"]=-360585.14285713,},
|
|
[12]={["y"]=-76471.285714286,["x"]=-360819.42857141,},
|
|
[13]={["y"]=-76325.571428571,["x"]=-360942.28571427,},
|
|
[14]={["y"]=-74671.857142857,["x"]=-360927.7142857,},
|
|
[15]={["y"]=-74522.714285714,["x"]=-360888.85714284,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=-74237.571428571,["x"]=-360591.7142857,},
|
|
[2]={["y"]=-74234.428571429,["x"]=-360493.71428571,},
|
|
[3]={["y"]=-77605.285714286,["x"]=-360399.14285713,},
|
|
[4]={["y"]=-77608.714285715,["x"]=-360498.85714285,},
|
|
[5]={["y"]=-74237.857142857,["x"]=-360591.7142857,},
|
|
},
|
|
[2]={
|
|
[1]={["y"]=-75807.571428572,["x"]=-359073.42857142,},
|
|
[2]={["y"]=-74770.142857144,["x"]=-360581.71428571,},
|
|
[3]={["y"]=-74641.285714287,["x"]=-360585.42857142,},
|
|
[4]={["y"]=-75734.142857144,["x"]=-359023.14285714,},
|
|
[5]={["y"]=-75807.285714287,["x"]=-359073.42857142,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
GroomLake={
|
|
PointsBoundary={
|
|
[1]={["y"]=-88916.714285714,["x"]=-289102.28571425,},
|
|
[2]={["y"]=-87023.571428572,["x"]=-290388.57142857,},
|
|
[3]={["y"]=-85916.428571429,["x"]=-290674.28571428,},
|
|
[4]={["y"]=-87645.000000001,["x"]=-286567.14285714,},
|
|
[5]={["y"]=-88380.714285715,["x"]=-286388.57142857,},
|
|
[6]={["y"]=-89670.714285715,["x"]=-283524.28571428,},
|
|
[7]={["y"]=-89797.857142858,["x"]=-283567.14285714,},
|
|
[8]={["y"]=-88635.000000001,["x"]=-286749.99999999,},
|
|
[9]={["y"]=-89177.857142858,["x"]=-287207.14285714,},
|
|
[10]={["y"]=-89092.142857144,["x"]=-288892.85714285,},
|
|
[11]={["y"]=-88917.000000001,["x"]=-289102.85714285,},
|
|
},
|
|
PointsRunways={
|
|
[1]={
|
|
[1]={["y"]=-86039.000000001,["x"]=-290606.28571428,},
|
|
[2]={["y"]=-85965.285714287,["x"]=-290573.99999999,},
|
|
[3]={["y"]=-87692.714285715,["x"]=-286634.85714285,},
|
|
[4]={["y"]=-87756.714285715,["x"]=-286663.99999999,},
|
|
[5]={["y"]=-86038.714285715,["x"]=-290606.85714285,},
|
|
},
|
|
[2]={
|
|
[1]={["y"]=-86808.428571429,["x"]=-290375.7142857,},
|
|
[2]={["y"]=-86732.714285715,["x"]=-290344.28571427,},
|
|
[3]={["y"]=-89672.714285714,["x"]=-283546.57142855,},
|
|
[4]={["y"]=-89772.142857143,["x"]=-283587.71428569,},
|
|
[5]={["y"]=-86808.142857143,["x"]=-290375.7142857,},
|
|
},
|
|
},
|
|
ZoneBoundary={},
|
|
ZoneRunways={},
|
|
MaximumSpeed=50,
|
|
},
|
|
},
|
|
}
|
|
function AIRBASEPOLICE_NEVADA:New(SetClient)
|
|
local self=BASE:Inherit(self,AIRBASEPOLICE_BASE:New(SetClient,self.Airbases))
|
|
end
|
|
do
|
|
DETECTION_BASE={
|
|
ClassName="DETECTION_BASE",
|
|
DetectionSetGroup=nil,
|
|
DetectionRange=nil,
|
|
DetectedObjects={},
|
|
DetectionRun=0,
|
|
DetectedObjectsIdentified={},
|
|
DetectedItems={},
|
|
}
|
|
function DETECTION_BASE:New(DetectionSetGroup)
|
|
local self=BASE:Inherit(self,FSM:New())
|
|
self.DetectedItemCount=0
|
|
self.DetectedItemMax=0
|
|
self.DetectedItems={}
|
|
self.DetectionSetGroup=DetectionSetGroup
|
|
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","DetectionGroup","Detecting")
|
|
self:AddTransition("Detecting","Detected","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)
|
|
self:E({From,Event,To})
|
|
local DetectDelay=0.1
|
|
self.DetectionCount=0
|
|
self.DetectionRun=0
|
|
self:UnIdentifyAllDetectedObjects()
|
|
local DetectionTimeStamp=timer.getTime()
|
|
for DetectionGroupID,DetectionGroupData in pairs(self.DetectionSetGroup:GetSet())do
|
|
self:__DetectionGroup(DetectDelay,DetectionGroupData,DetectionTimeStamp)
|
|
self.DetectionCount=self.DetectionCount+1
|
|
DetectDelay=DetectDelay+1
|
|
end
|
|
end
|
|
function DETECTION_BASE:onafterDetectionGroup(From,Event,To,DetectionGroup,DetectionTimeStamp)
|
|
self:E({From,Event,To})
|
|
self.DetectionRun=self.DetectionRun+1
|
|
local HasDetectedObjects=false
|
|
if DetectionGroup:IsAlive()then
|
|
self:T({"DetectionGroup is Alive",DetectionGroup:GetName()})
|
|
local DetectionGroupName=DetectionGroup:GetName()
|
|
local DetectionUnit=DetectionGroup:GetUnit(1)
|
|
local DetectedUnits={}
|
|
local DetectedTargets=DetectionGroup:GetDetectedTargets(
|
|
self.DetectVisual,
|
|
self.DetectOptical,
|
|
self.DetectRadar,
|
|
self.DetectIRST,
|
|
self.DetectRWR,
|
|
self.DetectDLINK
|
|
)
|
|
self:F(DetectedTargets)
|
|
for DetectionObjectID,Detection in pairs(DetectedTargets)do
|
|
local DetectedObject=Detection.object
|
|
if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then
|
|
local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity=DetectionUnit:IsTargetDetected(
|
|
DetectedObject,
|
|
self.DetectVisual,
|
|
self.DetectOptical,
|
|
self.DetectRadar,
|
|
self.DetectIRST,
|
|
self.DetectRWR,
|
|
self.DetectDLINK
|
|
)
|
|
self:T2({TargetIsDetected=TargetIsDetected,TargetIsVisible=TargetIsVisible,TargetLastTime=TargetLastTime,TargetKnowType=TargetKnowType,TargetKnowDistance=TargetKnowDistance,TargetLastPos=TargetLastPos,TargetLastVelocity=TargetLastVelocity})
|
|
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=DetectionGroup:GetVec3()
|
|
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
|
|
self:F({"Detected Target:",DetectionGroupName,DetectedObjectName,DetectedObjectType,Distance,DetectedUnitCategory})
|
|
DetectionAccepted=self._.FilterCategories[DetectedUnitCategory]~=nil and DetectionAccepted or false
|
|
if self.AcceptRange and Distance>self.AcceptRange then
|
|
DetectionAccepted=false
|
|
end
|
|
if self.AcceptZones then
|
|
for AcceptZoneID,AcceptZone in pairs(self.AcceptZones)do
|
|
local AcceptZone=AcceptZone
|
|
if AcceptZone:IsVec2InZone(DetectedObjectVec2)==false then
|
|
DetectionAccepted=false
|
|
end
|
|
end
|
|
end
|
|
if self.RejectZones then
|
|
for RejectZoneID,RejectZone in pairs(self.RejectZones)do
|
|
local RejectZone=RejectZone
|
|
if RejectZone:IsPointVec2InZone(DetectedObjectVec2)==true then
|
|
DetectionAccepted=false
|
|
end
|
|
end
|
|
end
|
|
if not self.DetectedObjects[DetectedObjectName]and Detection.visible 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()
|
|
self:T({Probability,DistanceProbability})
|
|
if Probability>DistanceProbability then
|
|
DetectionAccepted=false
|
|
end
|
|
end
|
|
if not self.DetectedObjects[DetectedObjectName]and Detection.visible 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()
|
|
self:T({Probability,AlphaAngleProbability})
|
|
if Probability>AlphaAngleProbability then
|
|
DetectionAccepted=false
|
|
end
|
|
end
|
|
if not self.DetectedObjects[DetectedObjectName]and Detection.visible and self.ZoneProbability then
|
|
for ZoneDataID,ZoneData in pairs(self.ZoneProbability)do
|
|
self:E({ZoneData})
|
|
local ZoneObject=ZoneData[1]
|
|
local ZoneProbability=ZoneData[2]
|
|
ZoneProbability=ZoneProbability*30/300
|
|
if ZoneObject:IsPointVec2InZone(DetectedObjectVec2)==true then
|
|
local Probability=math.random()
|
|
self:T({Probability,ZoneProbability})
|
|
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
|
|
self.DetectedObjects[DetectedObjectName].IsDetected=TargetIsDetected
|
|
self.DetectedObjects[DetectedObjectName].IsVisible=TargetIsVisible
|
|
self.DetectedObjects[DetectedObjectName].LastTime=TargetLastTime
|
|
self.DetectedObjects[DetectedObjectName].LastPos=TargetLastPos
|
|
self.DetectedObjects[DetectedObjectName].LastVelocity=TargetLastVelocity
|
|
self.DetectedObjects[DetectedObjectName].KnowType=TargetKnowType
|
|
self.DetectedObjects[DetectedObjectName].KnowDistance=Detection.distance
|
|
self.DetectedObjects[DetectedObjectName].Distance=Distance
|
|
self.DetectedObjects[DetectedObjectName].DetectionTimeStamp=DetectionTimeStamp
|
|
self:F({DetectedObject=self.DetectedObjects[DetectedObjectName]})
|
|
local DetectedUnit=UNIT:FindByName(DetectedObjectName)
|
|
DetectedUnits[DetectedObjectName]=DetectedUnit
|
|
else
|
|
if self.DetectedObjects[DetectedObjectName]then
|
|
self.DetectedObjects[DetectedObjectName]=nil
|
|
end
|
|
end
|
|
end
|
|
self:T2(self.DetectedObjects)
|
|
end
|
|
if HasDetectedObjects then
|
|
self:__Detected(0.1,DetectedUnits)
|
|
end
|
|
end
|
|
if self.DetectionCount>0 and self.DetectionRun==self.DetectionCount then
|
|
self:T("--> Create Detection Sets")
|
|
for DetectedObjectName,DetectedObject in pairs(self.DetectedObjects)do
|
|
if self.DetectedObjects[DetectedObjectName].IsDetected==true and self.DetectedObjects[DetectedObjectName].DetectionTimeStamp+60<=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)
|
|
end
|
|
self:__Detect(self.RefreshTimeInterval)
|
|
end
|
|
end
|
|
end
|
|
do
|
|
function DETECTION_BASE:CleanDetectionItem(DetectedItem,DetectedItemID)
|
|
self:F2()
|
|
local DetectedSet=DetectedItem.Set
|
|
if DetectedSet:Count()==0 then
|
|
self:RemoveDetectedItem(DetectedItemID)
|
|
end
|
|
return self
|
|
end
|
|
function DETECTION_BASE:ForgetDetectedUnit(UnitName)
|
|
self:F2()
|
|
local DetectedItems=self:GetDetectedItems()
|
|
for DetectedItemIndex,DetectedItem in pairs(DetectedItems)do
|
|
local DetectedSet=self:GetDetectedSet(DetectedItemIndex)
|
|
if DetectedSet then
|
|
DetectedSet:RemoveUnitsByName(UnitName)
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
function DETECTION_BASE:CreateDetectionItems()
|
|
self:F2()
|
|
self:E("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:E({"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:E({"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:E({"Change on Detection Item:",DetectedItem.ID,ChangeCode,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:E({"Change on Detection Item:",DetectedItem.ID,ChangeCode,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)
|
|
return DetectedItem.FriendliesNearBy~=nil or false
|
|
end
|
|
function DETECTION_BASE:GetFriendliesNearBy(DetectedItem)
|
|
return DetectedItem.FriendliesNearBy
|
|
end
|
|
function DETECTION_BASE:FilterFriendliesCategory(FriendliesCategory)
|
|
self.FriendliesCategory=FriendliesCategory
|
|
return self
|
|
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(ReportGroupData)
|
|
self:F2()
|
|
local DetectedItem=ReportGroupData.DetectedItem
|
|
local DetectedSet=ReportGroupData.DetectedItem.Set
|
|
local DetectedUnit=DetectedSet:GetFirst()
|
|
DetectedItem.FriendliesNearBy=nil
|
|
if DetectedUnit and DetectedUnit:IsAlive()then
|
|
local DetectedUnitCoord=DetectedUnit:GetCoordinate()
|
|
local InterceptCoord=ReportGroupData.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 FoundUnitName=FoundDCSUnit:getName()
|
|
local FoundUnitGroupName=FoundDCSUnit:getGroup():getName()
|
|
local EnemyUnitName=DetectedUnit:GetName()
|
|
local FoundUnitInReportSetGroup=ReportSetGroup:FindGroup(FoundUnitGroupName)~=nil
|
|
self:T({"Friendlies search:",FoundUnitName,FoundUnitCoalition,EnemyUnitName,EnemyCoalition,FoundUnitInReportSetGroup})
|
|
if FoundUnitInReportSetGroup==true then
|
|
for PrefixID,Prefix in pairs(self.FriendlyPrefixes or{})do
|
|
self:F({"FriendlyPrefix:",Prefix})
|
|
if string.find(FoundUnitName,Prefix:gsub("-","%%-"),1)then
|
|
FoundUnitInReportSetGroup=false
|
|
break
|
|
end
|
|
end
|
|
end
|
|
self:F({"Friendlies search:",FoundUnitName,FoundUnitCoalition,EnemyUnitName,EnemyCoalition,FoundUnitInReportSetGroup})
|
|
if FoundUnitCoalition~=EnemyCoalition and FoundUnitInReportSetGroup==false then
|
|
local FriendlyUnit=UNIT:Find(FoundDCSUnit)
|
|
local FriendlyUnitName=FriendlyUnit:GetName()
|
|
local FriendlyUnitCategory=FriendlyUnit:GetDesc().category
|
|
self:T({FriendlyUnitCategory=FriendlyUnitCategory,FriendliesCategory=self.FriendliesCategory})
|
|
DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{}
|
|
DetectedItem.FriendliesNearBy[FriendlyUnitName]=FriendlyUnit
|
|
local Distance=DetectedUnitCoord:Get2DDistance(FriendlyUnit:GetCoordinate())
|
|
DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{}
|
|
DetectedItem.FriendliesDistance[Distance]=FriendlyUnit
|
|
self:T({FriendlyUnitName=FriendlyUnitName,Distance=Distance})
|
|
return true
|
|
end
|
|
return true
|
|
end
|
|
world.searchObjects(Object.Category.UNIT,SphereSearch,FindNearByFriendlies,ReportGroupData)
|
|
DetectedItem.PlayersNearBy=nil
|
|
local DetectionZone=ZONE_UNIT:New("DetectionPlayers",DetectedUnit,self.FriendliesRange)
|
|
_DATABASE:ForEachPlayer(
|
|
function(PlayerUnitName)
|
|
local PlayerUnit=UNIT:FindByName(PlayerUnitName)
|
|
if PlayerUnit and PlayerUnit:IsInZone(DetectionZone)then
|
|
local PlayerUnitCategory=PlayerUnit:GetDesc().category
|
|
if(not self.FriendliesCategory)or(self.FriendliesCategory and(self.FriendliesCategory==PlayerUnitCategory))then
|
|
DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{}
|
|
local PlayerUnitName=PlayerUnit:GetName()
|
|
DetectedItem.PlayersNearBy=DetectedItem.PlayersNearBy or{}
|
|
DetectedItem.PlayersNearBy[PlayerUnitName]=PlayerUnit
|
|
DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{}
|
|
DetectedItem.FriendliesNearBy[PlayerUnitName]=PlayerUnit
|
|
local Distance=DetectedUnitCoord:Get2DDistance(PlayerUnit:GetCoordinate())
|
|
DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{}
|
|
DetectedItem.FriendliesDistance[Distance]=PlayerUnit
|
|
end
|
|
end
|
|
end
|
|
)
|
|
end
|
|
end
|
|
end
|
|
function DETECTION_BASE:IsDetectedObjectIdentified(DetectedObject)
|
|
local DetectedObjectName=DetectedObject.Name
|
|
if DetectedObjectName then
|
|
local DetectedObjectIdentified=self.DetectedObjectsIdentified[DetectedObjectName]==true
|
|
self:T3(DetectedObjectIdentified)
|
|
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)
|
|
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,DetectedItemIndex,Set)
|
|
local DetectedItem={}
|
|
self.DetectedItemCount=self.DetectedItemCount+1
|
|
self.DetectedItemMax=self.DetectedItemMax+1
|
|
if DetectedItemIndex then
|
|
self.DetectedItems[DetectedItemIndex]=DetectedItem
|
|
else
|
|
self.DetectedItems[self.DetectedItemCount]=DetectedItem
|
|
end
|
|
DetectedItem.Set=Set or SET_UNIT:New():FilterDeads():FilterCrashes()
|
|
DetectedItem.Index=DetectedItemIndex or self.DetectedItemCount
|
|
DetectedItem.ItemID=ItemPrefix.."."..self.DetectedItemMax
|
|
DetectedItem.ID=self.DetectedItemMax
|
|
DetectedItem.Removed=false
|
|
return DetectedItem
|
|
end
|
|
function DETECTION_BASE:AddDetectedItemZone(DetectedItemIndex,Set,Zone)
|
|
local DetectedItem=self:AddDetectedItem("AREA",DetectedItemIndex,Set)
|
|
DetectedItem.Zone=Zone
|
|
return DetectedItem
|
|
end
|
|
function DETECTION_BASE:RemoveDetectedItem(DetectedItemIndex)
|
|
if self.DetectedItems[DetectedItemIndex]then
|
|
self.DetectedItemCount=self.DetectedItemCount-1
|
|
self.DetectedItems[DetectedItemIndex]=nil
|
|
end
|
|
end
|
|
function DETECTION_BASE:GetDetectedItems()
|
|
return self.DetectedItems
|
|
end
|
|
function DETECTION_BASE:GetDetectedItemsCount()
|
|
local DetectedCount=self.DetectedItemCount
|
|
return DetectedCount
|
|
end
|
|
function DETECTION_BASE:GetDetectedItem(Index)
|
|
local DetectedItem=self.DetectedItems[Index]
|
|
if DetectedItem then
|
|
return DetectedItem
|
|
end
|
|
return nil
|
|
end
|
|
function DETECTION_BASE:GetDetectedItemID(Index)
|
|
local DetectedItem=self.DetectedItems[Index]
|
|
if DetectedItem then
|
|
return DetectedItem.ItemID
|
|
end
|
|
return""
|
|
end
|
|
function DETECTION_BASE:GetDetectedID(Index)
|
|
local DetectedItem=self.DetectedItems[Index]
|
|
if DetectedItem then
|
|
return DetectedItem.ID
|
|
end
|
|
return""
|
|
end
|
|
function DETECTION_BASE:GetDetectedSet(Index)
|
|
local DetectedItem=self:GetDetectedItem(Index)
|
|
local DetectedSetUnit=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(Index)
|
|
local DetectedZone=self.DetectedItems[Index].Zone
|
|
if DetectedZone then
|
|
return DetectedZone
|
|
end
|
|
local Detected
|
|
return nil
|
|
end
|
|
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(Index)
|
|
self:F({Index=Index})
|
|
local DetectedItem=self:GetDetectedItem(Index)
|
|
if DetectedItem then
|
|
return DetectedItem.Coordinate
|
|
end
|
|
return nil
|
|
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(Index)
|
|
self:F({Index=Index})
|
|
local DetectedItem=self:GetDetectedItem(Index)
|
|
if DetectedItem then
|
|
return DetectedItem.ThreatLevel or 0,DetectedItem.ThreatText or""
|
|
end
|
|
return nil,""
|
|
end
|
|
function DETECTION_BASE:DetectedItemMenu(Index,AttackGroup)
|
|
self:F(Index)
|
|
return nil
|
|
end
|
|
function DETECTION_BASE:DetectedItemReportSummary(Index,AttackGroup,Settings)
|
|
self:F(Index)
|
|
return nil
|
|
end
|
|
function DETECTION_BASE:DetectedReportDetailed(AttackGroup)
|
|
self:F()
|
|
return nil
|
|
end
|
|
function DETECTION_BASE:GetDetectionSetGroup()
|
|
local DetectionSetGroup=self.DetectionSetGroup
|
|
return DetectionSetGroup
|
|
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()
|
|
self:F2(#self.DetectedObjects)
|
|
for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
|
|
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)
|
|
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
|
|
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:GetDetectedItem(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.DetectionSetGroup})
|
|
end
|
|
end
|
|
function DETECTION_UNITS:DetectedItemMenu(Index,AttackGroup)
|
|
self:F(Index)
|
|
local DetectedItem=self:GetDetectedItem(Index)
|
|
local DetectedSet=self:GetDetectedSet(Index)
|
|
local DetectedItemID=self:GetDetectedItemID(Index)
|
|
self:T(DetectedSet)
|
|
if DetectedSet then
|
|
local ReportSummary=""
|
|
local UnitDistanceText=""
|
|
local UnitCategoryText=""
|
|
local DetectedItemCoordinate=self:GetDetectedItemCoordinate(Index)
|
|
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup)
|
|
ReportSummary=string.format(
|
|
"%s - %s",
|
|
DetectedItemID,
|
|
DetectedItemCoordText
|
|
)
|
|
self:T(ReportSummary)
|
|
return ReportSummary
|
|
end
|
|
end
|
|
function DETECTION_UNITS:DetectedItemReportSummary(Index,AttackGroup,Settings)
|
|
self:F({Index,self.DetectedItems})
|
|
local DetectedItem=self:GetDetectedItem(Index)
|
|
local DetectedItemID=self:GetDetectedItemID(Index)
|
|
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(Index)
|
|
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
|
|
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(Index)
|
|
local Report=REPORT:New()
|
|
Report:Add(DetectedItemID..", "..DetectedItemCoordText)
|
|
Report:Add(string.format("Threat: [%s]",string.rep("■",ThreatLevelA2G)))
|
|
Report:Add(string.format("Type: %s%s",UnitCategoryText,UnitDistanceText))
|
|
return Report
|
|
end
|
|
return nil
|
|
end
|
|
function DETECTION_UNITS:DetectedReportDetailed(AttackGroup)
|
|
self:F()
|
|
local Report=REPORT:New()
|
|
for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
|
|
local DetectedItem=DetectedItem
|
|
local ReportSummary=self:DetectedItemReportSummary(DetectedItemID,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()
|
|
self:F2(#self.DetectedObjects)
|
|
for DetectedItemID,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
|
|
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:GetDetectedItem(DetectedTypeName)
|
|
if not DetectedItem then
|
|
DetectedItem=self:AddDetectedItem("TYPE",DetectedTypeName)
|
|
DetectedItem.TypeName=DetectedUnit:GetTypeName()
|
|
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.DetectionSetGroup})
|
|
end
|
|
end
|
|
function DETECTION_TYPES:DetectedItemMenu(DetectedTypeName,AttackGroup)
|
|
self:F(DetectedTypeName)
|
|
local DetectedItem=self:GetDetectedItem(DetectedTypeName)
|
|
local DetectedItemID=self:GetDetectedItemID(DetectedTypeName)
|
|
if DetectedItem then
|
|
local DetectedItemCoordinate=self:GetDetectedItemCoordinate(DetectedTypeName)
|
|
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup)
|
|
local ReportSummary=string.format(
|
|
"%s - %s",
|
|
DetectedItemID,
|
|
DetectedItemCoordText
|
|
)
|
|
self:T(ReportSummary)
|
|
return ReportSummary
|
|
end
|
|
end
|
|
function DETECTION_TYPES:DetectedItemReportSummary(DetectedTypeName,AttackGroup,Settings)
|
|
self:F(DetectedTypeName)
|
|
local DetectedItem=self:GetDetectedItem(DetectedTypeName)
|
|
local DetectedSet=self:GetDetectedSet(DetectedTypeName)
|
|
local DetectedItemID=self:GetDetectedItemID(DetectedTypeName)
|
|
self:T(DetectedItem)
|
|
if DetectedItem then
|
|
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedTypeName)
|
|
local DetectedItemsCount=DetectedSet:Count()
|
|
local DetectedItemType=DetectedItem.TypeName
|
|
local DetectedItemCoordinate=self:GetDetectedItemCoordinate(DetectedTypeName)
|
|
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
|
|
local Report=REPORT:New()
|
|
Report:Add(DetectedItemID..", "..DetectedItemCoordText)
|
|
Report:Add(string.format("Threat: [%s]",string.rep("■",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 DetectedItemTypeName,DetectedItem in pairs(self.DetectedItems)do
|
|
local DetectedItem=DetectedItem
|
|
local ReportSummary=self:DetectedItemReportSummary(DetectedItemTypeName,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:DetectedItemMenu(Index,AttackGroup)
|
|
self:F(Index)
|
|
local DetectedItem=self:GetDetectedItem(Index)
|
|
local DetectedItemID=self:GetDetectedItemID(Index)
|
|
if DetectedItem then
|
|
local DetectedSet=self:GetDetectedSet(Index)
|
|
local ReportSummaryItem
|
|
local DetectedZone=self:GetDetectedItemZone(Index)
|
|
local DetectedItemCoordinate=DetectedZone:GetCoordinate()
|
|
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup)
|
|
local ReportSummary=string.format(
|
|
"%s - %s",
|
|
DetectedItemID,
|
|
DetectedItemCoordText
|
|
)
|
|
return ReportSummary
|
|
end
|
|
return nil
|
|
end
|
|
function DETECTION_AREAS:DetectedItemReportSummary(Index,AttackGroup,Settings)
|
|
self:F(Index)
|
|
local DetectedItem=self:GetDetectedItem(Index)
|
|
local DetectedItemID=self:GetDetectedItemID(Index)
|
|
if DetectedItem then
|
|
local DetectedSet=self:GetDetectedSet(Index)
|
|
local ReportSummaryItem
|
|
local DetectedZone=self:GetDetectedItemZone(Index)
|
|
local DetectedItemCoordinate=DetectedZone:GetCoordinate()
|
|
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
|
|
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(Index)
|
|
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)))
|
|
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(DetectedItemIndex,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:NearestFAC(DetectedItem)
|
|
local NearestRecce=nil
|
|
local DistanceRecce=1000000000
|
|
for RecceGroupName,RecceGroup in pairs(self.DetectionSetGroup: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.Index))
|
|
if Distance<DistanceRecce then
|
|
DistanceRecce=Distance
|
|
NearestRecce=RecceUnit
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
DetectedItem.NearestFAC=NearestRecce
|
|
DetectedItem.DistanceRecce=DistanceRecce
|
|
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:F2()
|
|
self:T("Checking Detected Items for new Detected Units ...")
|
|
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
|
|
local DetectedItem=DetectedItemData
|
|
if DetectedItem then
|
|
self:T({"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)
|
|
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
|
|
self:T("Detection Area #"..DetectedItem.ID)
|
|
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(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)
|
|
self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSetGroup})
|
|
self:SetDetectedItemThreatLevel(DetectedItem)
|
|
self:NearestFAC(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
|
|
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("*","Done","*")
|
|
self:AddTransition("*","Status","*")
|
|
self.CC=CC
|
|
self.Detection=Detection
|
|
self.AttackSet=AttackSet
|
|
self.RecceSet=Detection:GetDetectionSetGroup()
|
|
self.Recces={}
|
|
self.Designating={}
|
|
self:SetDesignateName()
|
|
self.LaseDuration=60
|
|
self:SetFlashStatusMenu(false)
|
|
self:SetMission(Mission)
|
|
self:SetLaserCodes({1688,1130,4785,6547,1465,4578})
|
|
self:SetAutoLase(false,false)
|
|
self:SetThreatLevelPrioritization(false)
|
|
self:SetMaximumDesignations(5)
|
|
self:SetMaximumDistanceDesignations(12000)
|
|
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:ForEachGroup(
|
|
function(AttackGroup)
|
|
self.FlashStatusMenu[AttackGroup]=FlashMenu
|
|
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:E({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: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:GetDetectedItems()
|
|
local DetectedItemCount=0
|
|
for DesignateIndex,Designating in pairs(self.Designating)do
|
|
local DetectedItem=DetectedItems[DesignateIndex]
|
|
if DetectedItem then
|
|
local IsDetected=self.Detection:IsDetectedItemDetected(DetectedItem)
|
|
self:F({IsDetected=IsDetected,DetectedItem})
|
|
if IsDetected==false then
|
|
self:F("Removing")
|
|
self.Designating[DesignateIndex]=nil
|
|
self.AttackSet:ForEachGroup(
|
|
function(AttackGroup)
|
|
local DetectionText=self.Detection:DetectedItemReportSummary(DesignateIndex,AttackGroup):Text(", ")
|
|
self.CC:GetPositionable():MessageToGroup("Targets out of LOS\n"..DetectionText,10,AttackGroup,self.DesignateName)
|
|
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:ForEachGroup(
|
|
function(AttackGroup)
|
|
local DetectionText=self.Detection:DetectedItemReportSummary(DesignateIndex,AttackGroup):Text(", ")
|
|
self.CC:GetPositionable():MessageToGroup("Targets detected at \n"..DetectionText,10,AttackGroup,self.DesignateName)
|
|
end
|
|
)
|
|
self.Designating[DesignateIndex]=""
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
function DESIGNATE:CoordinateLase()
|
|
local DetectedItems=self.Detection:GetDetectedItems()
|
|
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,Duration)
|
|
Duration=Duration or 10
|
|
self.AttackSet:ForEachGroup(
|
|
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:GetDetectedItems()
|
|
for DesignateIndex,Designating in pairs(self.Designating)do
|
|
local DetectedItem=DetectedItems[DesignateIndex]
|
|
if DetectedItem then
|
|
local Report=self.Detection:DetectedItemReportSummary(DesignateIndex,AttackGroup):Text(", ")
|
|
DetectedReport:Add(string.rep("-",140))
|
|
DetectedReport:Add(" - "..Report)
|
|
end
|
|
end
|
|
local CC=self.CC:GetPositionable()
|
|
CC:MessageToGroup(DetectedReport:Text("\n"),Duration,AttackGroup,self.DesignateName)
|
|
local DesignationReport=REPORT:New("Marking Targets:\n")
|
|
self.RecceSet:ForEachGroup(
|
|
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:MessageToGroup(DesignationReport:Text(),Duration,AttackGroup,self.DesignateName)
|
|
end
|
|
end
|
|
)
|
|
return self
|
|
end
|
|
function DESIGNATE:SetDesignateMenu()
|
|
self.AttackSet:Flush()
|
|
self.AttackSet:ForEachGroup(
|
|
function(AttackGroup)
|
|
self.MenuDesignate=self.MenuDesignate or{}
|
|
local MissionMenu=nil
|
|
if self.Mission then
|
|
MissionMenu=self.Mission:GetRootMenu(AttackGroup)
|
|
end
|
|
local MenuTime=timer.getTime()
|
|
self.MenuDesignate[AttackGroup]=MENU_GROUP:New(AttackGroup,self.DesignateName,MissionMenu):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
local MenuDesignate=self.MenuDesignate[AttackGroup]
|
|
if self.AutoLase then
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Auto Lase Off",MenuDesignate,self.MenuAutoLase,self,false):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
else
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Auto Lase On",MenuDesignate,self.MenuAutoLase,self,true):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
end
|
|
local StatusMenu=MENU_GROUP:New(AttackGroup,"Status",MenuDesignate):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Report Status 15s",StatusMenu,self.MenuStatus,self,AttackGroup,15):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Report Status 30s",StatusMenu,self.MenuStatus,self,AttackGroup,30):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Report Status 60s",StatusMenu,self.MenuStatus,self,AttackGroup,60):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
if self.FlashStatusMenu[AttackGroup]then
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Flash Status Report Off",StatusMenu,self.MenuFlashStatus,self,AttackGroup,false):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
else
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Flash Status Report On",StatusMenu,self.MenuFlashStatus,self,AttackGroup,true):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
end
|
|
for DesignateIndex,Designating in pairs(self.Designating)do
|
|
local DetectedItem=self.Detection:GetDetectedItem(DesignateIndex)
|
|
if DetectedItem then
|
|
local Coord=self.Detection:GetDetectedItemCoordinate(DesignateIndex)
|
|
local ID=self.Detection:GetDetectedItemID(DesignateIndex)
|
|
local MenuText=ID..", "..Coord:ToStringA2G(AttackGroup)
|
|
if Designating==""then
|
|
MenuText="(-) "..MenuText
|
|
local DetectedMenu=MENU_GROUP:New(AttackGroup,MenuText,MenuDesignate):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
MENU_GROUP_COMMAND: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:New(AttackGroup,string.format(MenuText,LaserCode),DetectedMenu,self.MenuLaseCode,self,DesignateIndex,60,LaserCode):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
end
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Lase with random laser code(s)",DetectedMenu,self.MenuLaseOn,self,DesignateIndex,60):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Smoke red",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Red):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Smoke blue",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Blue):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Smoke green",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Green):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Smoke white",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.White):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Smoke orange",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Orange):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Illuminate",DetectedMenu,self.MenuIlluminate,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
else
|
|
if Designating=="Laser"then
|
|
MenuText="(L) "..MenuText
|
|
elseif Designating=="Smoke"then
|
|
MenuText="(S) "..MenuText
|
|
elseif Designating=="Illuminate"then
|
|
MenuText="(I) "..MenuText
|
|
end
|
|
local DetectedMenu=MENU_GROUP:New(AttackGroup,MenuText,MenuDesignate):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
if Designating=="Laser"then
|
|
MENU_GROUP_COMMAND:New(AttackGroup,"Stop lasing",DetectedMenu,self.MenuLaseOff,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName)
|
|
else
|
|
end
|
|
end
|
|
end
|
|
end
|
|
MenuDesignate:Remove(MenuTime,self.DesignateName)
|
|
end
|
|
)
|
|
return self
|
|
end
|
|
function DESIGNATE:MenuStatus(AttackGroup,Duration)
|
|
self:E("Status")
|
|
self:SendStatus(AttackGroup,Duration)
|
|
end
|
|
function DESIGNATE:MenuFlashStatus(AttackGroup,Flash)
|
|
self:E("Flash Status")
|
|
self.FlashStatusMenu[AttackGroup]=Flash
|
|
self:SetDesignateMenu()
|
|
end
|
|
function DESIGNATE:MenuForget(Index)
|
|
self:E("Forget")
|
|
self.Designating[Index]=nil
|
|
self:SetDesignateMenu()
|
|
end
|
|
function DESIGNATE:MenuAutoLase(AutoLase)
|
|
self:E("AutoLase")
|
|
self:SetAutoLase(AutoLase,true)
|
|
end
|
|
function DESIGNATE:MenuSmoke(Index,Color)
|
|
self:E("Designate through Smoke")
|
|
self.Designating[Index]="Smoke"
|
|
self:Smoke(Index,Color)
|
|
end
|
|
function DESIGNATE:MenuIlluminate(Index)
|
|
self:E("Designate through Illumination")
|
|
self.Designating[Index]="Illuminate"
|
|
self:__Illuminate(1,Index)
|
|
end
|
|
function DESIGNATE:MenuLaseOn(Index,Duration)
|
|
self:E("Designate through Lase")
|
|
self:__LaseOn(1,Index,Duration)
|
|
self:SetDesignateMenu()
|
|
end
|
|
function DESIGNATE:MenuLaseCode(Index,Duration,LaserCode)
|
|
self:E("Designate through Lase using "..LaserCode)
|
|
self:__LaseOn(1,Index,Duration,LaserCode)
|
|
self:SetDesignateMenu()
|
|
end
|
|
function DESIGNATE:MenuLaseOff(Index,Duration)
|
|
self:E("Lasing off")
|
|
self.Designating[Index]=""
|
|
self:__LaseOff(1,Index)
|
|
self:SetDesignateMenu()
|
|
end
|
|
function DESIGNATE:onafterLaseOn(From,Event,To,Index,Duration,LaserCode)
|
|
self.Designating[Index]="Laser"
|
|
self.LaseStart=timer.getTime()
|
|
self.LaseDuration=Duration
|
|
self:__Lasing(-1,Index,Duration,LaserCode)
|
|
end
|
|
function DESIGNATE:onafterLasing(From,Event,To,Index,Duration,LaserCodeRequested)
|
|
local TargetSetUnit=self.Detection:GetDetectedSet(Index)
|
|
local MarkingCount=0
|
|
local MarkedTypes={}
|
|
local ReportTypes=REPORT:New()
|
|
local ReportLaserCodes=REPORT:New()
|
|
TargetSetUnit:Flush()
|
|
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:E("Lasing...")
|
|
self.RecceSet:Flush()
|
|
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
|
|
function Spot:OnAfterDestroyed(From,Event,To)
|
|
self:E("Destroyed Message")
|
|
self.Recce:ToSetGroup("Target "..TargetUnit:GetTypeName().." destroyed. "..TargetSetUnit:Count().." targets left.",5,AttackSet,self.DesignateName)
|
|
end
|
|
self.Recces[TargetUnit]=RecceUnit
|
|
RecceUnit:MessageToSetGroup("Marking "..TargetUnit:GetTypeName().." with laser "..RecceUnit:GetSpot().LaserCode.." for "..Duration.."s.",5,self.AttackSet,self.DesignateName)
|
|
MarkingCount=MarkingCount+1
|
|
local TargetUnitType=TargetUnit:GetTypeName()
|
|
if not MarkedTypes[TargetUnitType]then
|
|
MarkedTypes[TargetUnitType]=true
|
|
ReportTypes:Add(TargetUnitType)
|
|
end
|
|
ReportLaserCodes:Add(RecceUnit.LaserCode)
|
|
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!",5,self.AttackSet,self.DesignateName)
|
|
end
|
|
else
|
|
MarkingCount=MarkingCount+1
|
|
local TargetUnitType=TargetUnit:GetTypeName()
|
|
if not MarkedTypes[TargetUnitType]then
|
|
MarkedTypes[TargetUnitType]=true
|
|
ReportTypes:Add(TargetUnitType)
|
|
end
|
|
ReportLaserCodes:Add(RecceUnit.LaserCode)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
MarkingCount=MarkingCount+1
|
|
local TargetUnitType=TargetUnit:GetTypeName()
|
|
if not MarkedTypes[TargetUnitType]then
|
|
MarkedTypes[TargetUnitType]=true
|
|
ReportTypes:Add(TargetUnitType)
|
|
end
|
|
ReportLaserCodes:Add(Recce.LaserCode)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
)
|
|
local MarkedTypesText=ReportTypes:Text(', ')
|
|
local MarkedLaserCodesText=ReportLaserCodes:Text(', ')
|
|
for MarkedType,MarketCount in pairs(MarkedTypes)do
|
|
self.CC:GetPositionable():MessageToSetGroup("Marking "..MarkingCount.." x "..MarkedTypesText.." with lasers "..MarkedLaserCodesText..".",5,self.AttackSet,self.DesignateName)
|
|
end
|
|
self:__Lasing(-30,Index,Duration,LaserCodeRequested)
|
|
self:SetDesignateMenu()
|
|
else
|
|
self:__LaseOff(1)
|
|
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 TargetSetUnit=self.Detection:GetDetectedSet(Index)
|
|
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:SetDesignateMenu()
|
|
end
|
|
function DESIGNATE:onafterSmoke(From,Event,To,Index,Color)
|
|
local TargetSetUnit=self.Detection:GetDetectedSet(Index)
|
|
local TargetSetUnitCount=TargetSetUnit:Count()
|
|
local MarkedCount=0
|
|
TargetSetUnit:ForEachUnitPerThreatLevel(10,0,
|
|
function(SmokeUnit)
|
|
if MarkedCount<self.MaximumMarkings then
|
|
MarkedCount=MarkedCount+1
|
|
self:E("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)
|
|
self.MarkScheduler:Schedule(self,
|
|
function()
|
|
if SmokeUnit:IsAlive()then
|
|
SmokeUnit:Smoke(Color,50,2)
|
|
end
|
|
self:Done(Index)
|
|
end,{},math.random(5,20)
|
|
)
|
|
end
|
|
end
|
|
end
|
|
)
|
|
end
|
|
function DESIGNATE:onafterIlluminate(From,Event,To,Index)
|
|
local TargetSetUnit=self.Detection:GetDetectedSet(Index)
|
|
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)
|
|
self.MarkScheduler:Schedule(self,
|
|
function()
|
|
if TargetUnit:IsAlive()then
|
|
TargetUnit:GetPointVec3():AddY(300):IlluminationBomb()
|
|
end
|
|
self:Done(Index)
|
|
end,{},math.random(5,20)
|
|
)
|
|
end
|
|
end
|
|
end
|
|
function DESIGNATE:onafterDone(From,Event,To,Index)
|
|
self.Designating[Index]=nil
|
|
self:SetDesignateMenu()
|
|
end
|
|
end
|
|
RAT={
|
|
ClassName="RAT",
|
|
debug=false,
|
|
alias=nil,
|
|
spawndelay=5,
|
|
spawninterval=5,
|
|
coalition=nil,
|
|
country=nil,
|
|
category=nil,
|
|
friendly="same",
|
|
ctable={},
|
|
aircraft={},
|
|
Vcruisemax=nil,
|
|
Vclimb=1500,
|
|
AlphaDescent=3.6,
|
|
roe="hold",
|
|
rot="noreaction",
|
|
takeoff=0,
|
|
mindist=5000,
|
|
maxdist=500000,
|
|
airports_map={},
|
|
airports={},
|
|
random_departure=true,
|
|
random_destination=true,
|
|
departure_zones={},
|
|
departure_ports={},
|
|
destination_ports={},
|
|
excluded_ports={},
|
|
ratcraft={},
|
|
Tinactive=300,
|
|
reportstatus=false,
|
|
statusinterval=30,
|
|
placemarkers=false,
|
|
FLminuser=nil,
|
|
FLmaxuser=nil,
|
|
FLuser=nil,
|
|
commute=false,
|
|
continuejourney=false,
|
|
alive=0,
|
|
ngroups=nil,
|
|
f10menu=true,
|
|
Menu={},
|
|
SubMenuName=nil,
|
|
respawn_at_landing=false,
|
|
respawn_delay=nil,
|
|
markerids={},
|
|
livery=nil,
|
|
skill="High",
|
|
ATCswitch=true,
|
|
}
|
|
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,
|
|
}
|
|
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,
|
|
}
|
|
RAT.markerid=0
|
|
RAT.MenuF10=nil
|
|
RAT.id="RAT | "
|
|
function RAT:New(groupname,alias)
|
|
env.info(RAT.id.."Creating new RAT object from template: "..groupname)
|
|
alias=alias or groupname
|
|
local self=BASE:Inherit(self,SPAWN:NewWithAlias(groupname,alias))
|
|
self.alias=alias
|
|
local DCSgroup=Group.getByName(groupname)
|
|
if DCSgroup==nil then
|
|
env.error("Group with name "..groupname.." does not exist in the mission editor!")
|
|
return nil
|
|
end
|
|
self.coalition=DCSgroup:getCoalition()
|
|
self:_InitAircraft(DCSgroup)
|
|
self:_GetAirportsOfMap()
|
|
return self
|
|
end
|
|
function RAT:Spawn(naircraft)
|
|
self.ngroups=naircraft or 1
|
|
if self.ATCswitch and not RAT.ATC.init then
|
|
RAT:_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
|
|
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("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("Takeoff type: %i\n",self.takeoff)
|
|
text=text..string.format("Commute: %s\n",tostring(self.commute))
|
|
text=text..string.format("Journey: %s\n",tostring(self.continuejourney))
|
|
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 after landing: %s\n",tostring(self.respawn_at_landing))
|
|
text=text..string.format("Respawn delay: %s\n",tostring(self.respawn_delay))
|
|
text=text..string.format("ROE: %s\n",tostring(self.roe))
|
|
text=text..string.format("ROT: %s\n",tostring(self.rot))
|
|
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("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("******************************************************\n")
|
|
env.info(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._SpawnWithRoute,{self},Tstart,dt,0.0,Tstop)
|
|
SCHEDULER:New(nil,self.Status,{self},Tstart+1,self.statusinterval)
|
|
self:HandleEvent(EVENTS.Birth,self._OnBirth)
|
|
self:HandleEvent(EVENTS.EngineStartup,self._EngineStartup)
|
|
self:HandleEvent(EVENTS.Takeoff,self._OnTakeoff)
|
|
self:HandleEvent(EVENTS.Land,self._OnLand)
|
|
self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutdown)
|
|
self:HandleEvent(EVENTS.Dead,self._OnDead)
|
|
self:HandleEvent(EVENTS.Crash,self._OnCrash)
|
|
end
|
|
function RAT:SetCoalition(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
|
|
end
|
|
function RAT:SetCoalitionAircraft(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
|
|
end
|
|
end
|
|
function RAT:SetCoalition2(id)
|
|
self.country=id
|
|
end
|
|
function RAT:SetTakeoff(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
|
|
end
|
|
function RAT:SetDeparture(names)
|
|
self.random_departure=false
|
|
if type(names)=="table"then
|
|
for _,name in pairs(names)do
|
|
if self:_AirportExists(name)then
|
|
table.insert(self.departure_ports,name)
|
|
else
|
|
local z=trigger.misc.getZone(name)
|
|
if z then
|
|
table.insert(self.departure_zones,name)
|
|
end
|
|
end
|
|
end
|
|
elseif type(names)=="string"then
|
|
if self:_AirportExists(names)then
|
|
table.insert(self.departure_ports,names)
|
|
else
|
|
table.insert(self.departure_zones,names)
|
|
end
|
|
else
|
|
env.error("Input parameter must be a string or a table!")
|
|
end
|
|
end
|
|
function RAT:SetDestination(names)
|
|
self.random_destination=false
|
|
if type(names)=="table"then
|
|
for _,name in pairs(names)do
|
|
if self:_AirportExists(name)then
|
|
table.insert(self.destination_ports,name)
|
|
else
|
|
local text=string.format("Airport %s does not exist on map!",name)
|
|
env.error(text)
|
|
end
|
|
end
|
|
elseif type(names)=="string"then
|
|
if self:_AirportExists(names)then
|
|
self.destination_ports={names}
|
|
else
|
|
local text=string.format("Airport %s does not exist on map!",names)
|
|
env.error(text)
|
|
end
|
|
else
|
|
env.error("Input parameter must be a string or a table!")
|
|
end
|
|
end
|
|
function RAT:ExcludedAirports(ports)
|
|
if type(ports)=="string"then
|
|
self.excluded_ports={ports}
|
|
else
|
|
self.excluded_ports=ports
|
|
end
|
|
end
|
|
function RAT:Livery(skins)
|
|
if type(skins)=="string"then
|
|
self.livery={skins}
|
|
else
|
|
self.livery=skins
|
|
end
|
|
end
|
|
function RAT:ContinueJourney(switch)
|
|
switch=switch or true
|
|
self.continuejourney=switch
|
|
end
|
|
function RAT:Commute(switch)
|
|
switch=switch or true
|
|
self.commute=switch
|
|
end
|
|
function RAT:SetSpawnDelay(delay)
|
|
self.spawndelay=math.max(0.5,delay)
|
|
end
|
|
function RAT:SetSpawnInterval(interval)
|
|
self.spawninterval=math.max(0.5,interval)
|
|
end
|
|
function RAT:RespawnAfterLanding(delay)
|
|
delay=delay or 180
|
|
self.respawn_at_landing=true
|
|
self.respawn_delay=delay
|
|
end
|
|
function RAT:TimeDestroyInactive(time)
|
|
self.Tinactive=time
|
|
end
|
|
function RAT:SetMaxCruiseSpeed(speed)
|
|
self.Vcruisemax=speed/3.6
|
|
end
|
|
function RAT:SetClimbRate(rate)
|
|
self.Vclimb=rate
|
|
end
|
|
function RAT:SetDescentAngle(angle)
|
|
self.AlphaDescent=angle
|
|
end
|
|
function RAT:SetROE(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
|
|
end
|
|
function RAT:SetROT(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
|
|
end
|
|
function RAT:MenuName(name)
|
|
self.SubMenuName=tostring(name)
|
|
end
|
|
function RAT:EnableATC(switch)
|
|
self.ATCswitch=switch
|
|
end
|
|
function RAT:SetMinDistance(dist)
|
|
self.mindist=math.max(500,dist*1000)
|
|
end
|
|
function RAT:SetMaxDistance(dist)
|
|
self.maxdist=dist*1000
|
|
end
|
|
function RAT:_Debug(switch)
|
|
switch=switch or true
|
|
self.debug=switch
|
|
end
|
|
function RAT:StatusReports(switch)
|
|
switch=switch or true
|
|
self.reportstatus=switch
|
|
end
|
|
function RAT:PlaceMarkers(switch)
|
|
switch=switch or true
|
|
self.placemarkers=switch
|
|
end
|
|
function RAT:SetFL(height)
|
|
self.FLuser=height*RAT.unit.FL2m
|
|
end
|
|
function RAT:SetFLmax(height)
|
|
self.FLmaxuser=height*RAT.unit.FL2m
|
|
end
|
|
function RAT:SetMaxCruiseAltitude(alt)
|
|
self.FLmaxuser=alt
|
|
end
|
|
function RAT:SetFLmin(height)
|
|
self.FLminuser=height*RAT.unit.FL2m
|
|
end
|
|
function RAT:SetMinCruiseAltitude(alt)
|
|
self.FLminuser=alt
|
|
end
|
|
function RAT:SetFLcruise(height)
|
|
self.aircraft.FLcruise=height*RAT.unit.FL2m
|
|
end
|
|
function RAT:SetCruiseAltitude(alt)
|
|
self.aircraft.FLcruise=alt
|
|
end
|
|
function RAT:_InitAircraft(DCSgroup)
|
|
local DCSunit=DCSgroup:getUnit(1)
|
|
local DCSdesc=DCSunit:getDesc()
|
|
local DCScategory=DCSgroup:getCategory()
|
|
local DCStype=DCSunit:getTypeName()
|
|
if self.debug then
|
|
self:E({"DCSdesc",DCSdesc})
|
|
end
|
|
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"
|
|
env.error(RAT.id.."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.9
|
|
self.aircraft.Vmax=DCSdesc.speedMax
|
|
self.aircraft.Vymax=DCSdesc.VyMax
|
|
self.aircraft.ceiling=DCSdesc.Hmax
|
|
if self.category==RAT.cat.plane then
|
|
self.aircraft.FLcruise=200*RAT.unit.FL2m
|
|
else
|
|
self.aircraft.FLcruise=005*RAT.unit.FL2m
|
|
end
|
|
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("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\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("FL cruise = %6.1f km = FL%3.0f\n",self.aircraft.FLcruise/1000,self.aircraft.FLcruise/RAT.unit.FL2m)
|
|
text=text..string.format("******************************************************\n")
|
|
env.info(RAT.id..text)
|
|
end
|
|
function RAT:_SpawnWithRoute(_departure,_destination)
|
|
local _takeoff=self.takeoff
|
|
if self.takeoff==RAT.wp.coldorhot then
|
|
local temp={RAT.wp.cold,RAT.wp.hot}
|
|
_takeoff=temp[math.random(2)]
|
|
end
|
|
local departure,destination,waypoints=self:_SetRoute(_takeoff,_departure,_destination)
|
|
if not(departure and destination and waypoints)then
|
|
return nil
|
|
end
|
|
self:_ModifySpawnTemplate(waypoints)
|
|
local group=self:SpawnWithIndex(self.SpawnIndex)
|
|
self.alive=self.alive+1
|
|
if self.ATCswitch then
|
|
RAT:_ATCAddFlight(group:GetName(),destination:GetName())
|
|
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]["status"]="spawned"
|
|
self.ratcraft[self.SpawnIndex]["airborne"]=group:InAir()
|
|
if group:InAir()then
|
|
self.ratcraft[self.SpawnIndex]["Tground"]=nil
|
|
self.ratcraft[self.SpawnIndex]["Pground"]=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]["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
|
|
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("Clear for landing",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self.ClearForLanding,self,group:GetName())
|
|
MENU_MISSION_COMMAND:New("Place markers",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self._PlaceMarkers,self,waypoints)
|
|
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)
|
|
env.info("ATC: setting user flag "..name.." to 1.")
|
|
trigger.action.setUserFlag(name,1)
|
|
local flagvalue=trigger.misc.getUserFlag(name)
|
|
env.info("ATC: user flag "..name.." ="..flagvalue)
|
|
end
|
|
function RAT:_Respawn(group)
|
|
local index=self:GetSpawnIndexFromGroup(group)
|
|
local departure=self.ratcraft[index].departure
|
|
local destination=self.ratcraft[index].destination
|
|
local _departure=nil
|
|
local _destination=nil
|
|
if self.continuejourney then
|
|
_departure=destination:GetName()
|
|
elseif self.commute then
|
|
_departure=destination:GetName()
|
|
_destination=departure:GetName()
|
|
end
|
|
if self.respawn_delay then
|
|
SCHEDULER:New(nil,self._SpawnWithRoute,{self,_departure,_destination},self.respawn_delay)
|
|
else
|
|
self:_SpawnWithRoute(_departure,_destination)
|
|
end
|
|
end
|
|
function RAT:_SetRoute(takeoff,_departure,_destination)
|
|
local VxCruiseMax
|
|
if self.Vcruisemax then
|
|
VxCruiseMax=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=self:_Random_Gaussian((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 departure=nil
|
|
if _departure then
|
|
if self:_AirportExists(_departure)then
|
|
departure=AIRBASE:FindByName(_departure)
|
|
else
|
|
local text=string.format("ERROR: Specified departure airport %s does not exist for %s!",_departure,self.alias)
|
|
env.error(RAT.id..text)
|
|
end
|
|
else
|
|
departure=self:_PickDeparture(takeoff)
|
|
end
|
|
if not departure then
|
|
local text=string.format("No valid departure airport could be found for %s.",self.alias)
|
|
MESSAGE:New(text,60):ToAll()
|
|
env.error(RAT.id..text)
|
|
return nil
|
|
end
|
|
local Pdeparture
|
|
if takeoff==RAT.wp.air then
|
|
local vec2=departure:GetRandomVec2()
|
|
Pdeparture=COORDINATE:NewFromVec2(vec2)
|
|
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(self.aircraft.FLcruise*0.7,0.3,Pdeparture.y+Hmin,self.aircraft.FLcruise)
|
|
else
|
|
H_departure=Pdeparture.y
|
|
end
|
|
if self.FLminuser then
|
|
self.mindist=self:_MinDistance(AlphaClimb,AlphaDescent,self.FLminuser-H_departure)
|
|
local text=string.format("Adjusting min distance to %d km (for given min FL%03d)",self.mindist/1000,self.FLminuser/RAT.unit.FL2m)
|
|
env.info(RAT.id..text)
|
|
end
|
|
local destination=nil
|
|
if _destination then
|
|
if self:_AirportExists(_destination)then
|
|
destination=AIRBASE:FindByName(_destination)
|
|
else
|
|
local text=string.format("ERROR: Specified destination airport %s does not exist for %s!",_destination,self.alias)
|
|
env.error(RAT.id..text)
|
|
end
|
|
else
|
|
if self.continuejourney and _departure and#self.destination_ports<3 then
|
|
self.random_destination=true
|
|
end
|
|
local destinations=self:_GetDestinations(departure,Pdeparture,self.mindist,math.min(self.aircraft.Reff,self.maxdist))
|
|
destination=self:_PickDestination(destinations)
|
|
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()
|
|
env.error(RAT.id..text)
|
|
return nil
|
|
end
|
|
if destination:GetName()==departure:GetName()then
|
|
local text=string.format("%s: Destination and departure airport are identical. Airport %s (ID %d).",self.alias,destination:GetName(),destination:GetID())
|
|
MESSAGE:New(text,120):ToAll()
|
|
env.error(RAT.id..text)
|
|
end
|
|
local Pdestination=destination:GetCoordinate()
|
|
local H_destination=Pdestination.y
|
|
local Vholding
|
|
if self.category==RAT.cat.plane then
|
|
Vholding=destination:GetCoordinate():GetRandomVec2InRadius(10000,5000)
|
|
else
|
|
Vholding=destination:GetCoordinate():GetRandomVec2InRadius(1000,500)
|
|
end
|
|
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 d_holding=Pholding:Get2DDistance(Pdestination)
|
|
local deltaH=math.abs(h_holding+H_holding-H_departure)
|
|
local heading=self:_Course(Pdeparture,Pholding)
|
|
local d_total=Pdeparture:Get2DDistance(Pholding)
|
|
local phi=math.atan(deltaH/d_total)
|
|
local PhiClimb=AlphaClimb+phi
|
|
local PhiDescent=AlphaDescent-phi
|
|
local FLmax=self:_FLmax(AlphaClimb,AlphaDescent,d_total,phi,H_departure)
|
|
local FLmin=math.max(H_departure,H_holding+h_holding)
|
|
if FLmin>FLmax then
|
|
FLmin=FLmax*0.75
|
|
end
|
|
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*0.9)
|
|
if self.FLminuser then
|
|
FLmin=self.FLminuser
|
|
end
|
|
if self.FLmaxuser then
|
|
FLmax=self.FLmaxuser
|
|
end
|
|
if self.aircraft.FLcruise<FLmin then
|
|
self.aircraft.FLcruise=FLmin
|
|
end
|
|
if self.aircraft.FLcruise>FLmax then
|
|
self.aircraft.FLcruise=FLmax
|
|
end
|
|
local FLcruise=self:_Random_Gaussian(self.aircraft.FLcruise,(FLmax-FLmin)/4,FLmin,FLmax)
|
|
if self.FLuser then
|
|
FLcruise=self.FLuser
|
|
end
|
|
local h_climb=FLcruise-H_departure
|
|
local d_climb=h_climb/math.tan(PhiClimb)
|
|
local h_descent=FLcruise-(H_holding+h_holding)
|
|
local d_descent=h_descent/math.tan(PhiDescent)
|
|
local d_cruise=d_total-d_climb-d_descent
|
|
local text=string.format("\n******************************************************\n")
|
|
text=text..string.format("Template = %s\n\n",self.SpawnTemplatePrefix)
|
|
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.1f Deg\n",math.deg(AlphaClimb))
|
|
text=text..string.format("Alpha descent = %6.1f Deg\n",math.deg(AlphaDescent))
|
|
text=text..string.format("Phi (slope) = %6.1f Deg\n",math.deg(phi))
|
|
text=text..string.format("Phi climb = %6.1f Deg\n",math.deg(PhiClimb))
|
|
text=text..string.format("Phi descent = %6.1f Deg\n",math.deg(PhiDescent))
|
|
text=text..string.format("Heading = %6.1f Deg\n",heading)
|
|
text=text..string.format("******************************************************\n")
|
|
env.info(RAT.id..text)
|
|
if d_cruise<0 then
|
|
d_cruise=100
|
|
end
|
|
local c0=Pdeparture
|
|
local c1=c0:Translate(d_climb/2,heading)
|
|
local c2=c1:Translate(d_climb/2,heading)
|
|
local c3=c2:Translate(d_cruise,heading)
|
|
local c4=c3:Translate(d_descent/2,heading)
|
|
local c5=Pholding
|
|
local c6=Pdestination
|
|
local wp0=self:_Waypoint(takeoff,c0,VxClimb,H_departure,departure)
|
|
local wp1=self:_Waypoint(RAT.wp.climb,c1,VxClimb,H_departure+(FLcruise-H_departure)/2)
|
|
local wp2=self:_Waypoint(RAT.wp.cruise,c2,VxCruise,FLcruise)
|
|
local wp3=self:_Waypoint(RAT.wp.cruise,c3,VxCruise,FLcruise)
|
|
local wp4=self:_Waypoint(RAT.wp.descent,c4,VxDescent,FLcruise-(FLcruise-(h_holding+H_holding))/2)
|
|
local wp5=self:_Waypoint(RAT.wp.holding,c5,VxHolding,H_holding+h_holding)
|
|
local wp6=self:_Waypoint(RAT.wp.landing,c6,VxFinal,H_destination,destination)
|
|
local waypoints={wp0,wp1,wp2,wp3,wp4,wp5,wp6}
|
|
if self.placemarkers then
|
|
self:_PlaceMarkers(waypoints)
|
|
end
|
|
self:_Routeinfo(waypoints,"Waypoint info in set_route:")
|
|
return departure,destination,waypoints
|
|
end
|
|
function RAT:_PickDeparture(takeoff)
|
|
local departures={}
|
|
if takeoff==RAT.wp.air then
|
|
if self.random_departure then
|
|
for _,airport in pairs(self.airports)do
|
|
if not self:_Excluded(airport:GetName())then
|
|
table.insert(departures,airport:GetZone())
|
|
end
|
|
end
|
|
else
|
|
for _,name in pairs(self.departure_zones)do
|
|
if not self:_Excluded(name)then
|
|
table.insert(departures,ZONE:New(name))
|
|
end
|
|
end
|
|
for _,name in pairs(self.departure_ports)do
|
|
if not self:_Excluded(name)then
|
|
table.insert(departures,AIRBASE:FindByName(name):GetZone())
|
|
end
|
|
end
|
|
end
|
|
else
|
|
if self.random_departure then
|
|
for _,airport in pairs(self.airports)do
|
|
if not self:_Excluded(airport:GetName())then
|
|
table.insert(departures,airport)
|
|
end
|
|
end
|
|
else
|
|
for _,name in pairs(self.departure_ports)do
|
|
if not self:_Excluded(name)then
|
|
table.insert(departures,AIRBASE:FindByName(name))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local departure=departures[math.random(#departures)]
|
|
local text
|
|
if departure and departure:GetName()then
|
|
if takeoff==RAT.wp.air then
|
|
text="Chosen departure zone: "..departure:GetName()
|
|
else
|
|
text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")"
|
|
end
|
|
env.info(RAT.id..text)
|
|
if self.debug then
|
|
MESSAGE:New(text,30):ToAll()
|
|
end
|
|
else
|
|
departure=nil
|
|
end
|
|
return departure
|
|
end
|
|
function RAT:_PickDestination(destinations,_random)
|
|
local destination=nil
|
|
if destinations and#destinations>0 then
|
|
destination=destinations[math.random(#destinations)]
|
|
local text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")"
|
|
env.info(RAT.id..text)
|
|
if self.debug then
|
|
MESSAGE:New(text,30):ToAll()
|
|
end
|
|
else
|
|
env.error(RAT.id.."No destination airport found.")
|
|
end
|
|
return destination
|
|
end
|
|
function RAT:_GetDestinations(departure,q,minrange,maxrange)
|
|
minrange=minrange or self.mindist
|
|
maxrange=maxrange or self.maxdist
|
|
local possible_destinations={}
|
|
if self.random_destination then
|
|
for _,airport in pairs(self.airports)do
|
|
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
|
|
table.insert(possible_destinations,airport)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
for _,name in pairs(self.destination_ports)do
|
|
if name~=departure:GetName()then
|
|
local airport=AIRBASE:FindByName(name)
|
|
table.insert(possible_destinations,airport)
|
|
end
|
|
end
|
|
end
|
|
env.info(RAT.id.."Number of possible destination airports = "..#possible_destinations)
|
|
if#possible_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(possible_destinations,compare)
|
|
else
|
|
env.error(RAT.id.."No possible destination airports found!")
|
|
possible_destinations=nil
|
|
end
|
|
return possible_destinations
|
|
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)
|
|
table.insert(self.airports_map,_myab)
|
|
if self.debug then
|
|
local text1="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
|
|
local text2="DCS : Airport ID = "..airbase:getID().." and Name = "..airbase:getName()..", Category = "..airbase:getCategory()..", TypeName = "..airbase:getTypeName()
|
|
env.info(RAT.id..text1)
|
|
env.info(RAT.id..text2)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function RAT:_GetAirportsOfCoalition()
|
|
for _,coalition in pairs(self.ctable)do
|
|
for _,airport in pairs(self.airports_map)do
|
|
if airport:GetCoalition()==coalition then
|
|
local condition1=self.category==RAT.cat.plane and airport:GetTypeName()=="FARP"
|
|
local condition2=self.category==RAT.cat.plane and airport:GetCategory()==1
|
|
if not(condition1 or condition2)then
|
|
table.insert(self.airports,airport)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if#self.airports==0 then
|
|
local text="No possible departure/destination airports found!"
|
|
MESSAGE:New(text,60):ToAll()
|
|
env.error(RAT.id..text)
|
|
end
|
|
end
|
|
function RAT:Status(message,forID)
|
|
message=message or false
|
|
forID=forID or false
|
|
local ngroups=#self.ratcraft
|
|
if(message and not forID)or self.reportstatus then
|
|
local text=string.format("Alive groups of template %s: %d",self.SpawnTemplatePrefix,self.alive)
|
|
env.info(RAT.id..text)
|
|
MESSAGE:New(text,20):ToAll()
|
|
end
|
|
local Tnow=timer.getTime()
|
|
for i=1,ngroups do
|
|
if self.ratcraft[i].group then
|
|
if self.ratcraft[i].group:IsAlive()then
|
|
local group=self.ratcraft[i].group
|
|
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
|
|
local departure=self.ratcraft[i].departure:GetName()
|
|
local destination=self.ratcraft[i].destination:GetName()
|
|
local type=self.aircraft.type
|
|
local Tg=0
|
|
local Dg=0
|
|
local dTlast=0
|
|
local stationary=false
|
|
if airborne then
|
|
self.ratcraft[i]["Tground"]=nil
|
|
self.ratcraft[i]["Pground"]=nil
|
|
self.ratcraft[i]["Tlastcheck"]=nil
|
|
else
|
|
if self.ratcraft[i]["Tground"]then
|
|
Tg=Tnow-self.ratcraft[i]["Tground"]
|
|
Dg=coords:Get2DDistance(self.ratcraft[i]["Pground"])
|
|
dTlast=Tnow-self.ratcraft[i]["Tlastcheck"]
|
|
if dTlast>self.Tinactive then
|
|
if Dg<50 then
|
|
stationary=true
|
|
end
|
|
self.ratcraft[i]["Tlastcheck"]=Tnow
|
|
self.ratcraft[i]["Pground"]=coords
|
|
end
|
|
else
|
|
self.ratcraft[i]["Tground"]=Tnow
|
|
self.ratcraft[i]["Tlastcheck"]=Tnow
|
|
self.ratcraft[i]["Pground"]=coords
|
|
end
|
|
end
|
|
local Pn=coords
|
|
local Dtravel=Pn:Get2DDistance(self.ratcraft[i]["Pnow"])
|
|
self.ratcraft[i]["Pnow"]=Pn
|
|
self.ratcraft[i]["Distance"]=self.ratcraft[i]["Distance"]+Dtravel
|
|
local Ddestination=Pn:Get2DDistance(self.ratcraft[i].destination:GetCoordinate())
|
|
local Hp=COORDINATE:New(self.ratcraft[i].waypoints[6].x,self.ratcraft[i].waypoints[6].alt,self.ratcraft[i].waypoints[6].y)
|
|
local Dholding=Pn:Get2DDistance(Hp)
|
|
local status=self.ratcraft[i].status
|
|
local DRholding
|
|
if self.category==RAT.cat.plane then
|
|
DRholding=8000
|
|
else
|
|
DRholding=2000
|
|
end
|
|
if self.ATCswitch and Dholding<=DRholding and string.match(status,"On journey")then
|
|
RAT:_ATCRegisterFlight(group:GetName(),Tnow)
|
|
self.ratcraft[i].status="Holding"
|
|
end
|
|
if(forID and i==forID)or(not forID)then
|
|
local text=string.format("ID %i of group %s\n",i,prefix)
|
|
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",self.ratcraft[i].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\n",alt/RAT.unit.FL2m,alt)
|
|
text=text..string.format("Distance travelled = %6.1f km\n",self.ratcraft[i]["Distance"]/1000)
|
|
text=text..string.format("Distance to destination = %6.1f km",Dholding/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
|
|
if self.debug then
|
|
env.info(RAT.id..text)
|
|
end
|
|
if self.reportstatus or 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 %4.0f seconds inaktive on ground.",self.SpawnTemplatePrefix,dTlast)
|
|
env.info(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.SpawnTemplatePrefix,life)
|
|
self:_Despawn(group)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
if self.debug then
|
|
local text=string.format("Group %i does not exist.",i)
|
|
env.info(RAT.id..text)
|
|
end
|
|
end
|
|
end
|
|
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
|
|
if self.debug then
|
|
env.error(RAT.id.."Unit does not exist in RAT_Getlife(). Returning zero.")
|
|
end
|
|
end
|
|
else
|
|
if self.debug then
|
|
env.error(RAT.id.."Group does not exist in RAT_Getlife(). Returning zero.")
|
|
end
|
|
end
|
|
return life
|
|
end
|
|
function RAT:_SetStatus(group,status)
|
|
local index=self:GetSpawnIndexFromGroup(group)
|
|
env.info(RAT.id.."Status for group "..group:GetName()..": "..status)
|
|
self.ratcraft[index].status=status
|
|
end
|
|
function RAT:_OnBirth(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().." was born."
|
|
env.info(RAT.id..text)
|
|
local status
|
|
if SpawnGroup:InAir()then
|
|
status="Just born (after air start)"
|
|
else
|
|
status="Starting engines (after birth)"
|
|
end
|
|
self:_SetStatus(SpawnGroup,status)
|
|
end
|
|
end
|
|
else
|
|
if self.debug then
|
|
env.error("Group does not exist in RAT:_OnBirthDay().")
|
|
end
|
|
end
|
|
end
|
|
function RAT:_EngineStartup(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().." started engines."
|
|
env.info(RAT.id..text)
|
|
local status
|
|
if SpawnGroup:InAir()then
|
|
status="On journey (after air start)"
|
|
else
|
|
status="Taxiing (after engines started)"
|
|
end
|
|
self:_SetStatus(SpawnGroup,status)
|
|
end
|
|
end
|
|
else
|
|
if self.debug then
|
|
env.error("Group does not exist in RAT:_EngineStartup().")
|
|
end
|
|
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."
|
|
env.info(RAT.id..text)
|
|
self:_SetStatus(SpawnGroup,"On journey (after takeoff)")
|
|
end
|
|
end
|
|
else
|
|
if self.debug then
|
|
env.error("Group does not exist in RAT:_OnTakeoff().")
|
|
end
|
|
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."
|
|
env.info(RAT.id..text)
|
|
self:_SetStatus(SpawnGroup,"Taxiing (after landing)")
|
|
if self.ATCswitch then
|
|
RAT:_ATCFlightLanded(SpawnGroup:GetName())
|
|
end
|
|
if self.respawn_at_landing then
|
|
text="Event: Group "..SpawnGroup:GetName().." will be respawned."
|
|
env.info(RAT.id..text)
|
|
self:_Respawn(SpawnGroup)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
if self.debug then
|
|
env.error("Group does not exist in RAT:_OnLand().")
|
|
end
|
|
end
|
|
end
|
|
function RAT:_OnEngineShutdown(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().." shut down its engines."
|
|
env.info(RAT.id..text)
|
|
self:_SetStatus(SpawnGroup,"Parking (shutting down engines)")
|
|
if not self.respawn_at_landing then
|
|
text="Event: Group "..SpawnGroup:GetName().." will be respawned."
|
|
env.info(RAT.id..text)
|
|
self:_Respawn(SpawnGroup)
|
|
end
|
|
text="Event: Group "..SpawnGroup:GetName().." will be destroyed now."
|
|
env.info(RAT.id..text)
|
|
self:_Despawn(SpawnGroup)
|
|
end
|
|
end
|
|
else
|
|
if self.debug then
|
|
env.error("Group does not exist in RAT:_OnEngineShutdown().")
|
|
end
|
|
end
|
|
end
|
|
function RAT:_OnDead(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().." died."
|
|
env.info(RAT.id..text)
|
|
self:_SetStatus(SpawnGroup,"Destroyed (after dead)")
|
|
end
|
|
end
|
|
else
|
|
if self.debug then
|
|
env.error("Group does not exist in RAT:_OnDead().")
|
|
end
|
|
end
|
|
end
|
|
function RAT:_OnCrash(EventData)
|
|
local SpawnGroup=EventData.IniGroup
|
|
env.info(string.format("%sGroup %s crashed!",RAT.id,SpawnGroup:GetName()))
|
|
if SpawnGroup then
|
|
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
|
|
if EventPrefix then
|
|
if EventPrefix==self.alias then
|
|
local text="Event: Group "..SpawnGroup:GetName().." crashed."
|
|
env.info(RAT.id..text)
|
|
self:_SetStatus(SpawnGroup,"Crashed")
|
|
end
|
|
end
|
|
else
|
|
if self.debug then
|
|
env.error("Group does not exist in RAT:_OnCrash().")
|
|
end
|
|
end
|
|
end
|
|
function RAT:_Despawn(group)
|
|
local index=self:GetSpawnIndexFromGroup(group)
|
|
self.ratcraft[index].group:Destroy()
|
|
self.ratcraft[index].group=nil
|
|
self.alive=self.alive-1
|
|
if self.f10menu then
|
|
self.Menu[self.SubMenuName]["groups"][index]:Remove()
|
|
end
|
|
end
|
|
function RAT:_Waypoint(Type,Coord,Speed,Altitude,Airport)
|
|
local _Altitude=Altitude or Coord.y
|
|
local Hland=Coord:GetLandHeight()
|
|
local _Type=nil
|
|
local _Action=nil
|
|
local _alttype="RADIO"
|
|
local _AID=nil
|
|
if Type==RAT.wp.cold then
|
|
_Type="TakeOffParking"
|
|
_Action="From Parking Area"
|
|
_Altitude=0
|
|
_alttype="RADIO"
|
|
_AID=Airport:GetID()
|
|
elseif Type==RAT.wp.hot then
|
|
_Type="TakeOffParkingHot"
|
|
_Action="From Parking Area Hot"
|
|
_Altitude=0
|
|
_alttype="RADIO"
|
|
_AID=Airport:GetID()
|
|
elseif Type==RAT.wp.runway then
|
|
_Type="TakeOff"
|
|
_Action="From Parking Area"
|
|
_Altitude=0
|
|
_alttype="RADIO"
|
|
_AID=Airport:GetID()
|
|
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=0
|
|
_alttype="RADIO"
|
|
_AID=Airport:GetID()
|
|
else
|
|
env.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("Template = %s\n",self.SpawnTemplatePrefix)
|
|
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 with ID %i\n",Airport:GetName(),Airport:GetID())
|
|
end
|
|
else
|
|
text=text..string.format("No airport/zone specified\n")
|
|
end
|
|
text=text.."******************************************************\n"
|
|
if self.debug then
|
|
env.info(RAT.id..text)
|
|
end
|
|
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="RAT waypoint"
|
|
if(Airport~=nil)and Type~=RAT.wp.air then
|
|
local AirbaseID=Airport:GetID()
|
|
local AirbaseCategory=Airport:GetDesc().category
|
|
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
|
|
end
|
|
end
|
|
RoutePoint.properties={
|
|
["vnav"]=1,
|
|
["scale"]=0,
|
|
["angle"]=0,
|
|
["vangle"]=0,
|
|
["steer"]=2,
|
|
}
|
|
if Type==RAT.wp.holding then
|
|
local Duration=self:_Randomize(90,0.9)
|
|
RoutePoint.task=self:_TaskHolding({x=Coord.x,y=Coord.z},Altitude,Speed,Duration)
|
|
else
|
|
RoutePoint.task={}
|
|
RoutePoint.task.id="ComboTask"
|
|
RoutePoint.task.params={}
|
|
RoutePoint.task.params.tasks={}
|
|
end
|
|
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\n",i-1,p.x/1000,p.y/1000,p.alt)
|
|
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 = %i.\n",i-1,i,d/1000,heading)
|
|
end
|
|
text=text..string.format("Total distance = %6.1f km\n",total/1000)
|
|
local text=string.format("******************************************************\n")
|
|
if self.debug then
|
|
env.info(RAT.id..text)
|
|
end
|
|
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)
|
|
DCSTask.params.stopCondition={userFlag=userflagname,userFlagValue=1,duration=1800}
|
|
else
|
|
DCSTask.params.stopCondition={duration=Duration}
|
|
end
|
|
return DCSTask
|
|
end
|
|
function RAT:_FLmax(alpha,beta,d,phi,h0)
|
|
local gamma=math.rad(180)-alpha-beta
|
|
local a=d*math.sin(alpha)/math.sin(gamma)
|
|
local b=d*math.sin(beta)/math.sin(gamma)
|
|
local h1=b*math.sin(alpha)
|
|
local h2=a*math.sin(beta)
|
|
local h3=b*math.cos(math.pi/2-(alpha+phi))
|
|
local text=string.format("\nFLmax = FL%3.0f = %6.1f m.\n",h1/RAT.unit.FL2m,h1)
|
|
text=text..string.format("FLmax = FL%3.0f = %6.1f m.\n",h2/RAT.unit.FL2m,h2)
|
|
text=text..string.format("FLmax = FL%3.0f = %6.1f m.",h3/RAT.unit.FL2m,h3)
|
|
if self.debug then
|
|
env.info(RAT.id..text)
|
|
end
|
|
return h3+h0
|
|
end
|
|
function RAT:_MinDistance(alpha,beta,h)
|
|
local d1=h/math.tan(alpha)
|
|
local d2=h/math.tan(beta)
|
|
return d1+d2
|
|
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:_SetROE(group,roe)
|
|
env.info(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)
|
|
env.info(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
|
|
env.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:_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)
|
|
env.info(RAT.id..text)
|
|
end
|
|
return r
|
|
end
|
|
function RAT:_Random_Gaussian(x0,sigma,xmin,xmax)
|
|
sigma=sigma or 10
|
|
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 RAT:_PlaceMarkers(waypoints)
|
|
self:_SetMarker("Takeoff",waypoints[1])
|
|
self:_SetMarker("Climb",waypoints[2])
|
|
self:_SetMarker("Begin of Cruise",waypoints[3])
|
|
self:_SetMarker("End of Cruise",waypoints[4])
|
|
self:_SetMarker("Descent",waypoints[5])
|
|
self:_SetMarker("Holding Point",waypoints[6])
|
|
self:_SetMarker("Destination",waypoints[7])
|
|
end
|
|
function RAT:_SetMarker(text,wp)
|
|
RAT.markerid=RAT.markerid+1
|
|
self.markerids[#self.markerids+1]=RAT.markerid
|
|
if self.debug then
|
|
env.info(RAT.id..self.SpawnTemplatePrefix..": placing marker with ID "..RAT.markerid..": "..text)
|
|
end
|
|
local vec={x=wp.x,y=wp.alt,z=wp.y}
|
|
local text1=string.format("%s:\n%s",self.alias,text)
|
|
trigger.action.markToAll(RAT.markerid,text1,vec)
|
|
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)
|
|
local PointVec3={x=waypoints[1].x,y=waypoints[1].alt,z=waypoints[1].y}
|
|
local heading=self:_Course(waypoints[1],waypoints[2])
|
|
if self:_GetSpawnIndex(self.SpawnIndex+1)then
|
|
local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
|
|
if SpawnTemplate then
|
|
self:T(SpawnTemplate)
|
|
for UnitID=1,#SpawnTemplate.units do
|
|
self:T('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)
|
|
SpawnTemplate.units[UnitID].x=TX
|
|
SpawnTemplate.units[UnitID].y=TY
|
|
SpawnTemplate.units[UnitID].alt=PointVec3.y
|
|
SpawnTemplate.units[UnitID].heading=math.rad(heading)
|
|
if self.livery then
|
|
SpawnTemplate.units[UnitID].livery_id=self.livery[math.random(#self.livery)]
|
|
end
|
|
SpawnTemplate.units[UnitID]["skill"]=self.skill
|
|
SpawnTemplate.units[UnitID]["onboard_num"]=self.SpawnIndex
|
|
SpawnTemplate.CoalitionID=self.coalition
|
|
if self.country then
|
|
SpawnTemplate.CountryID=self.country
|
|
end
|
|
UnitTemplate.parking=nil
|
|
UnitTemplate.parking_id=nil
|
|
UnitTemplate.alt=PointVec3.y
|
|
self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y)
|
|
end
|
|
for i,wp in ipairs(waypoints)do
|
|
SpawnTemplate.route.points[i]=wp
|
|
end
|
|
SpawnTemplate.x=PointVec3.x
|
|
SpawnTemplate.y=PointVec3.z
|
|
self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate
|
|
self:T(SpawnTemplate)
|
|
end
|
|
end
|
|
end
|
|
function RAT:_ATCInit(airports_map)
|
|
if not RAT.ATC.init then
|
|
env.info(RAT.id.."Starting RAT ATC.")
|
|
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=nil
|
|
RAT.ATC.airport[name].traffic=0
|
|
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)
|
|
env.info(string.format("%s%s ATC: 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)
|
|
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 is currently clear"
|
|
if RAT.ATC.airport[dest].busy then
|
|
if RAT.ATC.airport[dest].onfinal then
|
|
busy="Runway is occupied by "..RAT.ATC.airport[dest].onfinal
|
|
else
|
|
busy="Runway is occupied"
|
|
end
|
|
end
|
|
env.info(string.format("%s%s ATC: Flight %s is holding for %i:%02d. %s.",RAT.id,dest,name,hold/60,hold%60,busy))
|
|
elseif hold==RAT.ATC.onfinal then
|
|
local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal
|
|
env.info(string.format("%s%s ATC: Flight %s was cleared for landing. Waiting %i:%02d for landing event.",RAT.id,dest,name,Tfinal/60,Tfinal%60))
|
|
if Tfinal>300 then
|
|
end
|
|
elseif hold==RAT.ATC.unregistered then
|
|
else
|
|
env.error(RAT.id.."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
|
|
local qw={}
|
|
for qID,flight in ipairs(RAT.ATC.airport[name].queue)do
|
|
local nqueue=#RAT.ATC.airport[name].queue
|
|
if RAT.ATC.airport[name].busy then
|
|
RAT.ATC.flight[flight].holding=Tnow-RAT.ATC.flight[flight].Tarrive
|
|
local text=string.format("%s ATC: 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)
|
|
env.info(text)
|
|
else
|
|
RAT:_ATCClearForLanding(name,flight)
|
|
table.insert(qw,qID)
|
|
end
|
|
end
|
|
for _,i in pairs(qw)do
|
|
table.remove(RAT.ATC.airport[name].queue,i)
|
|
end
|
|
end
|
|
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
|
|
RAT.ATC.flight[flight].Tonfinal=timer.getTime()
|
|
trigger.action.setUserFlag(flight,1)
|
|
local flagvalue=trigger.misc.getUserFlag(flight)
|
|
local text1=string.format("%s%s ATC: Flight %s cleared for final approach (flag=%d).",RAT.id,airport,flight,flagvalue)
|
|
local text2=string.format("%s ATC: Flight %s you are cleared for landing.",airport,flight)
|
|
env.info(text1)
|
|
MESSAGE:New(text2,10):ToAll()
|
|
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=nil
|
|
RAT:_ATCDelFlight(RAT.ATC.flight,name)
|
|
RAT.ATC.airport[dest].traffic=RAT.ATC.airport[dest].traffic+1
|
|
local text1=string.format("%s%s ATC: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",RAT.id,dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)
|
|
local text2=string.format("%s ATC: Flight %s landed. Welcome to %s.",dest,name,dest)
|
|
env.info(text1)
|
|
env.info(string.format("%s%s ATC: Number of planes landed in total %d.",RAT.id,dest,RAT.ATC.airport[dest].traffic))
|
|
MESSAGE:New(text2,10):ToAll()
|
|
end
|
|
end
|
|
function RAT:_ATCQueue()
|
|
for airport,_ in pairs(RAT.ATC.airport)do
|
|
local _queue={}
|
|
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 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
|
|
PROTECT={
|
|
ClassName="PROTECT",
|
|
}
|
|
function PROTECT:New(ProtectZone,Coalition)
|
|
local self=BASE:Inherit(self,FSM:New())
|
|
self.ProtectZone=ProtectZone
|
|
self.ProtectUnitSet=SET_UNIT:New()
|
|
self.ProtectStaticSet=SET_STATIC:New()
|
|
self.CaptureUnitSet=SET_UNIT:New()
|
|
self:SetStartState("-")
|
|
self:AddTransition({"-","Protected","Captured"},"Protected","Protected")
|
|
self:AddTransition({"Protected","Attacked"},"Destroyed","Destroyed")
|
|
self:AddTransition({"Protected","Destroyed"},"Attacked","Attacked")
|
|
self:AddTransition({"Protected","Attacked","Destroyed"},"Captured","Captured")
|
|
self:ScheduleRepeat(60,60,0,nil,self.Status,self)
|
|
self:SetCoalition(Coalition)
|
|
self:__Protected(5)
|
|
return self
|
|
end
|
|
function PROTECT:SetCoalition(Coalition)
|
|
self.Coalition=Coalition
|
|
end
|
|
function PROTECT:GetCoalition()
|
|
return self.Coalition
|
|
end
|
|
function PROTECT:AddProtectUnit(ProtectUnit)
|
|
self.ProtectUnitSet:AddUnit(ProtectUnit)
|
|
end
|
|
function PROTECT:GetProtectUnitSet()
|
|
return self.ProtectUnitSet
|
|
end
|
|
function PROTECT:AddProtectStatic(ProtectStatic)
|
|
self.ProtectStaticSet:AddStatic(ProtectStatic)
|
|
end
|
|
function PROTECT:GetProtectStaticSet()
|
|
return self.ProtectStaticSet
|
|
end
|
|
function PROTECT:AddCaptureUnit(CaptureUnit)
|
|
self.CaptureUnitSet:AddUnit(CaptureUnit)
|
|
end
|
|
function PROTECT:GetCaptureUnitSet()
|
|
return self.CaptureUnitSet
|
|
end
|
|
function PROTECT:IsProtected()
|
|
local IsAllCoalition=self.ProtectZone:IsAllInZoneOfCoalition(self.Coalition)
|
|
self:E({IsAllCoalition=IsAllCoalition})
|
|
return IsAllCoalition
|
|
end
|
|
function PROTECT:IsCaptured()
|
|
local IsCaptured=self.ProtectZone:IsAllInZoneOfOtherCoalition(self.Coalition)
|
|
self:E({IsCaptured=IsCaptured})
|
|
return IsCaptured
|
|
end
|
|
function PROTECT:IsAttacked()
|
|
local IsSomeCoalition=self.ProtectZone:IsSomeInZoneOfCoalition(self.Coalition)
|
|
self:E({IsSomeCoalition=IsSomeCoalition})
|
|
return IsSomeCoalition
|
|
end
|
|
function PROTECT:AreProtectUnitsAlive()
|
|
local IsAlive=false
|
|
local UnitSet=self.ProtectUnitSet
|
|
UnitSet:Flush()
|
|
local UnitList=UnitSet:GetSet()
|
|
for UnitID,ProtectUnit in pairs(UnitList)do
|
|
local IsUnitAlive=ProtectUnit:IsAlive()
|
|
if IsUnitAlive==true then
|
|
IsAlive=true
|
|
break
|
|
end
|
|
end
|
|
return IsAlive
|
|
end
|
|
function PROTECT:AreProtectStaticsAlive()
|
|
local IsAlive=false
|
|
local StaticSet=self.ProtectStaticSet
|
|
StaticSet:Flush()
|
|
local StaticList=StaticSet:GetSet()
|
|
for UnitID,ProtectStatic in pairs(StaticList)do
|
|
local IsStaticAlive=ProtectStatic:IsAlive()
|
|
if IsStaticAlive==true then
|
|
IsAlive=true
|
|
break
|
|
end
|
|
end
|
|
return IsAlive
|
|
end
|
|
function PROTECT:IsCaptureUnitInZone()
|
|
local CaptureUnitSet=self.CaptureUnitSet
|
|
CaptureUnitSet:Flush()
|
|
local IsInZone=self.CaptureUnitSet:IsPartiallyInZone(self.ProtectZone)
|
|
self:E({IsInZone=IsInZone})
|
|
return IsInZone
|
|
end
|
|
function PROTECT:Smoke(SmokeColor)
|
|
self.ProtectZone:GetCoordinate():Smoke(SmokeColor)
|
|
end
|
|
function PROTECT:onenterCaptured()
|
|
local NewCoalition=self.ProtectZone:GetCoalition()
|
|
self:E({NewCoalition=NewCoalition})
|
|
self:SetCoalition(NewCoalition)
|
|
end
|
|
function PROTECT:Status()
|
|
self.ProtectZone:Scan()
|
|
if self:IsProtected()then
|
|
self:Protected()
|
|
else
|
|
if self:IsAttacked()then
|
|
self:Attacked()
|
|
else
|
|
if self:IsCaptured()then
|
|
self:Captured()
|
|
end
|
|
end
|
|
end
|
|
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:E("Spawning new AIGroup")
|
|
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()
|
|
SetGroup:Remove(ClientName)
|
|
SetGroup:Flush()
|
|
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:MessageToRed("Returning to "..ClosestAirbase:GetName().." ...",30)
|
|
local RTBRoute=AIGroup:RouteReturnToAirbase(ClosestAirbase)
|
|
AIGroupTemplate.route=RTBRoute
|
|
AIGroup:Respawn(AIGroupTemplate)
|
|
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 Client:IsAlive()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:ForEachPlayer(
|
|
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.")
|
|
if not self.SpawnQueue[Client.UnitName]then
|
|
self:__Spawn(math.random(self.Earliest,self.Latest),Client.UnitName)
|
|
self.SpawnQueue[Client.UnitName]=true
|
|
self:E("New AI Spawned for Client "..Client.UnitName)
|
|
end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
)
|
|
self:__Monitor(10)
|
|
end
|
|
AI_A2A={
|
|
ClassName="AI_A2A",
|
|
}
|
|
function AI_A2A:New(AIGroup)
|
|
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
|
|
self:SetControllable(AIGroup)
|
|
self:SetFuelThreshold(.2,60)
|
|
self:SetDamageThreshold(0.4)
|
|
self:SetDisengageRadius(70000)
|
|
self:SetStartState("Stopped")
|
|
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
|
|
return self
|
|
end
|
|
function GROUP:OnEventTakeoff(EventData,Fsm)
|
|
Fsm:Takeoff()
|
|
self:UnHandleEvent(EVENTS.Takeoff)
|
|
end
|
|
function AI_A2A:SetDispatcher(Dispatcher)
|
|
self.Dispatcher=Dispatcher
|
|
end
|
|
function AI_A2A:GetDispatcher()
|
|
return self.Dispatcher
|
|
end
|
|
function AI_A2A: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_A2A:ClearTargetDistance()
|
|
self.TargetDistance=nil
|
|
self.ClosestTargetDistance=nil
|
|
end
|
|
function AI_A2A:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed)
|
|
self:F2({PatrolMinSpeed,PatrolMaxSpeed})
|
|
self.PatrolMinSpeed=PatrolMinSpeed
|
|
self.PatrolMaxSpeed=PatrolMaxSpeed
|
|
end
|
|
function AI_A2A:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude)
|
|
self:F2({PatrolFloorAltitude,PatrolCeilingAltitude})
|
|
self.PatrolFloorAltitude=PatrolFloorAltitude
|
|
self.PatrolCeilingAltitude=PatrolCeilingAltitude
|
|
end
|
|
function AI_A2A:SetHomeAirbase(HomeAirbase)
|
|
self:F2({HomeAirbase})
|
|
self.HomeAirbase=HomeAirbase
|
|
end
|
|
function AI_A2A:SetTanker(TankerName)
|
|
self:F2({TankerName})
|
|
self.TankerName=TankerName
|
|
end
|
|
function AI_A2A:SetDisengageRadius(DisengageRadius)
|
|
self:F2({DisengageRadius})
|
|
self.DisengageRadius=DisengageRadius
|
|
end
|
|
function AI_A2A:SetStatusOff()
|
|
self:F2()
|
|
self.CheckStatus=false
|
|
end
|
|
function AI_A2A:SetFuelThreshold(PatrolFuelThresholdPercentage,PatrolOutOfFuelOrbitTime)
|
|
self.PatrolManageFuel=true
|
|
self.PatrolFuelThresholdPercentage=PatrolFuelThresholdPercentage
|
|
self.PatrolOutOfFuelOrbitTime=PatrolOutOfFuelOrbitTime
|
|
self.Controllable:OptionRTBBingoFuel(false)
|
|
return self
|
|
end
|
|
function AI_A2A:SetDamageThreshold(PatrolDamageThreshold)
|
|
self.PatrolManageDamage=true
|
|
self.PatrolDamageThreshold=PatrolDamageThreshold
|
|
return self
|
|
end
|
|
function AI_A2A:onafterStart(Controllable,From,Event,To)
|
|
self:F2()
|
|
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_A2A:onbeforeStatus()
|
|
return self.CheckStatus
|
|
end
|
|
function AI_A2A:onafterStatus()
|
|
self:F(" Checking Status")
|
|
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())
|
|
self:F({DistanceFromHomeBase=DistanceFromHomeBase})
|
|
if DistanceFromHomeBase>self.DisengageRadius then
|
|
self:E(self.Controllable:GetName().." is too far from home base, RTB!")
|
|
self:Hold(300)
|
|
RTB=false
|
|
end
|
|
end
|
|
if self:Is("Fuel")or self:Is("Damaged")or self:Is("LostControl")then
|
|
if DistanceFromHomeBase<5000 then
|
|
self:E(self.Controllable:GetName().." is too far from home base, RTB!")
|
|
self:Home("Destroy")
|
|
end
|
|
end
|
|
if not self:Is("Fuel")and not self:Is("Home")then
|
|
local Fuel=self.Controllable:GetFuel()
|
|
self:F({Fuel=Fuel})
|
|
if Fuel<self.PatrolFuelThresholdPercentage then
|
|
if self.TankerName then
|
|
self:E(self.Controllable:GetName().." is out of fuel: "..Fuel.." ... Refuelling at Tanker!")
|
|
self:Refuel()
|
|
else
|
|
self:E(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)
|
|
self:Fuel()
|
|
RTB=true
|
|
end
|
|
else
|
|
end
|
|
end
|
|
local Damage=self.Controllable:GetLife()
|
|
local InitialLife=self.Controllable:GetLife0()
|
|
self:F({Damage=Damage,InitialLife=InitialLife,DamageThreshold=self.PatrolDamageThreshold})
|
|
if(Damage/InitialLife)<self.PatrolDamageThreshold then
|
|
self:E(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("Home")then
|
|
if self.IdleCount>=2 then
|
|
if Damage~=InitialLife then
|
|
self:Damaged()
|
|
else
|
|
self:E(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(0.5)
|
|
end
|
|
self:__Status(10)
|
|
end
|
|
end
|
|
function AI_A2A.RTBRoute(AIGroup,Fsm)
|
|
AIGroup:F({"AI_A2A.RTBRoute:",AIGroup:GetName()})
|
|
if AIGroup:IsAlive()then
|
|
Fsm:__RTB(0.5)
|
|
end
|
|
end
|
|
function AI_A2A.RTBHold(AIGroup,Fsm)
|
|
AIGroup:F({"AI_A2A.RTBHold:",AIGroup:GetName()})
|
|
if AIGroup:IsAlive()then
|
|
Fsm:__RTB(0.5)
|
|
Fsm:Return()
|
|
local Task=AIGroup:TaskOrbitCircle(4000,400)
|
|
AIGroup:SetTask(Task)
|
|
end
|
|
end
|
|
function AI_A2A:onafterRTB(AIGroup,From,Event,To)
|
|
self:F({AIGroup,From,Event,To})
|
|
if AIGroup and AIGroup:IsAlive()then
|
|
self:E("Group "..AIGroup:GetName().." ... RTB! ( "..self:GetState().." )")
|
|
self:ClearTargetDistance()
|
|
AIGroup:ClearTasks()
|
|
local EngageRoute={}
|
|
local CurrentCoord=AIGroup:GetCoordinate()
|
|
local ToTargetCoord=self.HomeAirbase:GetCoordinate()
|
|
local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
|
|
local ToAirbaseAngle=CurrentCoord:GetAngleDegrees(CurrentCoord:GetDirectionVec3(ToTargetCoord))
|
|
local Distance=CurrentCoord:Get2DDistance(ToTargetCoord)
|
|
local ToAirbaseCoord=CurrentCoord:Translate(5000,ToAirbaseAngle)
|
|
if Distance<5000 then
|
|
self:E("RTB and near the airbase!")
|
|
self:Home()
|
|
return
|
|
end
|
|
local ToRTBRoutePoint=ToAirbaseCoord:WaypointAir(
|
|
self.PatrolAltType,
|
|
POINT_VEC3.RoutePointType.TurningPoint,
|
|
POINT_VEC3.RoutePointAction.TurningPoint,
|
|
ToTargetSpeed,
|
|
true
|
|
)
|
|
self:F({Angle=ToAirbaseAngle,ToTargetSpeed=ToTargetSpeed})
|
|
self:T2({self.MinSpeed,self.MaxSpeed,ToTargetSpeed})
|
|
EngageRoute[#EngageRoute+1]=ToRTBRoutePoint
|
|
EngageRoute[#EngageRoute+1]=ToRTBRoutePoint
|
|
AIGroup:OptionROEHoldFire()
|
|
AIGroup:OptionROTEvadeFire()
|
|
AIGroup:WayPointInitialize(EngageRoute)
|
|
local Tasks={}
|
|
Tasks[#Tasks+1]=AIGroup:TaskFunction("AI_A2A.RTBRoute",self)
|
|
EngageRoute[#EngageRoute].task=AIGroup:TaskCombo(Tasks)
|
|
AIGroup:Route(EngageRoute,0.5)
|
|
end
|
|
end
|
|
function AI_A2A:onafterHome(AIGroup,From,Event,To)
|
|
self:F({AIGroup,From,Event,To})
|
|
self:E("Group "..self.Controllable:GetName().." ... Home! ( "..self:GetState().." )")
|
|
if AIGroup and AIGroup:IsAlive()then
|
|
end
|
|
end
|
|
function AI_A2A:onafterHold(AIGroup,From,Event,To,HoldTime)
|
|
self:F({AIGroup,From,Event,To})
|
|
self:E("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_A2A.RTBHold",self)
|
|
local OrbitHoldTask=AIGroup:TaskOrbitCircle(4000,self.PatrolMinSpeed)
|
|
AIGroup:SetTask(AIGroup:TaskCombo({TimedOrbitTask,RTBTask,OrbitHoldTask}),1)
|
|
end
|
|
end
|
|
function AI_A2A.Resume(AIGroup,Fsm)
|
|
AIGroup:F({"AI_A2A.Resume:",AIGroup:GetName()})
|
|
if AIGroup:IsAlive()then
|
|
Fsm:__RTB(0.5)
|
|
end
|
|
end
|
|
function AI_A2A:onafterRefuel(AIGroup,From,Event,To)
|
|
self:F({AIGroup,From,Event,To})
|
|
self:E("Group "..self.Controllable:GetName().." ... Refuelling! ( "..self:GetState().." )")
|
|
if AIGroup and AIGroup:IsAlive()then
|
|
local Tanker=GROUP:FindByName(self.TankerName)
|
|
if Tanker:IsAlive()and Tanker:IsAirPlane()then
|
|
local RefuelRoute={}
|
|
local CurrentCoord=AIGroup:GetCoordinate()
|
|
local ToRefuelCoord=Tanker:GetCoordinate()
|
|
local ToRefuelSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
|
|
local ToRefuelRoutePoint=ToRefuelCoord:WaypointAir(
|
|
self.PatrolAltType,
|
|
POINT_VEC3.RoutePointType.TurningPoint,
|
|
POINT_VEC3.RoutePointAction.TurningPoint,
|
|
ToRefuelSpeed,
|
|
true
|
|
)
|
|
self:F({ToRefuelSpeed=ToRefuelSpeed})
|
|
RefuelRoute[#RefuelRoute+1]=ToRefuelRoutePoint
|
|
RefuelRoute[#RefuelRoute+1]=ToRefuelRoutePoint
|
|
AIGroup:OptionROEHoldFire()
|
|
AIGroup:OptionROTEvadeFire()
|
|
local Tasks={}
|
|
Tasks[#Tasks+1]=AIGroup:TaskRefueling()
|
|
Tasks[#Tasks+1]=AIGroup:TaskFunction(self:GetClassName()..".Resume",self)
|
|
RefuelRoute[#RefuelRoute].task=AIGroup:TaskCombo(Tasks)
|
|
AIGroup:Route(RefuelRoute,0.5)
|
|
else
|
|
self:RTB()
|
|
end
|
|
end
|
|
end
|
|
function AI_A2A:onafterDead()
|
|
self:SetStatusOff()
|
|
end
|
|
function AI_A2A:OnCrash(EventData)
|
|
if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then
|
|
self:E(self.Controllable:GetUnits())
|
|
if#self.Controllable:GetUnits()==1 then
|
|
self:__Crash(1,EventData)
|
|
end
|
|
end
|
|
end
|
|
function AI_A2A:OnEjection(EventData)
|
|
if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then
|
|
self:__Eject(1,EventData)
|
|
end
|
|
end
|
|
function AI_A2A:OnPilotDead(EventData)
|
|
if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then
|
|
self:__PilotDead(1,EventData)
|
|
end
|
|
end
|
|
AI_A2A_PATROL={
|
|
ClassName="AI_A2A_PATROL",
|
|
}
|
|
function AI_A2A_PATROL:New(AIGroup,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)
|
|
local self=BASE:Inherit(self,AI_A2A:New(AIGroup))
|
|
self.PatrolZone=PatrolZone
|
|
self.PatrolFloorAltitude=PatrolFloorAltitude
|
|
self.PatrolCeilingAltitude=PatrolCeilingAltitude
|
|
self.PatrolMinSpeed=PatrolMinSpeed
|
|
self.PatrolMaxSpeed=PatrolMaxSpeed
|
|
self.PatrolAltType=PatrolAltType or"RADIO"
|
|
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(Controllable,From,Event,To)
|
|
self:F2()
|
|
self:ClearTargetDistance()
|
|
self:__Route(1)
|
|
self.Controllable:OnReSpawn(
|
|
function(PatrolGroup)
|
|
self:E("ReSpawn")
|
|
self:__Reset(1)
|
|
self:__Route(5)
|
|
end
|
|
)
|
|
end
|
|
function AI_A2A_PATROL.PatrolRoute(AIGroup,Fsm)
|
|
AIGroup:F({"AI_A2A_PATROL.PatrolRoute:",AIGroup:GetName()})
|
|
if AIGroup:IsAlive()then
|
|
Fsm:Route()
|
|
end
|
|
end
|
|
function AI_A2A_PATROL:onafterRoute(AIGroup,From,Event,To)
|
|
self:F2()
|
|
if From=="RTB"then
|
|
return
|
|
end
|
|
if AIGroup:IsAlive()then
|
|
local PatrolRoute={}
|
|
local CurrentCoord=AIGroup:GetCoordinate()
|
|
local ToTargetCoord=self.PatrolZone:GetRandomPointVec2()
|
|
ToTargetCoord:SetAlt(math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude))
|
|
self:SetTargetDistance(ToTargetCoord)
|
|
local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed)
|
|
local ToPatrolRoutePoint=ToTargetCoord:WaypointAir(
|
|
self.PatrolAltType,
|
|
POINT_VEC3.RoutePointType.TurningPoint,
|
|
POINT_VEC3.RoutePointAction.TurningPoint,
|
|
ToTargetSpeed,
|
|
true
|
|
)
|
|
PatrolRoute[#PatrolRoute+1]=ToPatrolRoutePoint
|
|
PatrolRoute[#PatrolRoute+1]=ToPatrolRoutePoint
|
|
local Tasks={}
|
|
Tasks[#Tasks+1]=AIGroup:TaskFunction("AI_A2A_PATROL.PatrolRoute",self)
|
|
PatrolRoute[#PatrolRoute].task=AIGroup:TaskCombo(Tasks)
|
|
AIGroup:OptionROEReturnFire()
|
|
AIGroup:OptionROTPassiveDefense()
|
|
AIGroup:Route(PatrolRoute,0.5)
|
|
end
|
|
end
|
|
function AI_A2A_PATROL.Resume(AIGroup)
|
|
AIGroup:F({"AI_A2A_PATROL.Resume:",AIGroup:GetName()})
|
|
if AIGroup:IsAlive()then
|
|
local _AI_A2A=AIGroup:GetState(AIGroup,"AI_A2A")
|
|
_AI_A2A:__Reset(1)
|
|
_AI_A2A:__Route(5)
|
|
end
|
|
end
|
|
AI_A2A_CAP={
|
|
ClassName="AI_A2A_CAP",
|
|
}
|
|
function AI_A2A_CAP:New(AIGroup,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,PatrolAltType)
|
|
local self=BASE:Inherit(self,AI_A2A_PATROL:New(AIGroup,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType))
|
|
self.Accomplished=false
|
|
self.Engaging=false
|
|
self.EngageMinSpeed=EngageMinSpeed
|
|
self.EngageMaxSpeed=EngageMaxSpeed
|
|
self:AddTransition({"Patrolling","Engaging","Returning","Airborne"},"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_A2A_CAP:onafterStart(AIGroup,From,Event,To)
|
|
AIGroup: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:onafterPatrol(AIGroup,From,Event,To)
|
|
self:GetParent(self).onafterPatrol(self,AIGroup,From,Event,To)
|
|
self:HandleEvent(EVENTS.Dead)
|
|
end
|
|
function AI_A2A_CAP.AttackRoute(AIGroup,Fsm)
|
|
AIGroup:F({"AI_A2A_CAP.AttackRoute:",AIGroup:GetName()})
|
|
if AIGroup:IsAlive()then
|
|
Fsm:__Engage(0.5)
|
|
end
|
|
end
|
|
function AI_A2A_CAP:onbeforeEngage(AIGroup,From,Event,To)
|
|
if self.Accomplished==true then
|
|
return false
|
|
end
|
|
end
|
|
function AI_A2A_CAP:onafterAbort(AIGroup,From,Event,To)
|
|
AIGroup:ClearTasks()
|
|
self:__Route(0.5)
|
|
end
|
|
function AI_A2A_CAP:onafterEngage(AIGroup,From,Event,To,AttackSetUnit)
|
|
self:F({AIGroup,From,Event,To,AttackSetUnit})
|
|
self.AttackSetUnit=AttackSetUnit or self.AttackSetUnit
|
|
local FirstAttackUnit=self.AttackSetUnit:GetFirst()
|
|
if FirstAttackUnit and FirstAttackUnit:IsAlive()then
|
|
if AIGroup:IsAlive()then
|
|
local EngageRoute={}
|
|
local CurrentCoord=AIGroup:GetCoordinate()
|
|
local ToTargetCoord=self.AttackSetUnit:GetFirst():GetCoordinate()
|
|
local ToTargetSpeed=math.random(self.EngageMinSpeed,self.EngageMaxSpeed)
|
|
local ToInterceptAngle=CurrentCoord:GetAngleDegrees(CurrentCoord:GetDirectionVec3(ToTargetCoord))
|
|
local ToPatrolRoutePoint=CurrentCoord:Translate(5000,ToInterceptAngle):WaypointAir(
|
|
self.PatrolAltType,
|
|
POINT_VEC3.RoutePointType.TurningPoint,
|
|
POINT_VEC3.RoutePointAction.TurningPoint,
|
|
ToTargetSpeed,
|
|
true
|
|
)
|
|
self:F({Angle=ToInterceptAngle,ToTargetSpeed=ToTargetSpeed})
|
|
self:T2({self.MinSpeed,self.MaxSpeed,ToTargetSpeed})
|
|
EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint
|
|
EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint
|
|
local AttackTasks={}
|
|
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
|
|
AttackTasks[#AttackTasks+1]=AIGroup:TaskAttackUnit(AttackUnit)
|
|
end
|
|
end
|
|
if#AttackTasks==0 then
|
|
self:E("No targets found -> Going back to Patrolling")
|
|
self:__Abort(0.5)
|
|
else
|
|
AIGroup:OptionROEOpenFire()
|
|
AIGroup:OptionROTPassiveDefense()
|
|
AttackTasks[#AttackTasks+1]=AIGroup:TaskFunction("AI_A2A_CAP.AttackRoute",self)
|
|
EngageRoute[#EngageRoute].task=AIGroup:TaskCombo(AttackTasks)
|
|
end
|
|
AIGroup:Route(EngageRoute,0.5)
|
|
end
|
|
else
|
|
self:E("No targets found -> Going back to Patrolling")
|
|
self:__Abort(0.5)
|
|
end
|
|
end
|
|
function AI_A2A_CAP:onafterAccomplish(Controllable,From,Event,To)
|
|
self.Accomplished=true
|
|
self:SetDetectionOff()
|
|
end
|
|
function AI_A2A_CAP:onafterDestroy(Controllable,From,Event,To,EventData)
|
|
if EventData.IniUnit then
|
|
self.AttackUnits[EventData.IniUnit]=nil
|
|
end
|
|
end
|
|
function AI_A2A_CAP:OnEventDead(EventData)
|
|
self:F({"EventDead",EventData})
|
|
if EventData.IniDCSUnit then
|
|
if self.AttackUnits and self.AttackUnits[EventData.IniUnit]then
|
|
self:__Destroy(1,EventData)
|
|
end
|
|
end
|
|
end
|
|
function AI_A2A_CAP.Resume(AIGroup)
|
|
AIGroup:F({"AI_A2A_CAP.Resume:",AIGroup:GetName()})
|
|
if AIGroup:IsAlive()then
|
|
local _AI_A2A=AIGroup:GetState(AIGroup,"AI_A2A")
|
|
_AI_A2A:__Reset(1)
|
|
_AI_A2A:__Route(5)
|
|
end
|
|
end
|
|
AI_A2A_GCI={
|
|
ClassName="AI_A2A_GCI",
|
|
}
|
|
function AI_A2A_GCI:New(AIGroup,EngageMinSpeed,EngageMaxSpeed)
|
|
local self=BASE:Inherit(self,AI_A2A:New(AIGroup))
|
|
self.Accomplished=false
|
|
self.Engaging=false
|
|
self.EngageMinSpeed=EngageMinSpeed
|
|
self.EngageMaxSpeed=EngageMaxSpeed
|
|
self.PatrolMinSpeed=EngageMinSpeed
|
|
self.PatrolMaxSpeed=EngageMaxSpeed
|
|
self.PatrolAltType="RADIO"
|
|
self:AddTransition({"Started","Engaging","Returning","Airborne"},"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_A2A_GCI:onafterStart(AIGroup,From,Event,To)
|
|
AIGroup:HandleEvent(EVENTS.Takeoff,nil,self)
|
|
end
|
|
function AI_A2A_GCI:onafterEngage(AIGroup,From,Event,To)
|
|
self:HandleEvent(EVENTS.Dead)
|
|
end
|
|
function AI_A2A_GCI.InterceptRoute(AIGroup,Fsm)
|
|
AIGroup:F({"AI_A2A_GCI.InterceptRoute:",AIGroup:GetName()})
|
|
if AIGroup:IsAlive()then
|
|
Fsm:__Engage(0.5)
|
|
end
|
|
end
|
|
function AI_A2A_GCI:onbeforeEngage(AIGroup,From,Event,To)
|
|
if self.Accomplished==true then
|
|
return false
|
|
end
|
|
end
|
|
function AI_A2A_GCI:onafterAbort(AIGroup,From,Event,To)
|
|
AIGroup:ClearTasks()
|
|
self:Return()
|
|
self:__RTB(0.5)
|
|
end
|
|
function AI_A2A_GCI:onafterEngage(AIGroup,From,Event,To,AttackSetUnit)
|
|
self:F({AIGroup,From,Event,To,AttackSetUnit})
|
|
self.AttackSetUnit=AttackSetUnit or self.AttackSetUnit
|
|
local FirstAttackUnit=self.AttackSetUnit:GetFirst()
|
|
if FirstAttackUnit and FirstAttackUnit:IsAlive()then
|
|
if AIGroup:IsAlive()then
|
|
local EngageRoute={}
|
|
local CurrentCoord=AIGroup:GetCoordinate()
|
|
local CurrentCoord=AIGroup:GetCoordinate()
|
|
local ToTargetCoord=self.AttackSetUnit:GetFirst():GetCoordinate()
|
|
self:SetTargetDistance(ToTargetCoord)
|
|
local ToTargetSpeed=math.random(self.EngageMinSpeed,self.EngageMaxSpeed)
|
|
local ToInterceptAngle=CurrentCoord:GetAngleDegrees(CurrentCoord:GetDirectionVec3(ToTargetCoord))
|
|
local ToPatrolRoutePoint=CurrentCoord:Translate(15000,ToInterceptAngle):WaypointAir(
|
|
self.PatrolAltType,
|
|
POINT_VEC3.RoutePointType.TurningPoint,
|
|
POINT_VEC3.RoutePointAction.TurningPoint,
|
|
ToTargetSpeed,
|
|
true
|
|
)
|
|
self:F({Angle=ToInterceptAngle,ToTargetSpeed=ToTargetSpeed})
|
|
self:F({self.EngageMinSpeed,self.EngageMaxSpeed,ToTargetSpeed})
|
|
EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint
|
|
EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint
|
|
local AttackTasks={}
|
|
for AttackUnitID,AttackUnit in pairs(self.AttackSetUnit:GetSet())do
|
|
local AttackUnit=AttackUnit
|
|
if AttackUnit:IsAlive()and AttackUnit:IsAir()then
|
|
self:T({"Intercepting Unit:",AttackUnit:GetName(),AttackUnit:IsAlive(),AttackUnit:IsAir()})
|
|
AttackTasks[#AttackTasks+1]=AIGroup:TaskAttackUnit(AttackUnit)
|
|
end
|
|
end
|
|
if#AttackTasks==0 then
|
|
self:E("No targets found -> Going RTB")
|
|
self:Return()
|
|
self:__RTB(0.5)
|
|
else
|
|
AIGroup:OptionROEOpenFire()
|
|
AIGroup:OptionROTPassiveDefense()
|
|
AttackTasks[#AttackTasks+1]=AIGroup:TaskFunction("AI_A2A_GCI.InterceptRoute",self)
|
|
EngageRoute[#EngageRoute].task=AIGroup:TaskCombo(AttackTasks)
|
|
end
|
|
AIGroup:Route(EngageRoute,0.5)
|
|
end
|
|
else
|
|
self:E("No targets found -> Going RTB")
|
|
self:Return()
|
|
self:__RTB(0.5)
|
|
end
|
|
end
|
|
function AI_A2A_GCI:onafterAccomplish(AIGroup,From,Event,To)
|
|
self.Accomplished=true
|
|
self:SetDetectionOff()
|
|
end
|
|
function AI_A2A_GCI:onafterDestroy(AIGroup,From,Event,To,EventData)
|
|
if EventData.IniUnit then
|
|
self.AttackUnits[EventData.IniUnit]=nil
|
|
end
|
|
end
|
|
function AI_A2A_GCI:OnEventDead(EventData)
|
|
self:F({"EventDead",EventData})
|
|
if EventData.IniDCSUnit then
|
|
if self.AttackUnits and self.AttackUnits[EventData.IniUnit]then
|
|
self:__Destroy(1,EventData)
|
|
end
|
|
end
|
|
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.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:SetTacticalDisplay(false)
|
|
self:__Start(5)
|
|
return self
|
|
end
|
|
function AI_A2A_DISPATCHER:OnEventCrashOrDead(EventData)
|
|
self.Detection:ForgetDetectedUnit(EventData.IniUnitName)
|
|
end
|
|
function AI_A2A_DISPATCHER:OnEventLand(EventData)
|
|
self:E("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()
|
|
return
|
|
end
|
|
if DefenderUnit:GetLife()~=DefenderUnit:GetLife0()then
|
|
DefenderUnit:Destroy()
|
|
return
|
|
end
|
|
if DefenderUnit:GetFuel()<=self.DefenderDefault.FuelThreshold 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 then
|
|
local DefenderSize=Defender:GetSize()
|
|
if DefenderSize==1 then
|
|
self:RemoveDefenderFromSquadron(Squadron,Defender)
|
|
end
|
|
DefenderUnit:Destroy()
|
|
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: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: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()})
|
|
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,Resources)
|
|
self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{}
|
|
local DefenderSquadron=self.DefenderSquadrons[SquadronName]
|
|
DefenderSquadron.Name=SquadronName
|
|
DefenderSquadron.Airbase=AIRBASE:FindByName(AirbaseName)
|
|
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.Resources=Resources
|
|
DefenderSquadron.TemplatePrefixes=TemplatePrefixes
|
|
self:E({Squadron={SquadronName,AirbaseName,TemplatePrefixes,Resources}})
|
|
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:SetSquadronCap(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType)
|
|
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.Zone=Zone
|
|
Cap.FloorAltitude=FloorAltitude
|
|
Cap.CeilingAltitude=CeilingAltitude
|
|
Cap.PatrolMinSpeed=PatrolMinSpeed
|
|
Cap.PatrolMaxSpeed=PatrolMaxSpeed
|
|
Cap.EngageMinSpeed=EngageMinSpeed
|
|
Cap.EngageMaxSpeed=EngageMaxSpeed
|
|
Cap.AltType=AltType
|
|
self:SetSquadronCapInterval(SquadronName,self.DefenderDefault.CapLimit,self.DefenderDefault.CapMinSeconds,self.DefenderDefault.CapMaxSeconds,1)
|
|
self:E({CAP={SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType}})
|
|
local RecceSet=self.Detection:GetDetectionSetGroup()
|
|
RecceSet:FilterPrefixes(DefenderSquadron.TemplatePrefixes)
|
|
RecceSet:FilterStart()
|
|
self.Detection:SetFriendlyPrefixes(DefenderSquadron.TemplatePrefixes)
|
|
return self
|
|
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(not DefenderSquadron.Resources)or(DefenderSquadron.Resources and DefenderSquadron.Resources>0)then
|
|
local Cap=DefenderSquadron.Cap
|
|
if Cap then
|
|
local CapCount=self:CountCapAirborne(SquadronName)
|
|
self:E({CapCount=CapCount})
|
|
if CapCount<Cap.CapLimit then
|
|
local Probability=math.random()
|
|
if Probability<=Cap.Probability then
|
|
return DefenderSquadron
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
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(not DefenderSquadron.Resources)or(DefenderSquadron.Resources and DefenderSquadron.Resources>0)then
|
|
local Gci=DefenderSquadron.Gci
|
|
if Gci then
|
|
return DefenderSquadron
|
|
end
|
|
end
|
|
return nil
|
|
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:E({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: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:AddDefenderToSquadron(Squadron,Defender,Size)
|
|
self.Defenders=self.Defenders or{}
|
|
local DefenderName=Defender:GetName()
|
|
self.Defenders[DefenderName]=Squadron
|
|
if Squadron.Resources then
|
|
Squadron.Resources=Squadron.Resources-Size
|
|
end
|
|
self:E({DefenderName=DefenderName,SquadronResources=Squadron.Resources})
|
|
end
|
|
function AI_A2A_DISPATCHER:RemoveDefenderFromSquadron(Squadron,Defender)
|
|
self.Defenders=self.Defenders or{}
|
|
local DefenderName=Defender:GetName()
|
|
if Squadron.Resources then
|
|
Squadron.Resources=Squadron.Resources+Defender:GetSize()
|
|
end
|
|
self.Defenders[DefenderName]=nil
|
|
self:F({DefenderName=DefenderName,SquadronResources=Squadron.Resources})
|
|
end
|
|
function AI_A2A_DISPATCHER:GetSquadronFromDefender(Defender)
|
|
self.Defenders=self.Defenders or{}
|
|
local DefenderName=Defender:GetName()
|
|
self:F({DefenderName=DefenderName})
|
|
return self.Defenders[DefenderName]
|
|
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:IsAlive()then
|
|
if DefenderTask.Fsm:Is("Patrolling")or DefenderTask.Fsm:Is("Engaging")or DefenderTask.Fsm:Is("Refuelling")then
|
|
CapCount=CapCount+1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return CapCount
|
|
end
|
|
function AI_A2A_DISPATCHER:CountDefendersEngaged(AttackerDetection)
|
|
local DefenderCount=0
|
|
self:E("Counting Defenders Engaged for Attacker:")
|
|
local DetectedSet=AttackerDetection.Set
|
|
DetectedSet:Flush()
|
|
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()
|
|
DefenderCount=DefenderCount+DefenderSize/SquadronOverhead
|
|
self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize)
|
|
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
|
|
local Friendly=AIFriendly:GetGroup()
|
|
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: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 Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)]
|
|
local DefenderGrouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping
|
|
Spawn:InitGrouping(DefenderGrouping)
|
|
local TakeoffMethod=self:GetSquadronTakeoff(SquadronName)
|
|
local DefenderCAP=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,TakeoffMethod,DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude)
|
|
self:AddDefenderToSquadron(DefenderSquadron,DefenderCAP,DefenderGrouping)
|
|
if DefenderCAP then
|
|
local Fsm=AI_A2A_CAP:New(DefenderCAP,Cap.Zone,Cap.FloorAltitude,Cap.CeilingAltitude,Cap.PatrolMinSpeed,Cap.PatrolMaxSpeed,Cap.EngageMinSpeed,Cap.EngageMaxSpeed,Cap.AltType)
|
|
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:SetTanker(DefenderSquadron.TankerName or self.DefenderDefault.TankerName)
|
|
Fsm:Start()
|
|
self:SetDefenderTask(SquadronName,DefenderCAP,"CAP",Fsm)
|
|
function Fsm:onafterTakeoff(Defender,From,Event,To)
|
|
self:F({"GCI Birth",Defender:GetName()})
|
|
local Dispatcher=Fsm:GetDispatcher()
|
|
local Squadron=Dispatcher:GetSquadronFromDefender(Defender)
|
|
if Squadron then
|
|
Fsm:__Patrol(2)
|
|
end
|
|
end
|
|
function Fsm:onafterRTB(Defender,From,Event,To)
|
|
self:F({"CAP RTB",Defender:GetName()})
|
|
self:GetParent(self).onafterRTB(self,Defender,From,Event,To)
|
|
local Dispatcher=self:GetDispatcher()
|
|
Dispatcher:ClearDefenderTaskTarget(Defender)
|
|
end
|
|
function Fsm:onafterHome(Defender,From,Event,To,Action)
|
|
self:E({"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()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function AI_A2A_DISPATCHER:onafterENGAGE(From,Event,To,AttackerDetection,Defenders)
|
|
if Defenders then
|
|
for DefenderID,Defender in pairs(Defenders)do
|
|
local Fsm=self:GetDefenderTaskFsm(Defender)
|
|
Fsm:__Engage(1,AttackerDetection.Set)
|
|
self:SetDefenderTaskTarget(Defender,AttackerDetection)
|
|
end
|
|
end
|
|
end
|
|
function AI_A2A_DISPATCHER:onafterGCI(From,Event,To,AttackerDetection,DefendersMissing,DefenderFriendlies)
|
|
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:__Engage(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.Resources and DefendersNeeded>DefenderSquadron.Resources then
|
|
DefendersNeeded=DefenderSquadron.Resources
|
|
BreakLoop=true
|
|
end
|
|
while(DefendersNeeded>0)do
|
|
local Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)]
|
|
local DefenderGrouping=(DefenderGrouping<DefendersNeeded)and DefenderGrouping or DefendersNeeded
|
|
if DefenderGrouping then
|
|
Spawn:InitGrouping(DefenderGrouping)
|
|
else
|
|
Spawn:InitGrouping()
|
|
end
|
|
local TakeoffMethod=self:GetSquadronTakeoff(ClosestDefenderSquadronName)
|
|
local DefenderGCI=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,TakeoffMethod,DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude)
|
|
self:E({GCIDefender=DefenderGCI:GetName()})
|
|
DefendersNeeded=DefendersNeeded-DefenderGrouping
|
|
self:AddDefenderToSquadron(DefenderSquadron,DefenderGCI,DefenderGrouping)
|
|
if DefenderGCI then
|
|
DefenderCount=DefenderCount-DefenderGrouping/DefenderOverhead
|
|
local Fsm=AI_A2A_GCI:New(DefenderGCI,Gci.EngageMinSpeed,Gci.EngageMaxSpeed)
|
|
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(Defender,From,Event,To)
|
|
self:F({"GCI Birth",Defender:GetName()})
|
|
local Dispatcher=Fsm:GetDispatcher()
|
|
local Squadron=Dispatcher:GetSquadronFromDefender(Defender)
|
|
local DefenderTarget=Dispatcher:GetDefenderTaskTarget(Defender)
|
|
if DefenderTarget then
|
|
Fsm:__Engage(2,DefenderTarget.Set)
|
|
end
|
|
end
|
|
function Fsm:onafterRTB(Defender,From,Event,To)
|
|
self:F({"GCI RTB",Defender:GetName()})
|
|
self:GetParent(self).onafterRTB(self,Defender,From,Event,To)
|
|
local Dispatcher=self:GetDispatcher()
|
|
Dispatcher:ClearDefenderTaskTarget(Defender)
|
|
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(Defender,From,Event,To,Action)
|
|
self:F({"GCI 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()
|
|
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,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: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:E({Defender=AIGroup:GetName(),DefenderState=DefenderTaskFsm:GetState()})
|
|
if not DefenderTaskFsm:Is("Started")then
|
|
self:ClearDefenderTask(AIGroup)
|
|
end
|
|
else
|
|
if DefenderTask.Target then
|
|
local AttackerItem=Detection:GetDetectedItem(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("\nTactical Overview")
|
|
for DetectedItemID,DetectedItem in pairs(Detection:GetDetectedItems())do
|
|
local DetectedItem=DetectedItem
|
|
local DetectedSet=DetectedItem.Set
|
|
local DetectedCount=DetectedSet:Count()
|
|
local DetectedZone=DetectedItem.Zone
|
|
self:F({"Target ID",DetectedItem.ItemID})
|
|
DetectedSet:Flush()
|
|
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 then
|
|
self:F({DefendersMissing=DefendersMissing})
|
|
self:GCI(DetectedItem,DefendersMissing,Friendlies)
|
|
end
|
|
end
|
|
if self.TacticalDisplay then
|
|
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
|
|
local Fuel=Defender:GetFuel()*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
|
|
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
|
|
local DefenderHasTask=Defender:HasTask()
|
|
local Fuel=Defender:GetFuel()*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
|
|
Report:Add(string.format("\n - %d Tasks",TaskCount))
|
|
self:E(Report:Text("\n"))
|
|
trigger.action.outText(Report:Text("\n"),25)
|
|
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(Target)
|
|
local DetectedSet=Target.Set
|
|
local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(Target)
|
|
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
|
|
end
|
|
do
|
|
AI_A2A_GCICAP={
|
|
ClassName="AI_A2A_GCICAP",
|
|
Detection=nil,
|
|
}
|
|
function AI_A2A_GCICAP:New(EWRPrefixes,TemplatePrefixes,CapPrefixes,CapLimit,GroupingRadius,EngageRadius,GciRadius,Resources)
|
|
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:F({Airbases=AirbaseNames})
|
|
self.Templates:Flush()
|
|
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
|
|
for TemplateID,Template in pairs(self.Templates:GetSet())do
|
|
local Template=Template
|
|
self:F({Template=Template:GetName()})
|
|
local TemplateCoord=Template:GetCoordinate()
|
|
if AirbaseZone:IsVec2InZone(TemplateCoord:GetVec2())then
|
|
Templates=Templates or{}
|
|
table.insert(Templates,Template:GetName())
|
|
end
|
|
end
|
|
if Templates then
|
|
self:SetSquadron(AirbaseName,AirbaseName,Templates,Resources)
|
|
end
|
|
end
|
|
self.CAPTemplates=SET_GROUP:New()
|
|
self.CAPTemplates:FilterPrefixes(CapPrefixes)
|
|
self.CAPTemplates:FilterOnce()
|
|
for CAPID,CAPTemplate in pairs(self.CAPTemplates:GetSet())do
|
|
local CAPZone=ZONE_POLYGON:New(CAPTemplate:GetName(),CAPTemplate)
|
|
local AirbaseDistance=99999999
|
|
local AirbaseClosest=nil
|
|
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())
|
|
if Distance<AirbaseDistance then
|
|
AirbaseDistance=Distance
|
|
AirbaseClosest=Airbase
|
|
end
|
|
end
|
|
end
|
|
if AirbaseClosest then
|
|
self:SetSquadronCap(AirbaseClosest:GetName(),CAPZone,6000,10000,500,800,800,1200,"RADIO")
|
|
self:SetSquadronCapInterval(AirbaseClosest:GetName(),CapLimit,300,600,1)
|
|
end
|
|
end
|
|
for AirbaseID,AirbaseName in pairs(AirbaseNames)do
|
|
local Airbase=_DATABASE:FindAirbase(AirbaseName)
|
|
local AirbaseName=Airbase:GetName()
|
|
local Squadron=self.DefenderSquadrons[AirbaseName]
|
|
if Squadron then
|
|
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,Resources)
|
|
local self=AI_A2A_GCICAP:New(EWRPrefixes,TemplatePrefixes,CapPrefixes,CapLimit,GroupingRadius,EngageRadius,GciRadius,Resources)
|
|
if BorderPrefix then
|
|
self:SetBorderZone(ZONE_POLYGON:New(BorderPrefix,GROUP:FindByName(BorderPrefix)))
|
|
end
|
|
return self
|
|
end
|
|
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"RADIO"
|
|
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.PatrolManageFuel=true
|
|
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:E("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)
|
|
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
|
|
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
|
|
if self.Controllable:IsAlive()then
|
|
local PatrolRoute={}
|
|
if self.Controllable:InAir()==false then
|
|
self:E("Not in the air, finding route path within PatrolZone")
|
|
local CurrentVec2=self.Controllable:GetVec2()
|
|
local CurrentAltitude=self.Controllable:GetUnit(1):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:E("In the air, finding route path within PatrolZone")
|
|
local CurrentVec2=self.Controllable:GetVec2()
|
|
local CurrentAltitude=self.Controllable:GetUnit(1):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:GetUnit(1):GetFuel()
|
|
if Fuel<self.PatrolFuelThresholdPercentage then
|
|
self:E(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:E(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()
|
|
local CurrentAltitude=self.Controllable:GetUnit(1):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
|
|
self:E(self.Controllable:GetUnits())
|
|
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:IsAlive()then
|
|
local EngageRoute={}
|
|
local CurrentVec2=self.Controllable:GetVec2()
|
|
local CurrentAltitude=self.Controllable:GetUnit(1):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:OptionROTPassiveDefense()
|
|
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)
|
|
self:E("onafterTarget")
|
|
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:E({"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:GetUnit(1):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:E({"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:GetUnit(1):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,
|
|
}
|
|
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:SetFlightRandomization(2)
|
|
self:SetStartState("None")
|
|
self:AddTransition("*","Stop","Stopped")
|
|
self:AddTransition("None","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: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)
|
|
self:F({FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace})
|
|
FollowGroupSet:Flush()
|
|
local FollowSet=FollowGroupSet:GetSet()
|
|
local i=0
|
|
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
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
|
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
|
|
end
|
|
return self
|
|
end
|
|
function AI_FORMATION:SetFlightRandomization(FlightRandomization)
|
|
self.FlightRandomization=FlightRandomization
|
|
return self
|
|
end
|
|
function AI_FORMATION:onenterFollowing(FollowGroupSet)
|
|
self:F()
|
|
self:T({self.FollowUnit.UnitName,self.FollowUnit:IsAlive()})
|
|
if self.FollowUnit:IsAlive()then
|
|
local ClientUnit=self.FollowUnit
|
|
self:T({ClientUnit.UnitName})
|
|
local CT1,CT2,CV1,CV2
|
|
CT1=ClientUnit:GetState(self,"CT1")
|
|
if CT1==nil or CT1==0 then
|
|
ClientUnit:SetState(self,"CV1",ClientUnit:GetPointVec3())
|
|
ClientUnit:SetState(self,"CT1",timer.getTime())
|
|
else
|
|
CT1=ClientUnit:GetState(self,"CT1")
|
|
CT2=timer.getTime()
|
|
CV1=ClientUnit:GetState(self,"CV1")
|
|
CV2=ClientUnit:GetPointVec3()
|
|
ClientUnit:SetState(self,"CT1",CT2)
|
|
ClientUnit:SetState(self,"CV1",CV2)
|
|
end
|
|
FollowGroupSet:ForEachGroup(
|
|
function(FollowGroup,Formation,ClientUnit,CT1,CV1,CT2,CV2)
|
|
FollowGroup:OptionROTPassiveDefense()
|
|
FollowGroup:OptionROEReturnFire()
|
|
local GroupUnit=FollowGroup:GetUnit(1)
|
|
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",GroupUnit:GetPointVec3())
|
|
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=GroupUnit:GetPointVec3()
|
|
GV2:AddX(math.random(-Formation.FlightRandomization/2,Formation.FlightRandomization/2))
|
|
GV2:AddY(math.random(-Formation.FlightRandomization/2,Formation.FlightRandomization/2))
|
|
GV2:AddZ(math.random(-Formation.FlightRandomization/2,Formation.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 CVI={x=CV2.x+CS*10*math.sin(Ca),
|
|
y=GH2.y-(Distance+FollowFormation.x)/5,
|
|
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=60
|
|
local Speed=-(Distance+FollowFormation.x)/Time
|
|
local GS=Speed+CS
|
|
if Speed<0 then
|
|
Speed=0
|
|
end
|
|
FollowGroup:RouteToVec3(GDV_Formation,GS)
|
|
end
|
|
end
|
|
end,
|
|
self,ClientUnit,CT1,CV1,CT2,CV2
|
|
)
|
|
self:__Follow(-0.5)
|
|
end
|
|
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,From,Event,To)
|
|
self:E({ProcessUnit,From,Event,To})
|
|
self:__Assign(1)
|
|
end
|
|
function ACT_ASSIGN_ACCEPT:onenterAssigned(ProcessUnit,From,Event,To)
|
|
env.info("in here")
|
|
self:E({ProcessUnit,From,Event,To})
|
|
local ProcessGroup=ProcessUnit:GetGroup()
|
|
self.Task:Assign(ProcessUnit,ProcessUnit:GetPlayerName())
|
|
end
|
|
end
|
|
do
|
|
ACT_ASSIGN_MENU_ACCEPT={
|
|
ClassName="ACT_ASSIGN_MENU_ACCEPT",
|
|
}
|
|
function ACT_ASSIGN_MENU_ACCEPT:New(TaskName,TaskBriefing)
|
|
local self=BASE:Inherit(self,ACT_ASSIGN:New())
|
|
self.TaskName=TaskName
|
|
self.TaskBriefing=TaskBriefing
|
|
return self
|
|
end
|
|
function ACT_ASSIGN_MENU_ACCEPT:Init(FsmAssign)
|
|
self.TaskName=FsmAssign.TaskName
|
|
self.TaskBriefing=FsmAssign.TaskBriefing
|
|
end
|
|
function ACT_ASSIGN_MENU_ACCEPT:Init(TaskName,TaskBriefing)
|
|
self.TaskBriefing=TaskBriefing
|
|
self.TaskName=TaskName
|
|
return self
|
|
end
|
|
function ACT_ASSIGN_MENU_ACCEPT:onafterStart(ProcessUnit,From,Event,To)
|
|
self:E({ProcessUnit,From,Event,To})
|
|
self:GetCommandCenter():MessageTypeToGroup("Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.",ProcessUnit:GetGroup(),MESSAGE.Type.Information)
|
|
local ProcessGroup=ProcessUnit:GetGroup()
|
|
self.Menu=MENU_GROUP:New(ProcessGroup,"Task "..self.TaskName.." acceptance")
|
|
self.MenuAcceptTask=MENU_GROUP_COMMAND:New(ProcessGroup,"Accept task "..self.TaskName,self.Menu,self.MenuAssign,self)
|
|
self.MenuRejectTask=MENU_GROUP_COMMAND:New(ProcessGroup,"Reject task "..self.TaskName,self.Menu,self.MenuReject,self)
|
|
end
|
|
function ACT_ASSIGN_MENU_ACCEPT:MenuAssign()
|
|
self:E()
|
|
self:__Assign(1)
|
|
end
|
|
function ACT_ASSIGN_MENU_ACCEPT:MenuReject()
|
|
self:E()
|
|
self:__Reject(1)
|
|
end
|
|
function ACT_ASSIGN_MENU_ACCEPT:onafterAssign(ProcessUnit,From,Event,To)
|
|
self:E({ProcessUnit.UnitNameFrom,Event,To})
|
|
self.Menu:Remove()
|
|
end
|
|
function ACT_ASSIGN_MENU_ACCEPT:onafterReject(ProcessUnit,From,Event,To)
|
|
self:E({ProcessUnit.UnitName,From,Event,To})
|
|
self.Menu:Remove()
|
|
ProcessUnit:Destroy()
|
|
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)
|
|
MENU_GROUP_COMMAND:New(
|
|
MenuGroup,
|
|
MenuText,
|
|
ParentMenu,
|
|
self.MenuCancel,
|
|
self
|
|
):SetTime(MenuTime)
|
|
return self
|
|
end
|
|
function ACT_ROUTE:SetRouteMode(RouteMode)
|
|
self.RouteMode=RouteMode
|
|
return self
|
|
end
|
|
function ACT_ROUTE:GetRouteText(Controllable)
|
|
self:E()
|
|
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:E({CC.ReferencePoints})
|
|
for ZoneName,Zone in pairs(CC.ReferencePoints)do
|
|
self:E({ZoneName=ZoneName})
|
|
local Zone=Zone
|
|
local ZoneCoord=Zone:GetCoordinate()
|
|
local ZoneDistance=ZoneCoord:Get2DDistance(self.Coordinate)
|
|
self:E({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:Cancel()
|
|
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)
|
|
self:F({"BeforeRoute 1",self.DisplayCount,self.DisplayInterval})
|
|
if ProcessUnit:IsAlive()then
|
|
self:F("BeforeRoute 2")
|
|
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
|
|
self:T({DisplayCount=self.DisplayCount})
|
|
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({self.Range})
|
|
self.Range=Range or 10000
|
|
end
|
|
function ACT_ROUTE_POINT:GetRange()
|
|
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="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=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="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:E({ProcessUnit=ProcessUnit})
|
|
local RouteText=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)
|
|
self:E({ProcessUnit,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)
|
|
self:E(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
|
|
COMMANDCENTER={
|
|
ClassName="COMMANDCENTER",
|
|
CommandCenterName="",
|
|
CommandCenterCoalition=nil,
|
|
CommandCenterPositionable=nil,
|
|
Name="",
|
|
ReferencePoints={},
|
|
ReferenceNames={},
|
|
CommunicationMode="80",
|
|
}
|
|
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:HandleEvent(EVENTS.Birth,
|
|
function(self,EventData)
|
|
if EventData.IniObjectCategory==1 then
|
|
local EventGroup=GROUP:Find(EventData.IniDCSGroup)
|
|
if EventGroup and self:HasGroup(EventGroup)then
|
|
local MenuReporting=MENU_GROUP:New(EventGroup,"Missions Reports",self.CommandCenterMenu)
|
|
local MenuMissionsSummary=MENU_GROUP_COMMAND:New(EventGroup,"Missions Status Report",MenuReporting,self.ReportMissionsStatus,self,EventGroup)
|
|
local MenuMissionsDetails=MENU_GROUP_COMMAND:New(EventGroup,"Missions Players Report",MenuReporting,self.ReportMissionsPlayers,self,EventGroup)
|
|
self:ReportSummary(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()
|
|
_DATABASE:PlayerSettingsMenu(PlayerUnit)
|
|
end
|
|
end
|
|
end
|
|
)
|
|
self:HandleEvent(EVENTS.PlayerEnterUnit,
|
|
function(self,EventData)
|
|
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
|
|
)
|
|
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)
|
|
return self
|
|
end
|
|
function COMMANDCENTER:GetName()
|
|
return self.CommandCenterName
|
|
end
|
|
function COMMANDCENTER:GetPositionable()
|
|
return self.CommandCenterPositionable
|
|
end
|
|
function COMMANDCENTER:GetMissions()
|
|
return self.Missions
|
|
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:F()
|
|
self.CommandCenterMenu=self.CommandCenterMenu or MENU_COALITION:New(self.CommandCenterCoalition,"Command Center ("..self:GetName()..")")
|
|
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
|
|
local Mission=Mission
|
|
Mission:RemoveMenu(MenuTime)
|
|
end
|
|
end
|
|
function COMMANDCENTER:GetMenu()
|
|
return self.CommandCenterMenu
|
|
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,20,self:GetName())
|
|
end
|
|
function COMMANDCENTER:MessageToGroup(Message,TaskGroup)
|
|
self:GetPositionable():MessageToGroup(Message,15,TaskGroup,self:GetName())
|
|
end
|
|
function COMMANDCENTER:MessageTypeToGroup(Message,TaskGroup,MessageType)
|
|
self:GetPositionable():MessageTypeToGroup(Message,MessageType,TaskGroup,self:GetName())
|
|
end
|
|
function COMMANDCENTER:MessageToCoalition(Message)
|
|
local CCCoalition=self:GetPositionable():GetCoalition()
|
|
self:GetPositionable():MessageToCoalition(Message,15,CCCoalition)
|
|
end
|
|
function COMMANDCENTER:MessageTypeToCoalition(Message,MessageType)
|
|
local CCCoalition=self:GetPositionable():GetCoalition()
|
|
self:GetPositionable():MessageTypeToCoalition(Message,MessageType,CCCoalition)
|
|
end
|
|
function COMMANDCENTER:ReportMissionsStatus(ReportGroup)
|
|
self:E(ReportGroup)
|
|
local Report=REPORT:New()
|
|
Report:Add("Status report of all missions.")
|
|
for MissionID,Mission in pairs(self.Missions)do
|
|
local Mission=Mission
|
|
Report:Add(" - "..Mission:ReportStatus())
|
|
end
|
|
self:MessageToGroup(Report:Text(),ReportGroup)
|
|
end
|
|
function COMMANDCENTER:ReportMissionsPlayers(ReportGroup)
|
|
self:E(ReportGroup)
|
|
local Report=REPORT:New()
|
|
Report:Add("Players active in all missions.")
|
|
for MissionID,Mission in pairs(self.Missions)do
|
|
local Mission=Mission
|
|
Report:Add(" - "..Mission:ReportPlayers())
|
|
end
|
|
self:MessageToGroup(Report:Text(),ReportGroup)
|
|
end
|
|
function COMMANDCENTER:ReportDetails(ReportGroup,Task)
|
|
self:E(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
|
|
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.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:GetName().." has been completed! Good job guys!",MESSAGE.Type.Information)
|
|
end
|
|
function MISSION:GetName()
|
|
return string.format('Mission "%s (%s)"',self.Name,self.MissionPriority)
|
|
end
|
|
function MISSION:JoinUnit(PlayerUnit,PlayerGroup)
|
|
self:F({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()
|
|
local SetGroup=SET_GROUP:New()
|
|
for TaskID,Task in pairs(self:GetTasks())do
|
|
local Task=Task
|
|
local GroupSet=Task:GetGroups()
|
|
GroupSet:ForEachGroup(
|
|
function(TaskGroup)
|
|
SetGroup:Add(TaskGroup,TaskGroup)
|
|
end
|
|
)
|
|
end
|
|
return SetGroup
|
|
end
|
|
function MISSION:SetMenu(MenuTime)
|
|
self:F({self:GetName(),MenuTime})
|
|
for _,TaskData in pairs(self:GetTasks())do
|
|
local Task=TaskData
|
|
Task:SetMenu(MenuTime)
|
|
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:T({"Mission is assigned to:",MissionGroup:GetName()})
|
|
return true
|
|
end
|
|
self:T({"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:E(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()
|
|
local MissionName=self:GetName()
|
|
self.MissionMenu=self.MissionMenu or MENU_COALITION:New(self.MissionCoalition,self:GetName(),CommandCenterMenu)
|
|
return self.MissionMenu
|
|
end
|
|
function MISSION:GetMenu(TaskGroup)
|
|
local CommandCenter=self:GetCommandCenter()
|
|
local CommandCenterMenu=CommandCenter:GetMenu()
|
|
local MissionName=self:GetName()
|
|
self.MissionGroupMenu=self.MissionGroupMenu or{}
|
|
self.MissionGroupMenu[TaskGroup]=self.MissionGroupMenu[TaskGroup]or{}
|
|
local GroupMenu=self.MissionGroupMenu[TaskGroup]
|
|
self.MissionMenu=self.MissionMenu or MENU_COALITION:New(self.MissionCoalition,self:GetName(),CommandCenterMenu)
|
|
GroupMenu.BriefingMenu=GroupMenu.BriefingMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Mission Briefing",self.MissionMenu,self.MenuReportBriefing,self,TaskGroup)
|
|
GroupMenu.TaskReportsMenu=GroupMenu.TaskReportsMenu or MENU_GROUP:New(TaskGroup,"Task Reports",self.MissionMenu)
|
|
GroupMenu.ReportTasksMenu=GroupMenu.ReportTasksMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksSummary,self,TaskGroup)
|
|
GroupMenu.ReportPlannedTasksMenu=GroupMenu.ReportPlannedTasksMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Planned Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Planned")
|
|
GroupMenu.ReportAssignedTasksMenu=GroupMenu.ReportAssignedTasksMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Assigned Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Assigned")
|
|
GroupMenu.ReportSuccessTasksMenu=GroupMenu.ReportSuccessTasksMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Successful Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Success")
|
|
GroupMenu.ReportFailedTasksMenu=GroupMenu.ReportFailedTasksMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Failed Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Failed")
|
|
GroupMenu.ReportHeldTasksMenu=GroupMenu.ReportHeldTasksMenu or MENU_GROUP_COMMAND:New(TaskGroup,"Report Held Tasks",GroupMenu.TaskReportsMenu,self.MenuReportTasksPerStatus,self,TaskGroup,"Hold")
|
|
GroupMenu.PlayerReportsMenu=GroupMenu.PlayerReportsMenu or MENU_GROUP:New(TaskGroup,"Statistics Reports",self.MissionMenu)
|
|
GroupMenu.ReportMissionHistory=GroupMenu.ReportPlayersHistory or MENU_GROUP_COMMAND:New(TaskGroup,"Report Mission Progress",GroupMenu.PlayerReportsMenu,self.MenuReportPlayersProgress,self,TaskGroup)
|
|
GroupMenu.ReportPlayersPerTaskMenu=GroupMenu.ReportPlayersPerTaskMenu or 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:AddTask(Task)
|
|
local TaskName=Task:GetTaskName()
|
|
self:F(TaskName)
|
|
self.Tasks[TaskName]=self.Tasks[TaskName]or{n=0}
|
|
self.Tasks[TaskName]=Task
|
|
self:GetCommandCenter():SetMenu()
|
|
return Task
|
|
end
|
|
function MISSION:RemoveTask(Task)
|
|
local TaskName=Task:GetTaskName()
|
|
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:GetNextTaskID(Task)
|
|
local TaskName=Task:GetTaskName()
|
|
self:F(TaskName)
|
|
self.Tasks[TaskName]=self.Tasks[TaskName]or{n=0}
|
|
self.Tasks[TaskName].n=self.Tasks[TaskName].n+1
|
|
return self.Tasks[TaskName].n
|
|
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:GetName()
|
|
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:ReportStatus()
|
|
local Report=REPORT:New()
|
|
local Name=self:GetName()
|
|
local Status="<"..self:GetState()..">"
|
|
Report:Add(string.format('%s - Status "%s"',Name,Status))
|
|
local TaskTypes=self:GetTaskTypes()
|
|
Report:Add(string.format(" - Task Types: %s",table.concat(TaskTypes,", ")))
|
|
local TaskStatusList={"Planned","Assigned","Success","Hold","Cancelled","Aborted","Failed"}
|
|
for TaskStatusID,TaskStatus in pairs(TaskStatusList)do
|
|
local TaskCount=0
|
|
local TaskPlayerCount=0
|
|
for TaskID,Task in pairs(self:GetTasks())do
|
|
local Task=Task
|
|
if Task:Is(TaskStatus)then
|
|
TaskCount=TaskCount+1
|
|
TaskPlayerCount=TaskPlayerCount+Task:GetPlayerCount()
|
|
end
|
|
end
|
|
if TaskCount>0 then
|
|
Report:Add(string.format(" - %02d %s Tasks (%dp)",TaskCount,TaskStatus,TaskPlayerCount))
|
|
end
|
|
end
|
|
return Report:Text()
|
|
end
|
|
function MISSION:ReportPlayersPerTask(ReportGroup)
|
|
local Report=REPORT:New()
|
|
local Name=self:GetName()
|
|
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:GetName()
|
|
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 TaskGoalTotal=Task:GetGoalTotal()or 0
|
|
local TaskName=Task:GetName()
|
|
PlayerList[TaskName]=PlayerList[TaskName]or{}
|
|
if TaskGoalTotal~=0 then
|
|
local PlayerNames=self:GetPlayerNames()
|
|
for PlayerName,PlayerData in pairs(PlayerNames)do
|
|
PlayerList[TaskName][PlayerName]=string.format('Player (%s): Task "%s": %d%%',PlayerName,TaskName,Task:GetPlayerProgress(PlayerName)*100/TaskGoalTotal)
|
|
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:ReportSummary(ReportGroup)
|
|
local Report=REPORT:New()
|
|
local Name=self:GetName()
|
|
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:GetName()
|
|
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:GetName()
|
|
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(Task:ReportDetails(ReportGroup))
|
|
end
|
|
return Report:Text()
|
|
end
|
|
function MISSION:GetTasks()
|
|
return self.Tasks
|
|
end
|
|
function MISSION:MenuReportBriefing(ReportGroup)
|
|
local Report=self:ReportBriefing()
|
|
self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Briefing)
|
|
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())
|
|
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("Assigned","Abort","Aborted")
|
|
self:AddTransition("Assigned","Cancel","Cancelled")
|
|
self:AddTransition("Assigned","Goal","*")
|
|
self:AddTransition("*","PlayerCrashed","*")
|
|
self:AddTransition("*","PlayerAborted","*")
|
|
self:AddTransition("*","PlayerDead","*")
|
|
self:AddTransition({"Failed","Aborted","Cancelled"},"Replan","Planned")
|
|
self:AddTransition("*","TimeOut","Cancelled")
|
|
self:E("New TASK "..TaskName)
|
|
self.Processes={}
|
|
self.Fsm={}
|
|
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.FsmTemplate=self.FsmTemplate or FSM_PROCESS:New()
|
|
self.TaskInfo={}
|
|
self.TaskProgress={}
|
|
return self
|
|
end
|
|
function TASK:GetUnitProcess(TaskUnit)
|
|
if TaskUnit then
|
|
return self:GetStateMachine(TaskUnit)
|
|
else
|
|
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:E({IsGroupAssigned=IsGroupAssigned})
|
|
if IsGroupAssigned then
|
|
self:AssignToUnit(PlayerUnit)
|
|
self:MessageToGroups(PlayerUnit:GetPlayerName().." joined Task "..self:GetName())
|
|
end
|
|
end
|
|
end
|
|
return PlayerUnitAdded
|
|
end
|
|
function TASK:AbortGroup(PlayerGroup)
|
|
self:F({PlayerGroup=PlayerGroup})
|
|
local PlayerGroups=self:GetGroups()
|
|
if PlayerGroups:IsIncludeObject(PlayerGroup)then
|
|
if self:IsStateAssigned()then
|
|
local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup)
|
|
self:E({IsGroupAssigned=IsGroupAssigned})
|
|
if IsGroupAssigned then
|
|
local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName()
|
|
self:UnAssignFromGroup(PlayerGroup)
|
|
PlayerGroups:Flush()
|
|
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:E({IsGroupAssigned=IsGroupAssigned})
|
|
if IsGroupAssigned then
|
|
local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName()
|
|
self:MessageToGroups(PlayerName.." crashed! ")
|
|
self:UnAssignFromGroup(PlayerGroup)
|
|
PlayerGroups:Flush()
|
|
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
|
|
do
|
|
function TASK:IsGroupAssigned(TaskGroup)
|
|
local TaskGroupName=TaskGroup:GetName()
|
|
if self.AssignedGroups[TaskGroupName]then
|
|
self:T({"Task is assigned to:",TaskGroup:GetName()})
|
|
return true
|
|
end
|
|
self:T({"Task is not assigned to:",TaskGroup:GetName()})
|
|
return false
|
|
end
|
|
function TASK:SetGroupAssigned(TaskGroup)
|
|
local TaskName=self:GetName()
|
|
local TaskGroupName=TaskGroup:GetName()
|
|
self.AssignedGroups[TaskGroupName]=TaskGroup
|
|
self:E(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: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:E(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()
|
|
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()
|
|
end
|
|
end
|
|
function TASK:HasGroup(FindGroup)
|
|
local SetAttackGroup=self:GetGroups()
|
|
return SetAttackGroup:FindGroup(FindGroup)
|
|
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)
|
|
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
|
|
local TaskGroup=TaskGroup
|
|
CC:MessageToGroup(Message,TaskGroup,TaskGroup:GetName())
|
|
end
|
|
end
|
|
function TASK:SendBriefingToAssignedGroups()
|
|
self:F2()
|
|
for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do
|
|
if self:IsGroupAssigned(TaskGroup)then
|
|
TaskGroup:Message(self.TaskBriefing,60)
|
|
end
|
|
end
|
|
end
|
|
function TASK:UnAssignFromGroups()
|
|
self:F2()
|
|
for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do
|
|
if self:IsGroupAssigned(TaskGroup)then
|
|
self:UnAssignFromGroup(TaskGroup)
|
|
end
|
|
end
|
|
end
|
|
function TASK:HasAliveUnits()
|
|
self:F()
|
|
for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
|
|
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
|
|
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()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 CommandCenter=Mission:GetCommandCenter()
|
|
local CommandCenterMenu=CommandCenter:GetMenu()
|
|
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())
|
|
local MissionMenu=Mission:GetMenu(TaskGroup)
|
|
self.MenuPlanned=self.MenuPlanned or{}
|
|
self.MenuPlanned[TaskGroup]=MENU_GROUP:New(TaskGroup,"Join Planned Task",MissionMenu,Mission.MenuReportTasksPerStatus,Mission,TaskGroup,"Planned"):SetTime(MenuTime):SetTag("Tasking")
|
|
local TaskTypeMenu=MENU_GROUP:New(TaskGroup,TaskType,self.MenuPlanned[TaskGroup]):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
|
|
local TaskTypeMenu=MENU_GROUP:New(TaskGroup,TaskText,TaskTypeMenu):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
|
|
local ReportTaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Report Task Status"),TaskTypeMenu,self.MenuTaskStatus,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
|
|
if not Mission:IsGroupAssigned(TaskGroup)then
|
|
self:F({"Replacing Join Task menu"})
|
|
local JoinTaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Join Task"),TaskTypeMenu,self.MenuAssignToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
|
|
local MarkTaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Mark Task on Map"),TaskTypeMenu,self.MenuMarkToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
|
|
end
|
|
return self
|
|
end
|
|
function TASK:SetAssignedMenuForGroup(TaskGroup,MenuTime)
|
|
self:F({TaskGroup:GetName(),MenuTime})
|
|
local Mission=self:GetMission()
|
|
local MissionName=Mission:GetName()
|
|
local CommandCenter=Mission:GetCommandCenter()
|
|
local CommandCenterMenu=CommandCenter:GetMenu()
|
|
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())
|
|
local MissionMenu=Mission:GetMenu(TaskGroup)
|
|
self.MenuAssigned=self.MenuAssigned or{}
|
|
self.MenuAssigned[TaskGroup]=MENU_GROUP:New(TaskGroup,string.format("Assigned Task %s",TaskName),MissionMenu):SetTime(MenuTime):SetTag("Tasking")
|
|
local TaskTypeMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Report Task Status"),self.MenuAssigned[TaskGroup],self.MenuTaskStatus,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
|
|
local TaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Abort Group from Task"),self.MenuAssigned[TaskGroup],self.MenuTaskAbort,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking"):SetRemoveParent(true)
|
|
return self
|
|
end
|
|
function TASK:RemoveMenu(MenuTime)
|
|
self:F({self:GetName(),MenuTime})
|
|
for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
|
|
local TaskGroup=TaskGroup
|
|
self:RefreshMenus(TaskGroup,MenuTime)
|
|
end
|
|
end
|
|
function TASK:RefreshMenus(TaskGroup,MenuTime)
|
|
self:F({TaskGroup:GetName(),MenuTime})
|
|
local Mission=self:GetMission()
|
|
local MissionName=Mission:GetName()
|
|
local CommandCenter=Mission:GetCommandCenter()
|
|
local CommandCenterMenu=CommandCenter:GetMenu()
|
|
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
|
|
PlannedMenu:Remove(MenuTime,"Tasking")
|
|
end
|
|
if AssignedMenu then
|
|
AssignedMenu:Remove(MenuTime,"Tasking")
|
|
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:E("Join Task menu selected")
|
|
self:AssignToGroup(TaskGroup)
|
|
end
|
|
function TASK:MenuMarkToGroup(TaskGroup)
|
|
self:E("Mark Task menu selected")
|
|
self:UpdateTaskInfo()
|
|
local Report=REPORT:New():SetIndent(0)
|
|
local Name=self:GetName()
|
|
Report:Add(Name..": "..self:GetTaskBriefing())
|
|
for TaskInfoID,TaskInfo in pairs(self.TaskInfo,function(t,a,b)return t[a].TaskInfoOrder<t[b].TaskInfoOrder end)do
|
|
local TaskInfoIDText=""
|
|
if type(TaskInfo.TaskInfoText)=="string"then
|
|
if TaskInfoID=="Targets"then
|
|
else
|
|
Report:Add(TaskInfoIDText..TaskInfo.TaskInfoText)
|
|
end
|
|
elseif type(TaskInfo)=="table"then
|
|
if TaskInfoID=="Coordinates"then
|
|
else
|
|
end
|
|
end
|
|
end
|
|
local Coordinate=self:GetInfo("Coordinates")
|
|
local Velocity=self.TargetSetUnit:GetVelocity()
|
|
local Heading=self.TargetSetUnit:GetHeading()
|
|
Coordinate:SetHeading(Heading)
|
|
Coordinate:SetVelocity(Velocity)
|
|
Report:Add("Targets are"..Coordinate:GetMovingText()..".")
|
|
local MarkText=Report:Text(", ")
|
|
self:F({Coordinate=Coordinate,MarkText=MarkText})
|
|
Coordinate:MarkToGroup(MarkText,TaskGroup)
|
|
end
|
|
function TASK:MenuTaskStatus(TaskGroup)
|
|
local ReportText=self:ReportDetails(TaskGroup)
|
|
self:T(ReportText)
|
|
self:GetMission():GetCommandCenter():MessageTypeToGroup(ReportText,TaskGroup,MESSAGE.Type.Detailed)
|
|
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:E("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:SetInfo(TaskInfo,TaskInfoText,TaskInfoOrder)
|
|
self.TaskInfo=self.TaskInfo or{}
|
|
self.TaskInfo[TaskInfo]=self.TaskInfo[TaskInfo]or{}
|
|
self.TaskInfo[TaskInfo].TaskInfoText=TaskInfoText
|
|
self.TaskInfo[TaskInfo].TaskInfoOrder=TaskInfoOrder
|
|
end
|
|
function TASK:GetInfo(TaskInfo)
|
|
self.TaskInfo=self.TaskInfo or{}
|
|
self.TaskInfo[TaskInfo]=self.TaskInfo[TaskInfo]or{}
|
|
return self.TaskInfo[TaskInfo].TaskInfoText
|
|
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:E(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
|
|
self:E({From,Event,To,PlayerUnit:GetName(),PlayerName})
|
|
self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." is assigned.")
|
|
self:SetGoalTotal()
|
|
if self.Dispatcher then
|
|
self:E("Firing Assign event ")
|
|
self.Dispatcher:Assign(self,PlayerUnit,PlayerName)
|
|
end
|
|
self:GetMission():__Start(1)
|
|
self:__Goal(-10)
|
|
self:SetMenu()
|
|
end
|
|
end
|
|
function TASK:onenterSuccess(From,Event,To)
|
|
self:E("Task Success")
|
|
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:E("Task Aborted")
|
|
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:E("Task Cancelled")
|
|
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:E("Task Replanned")
|
|
self:GetMission():GetCommandCenter():MessageToCoalition("Replanning Task "..self:GetName()..".")
|
|
self:SetMenu()
|
|
end
|
|
function TASK:onenterFailed(From,Event,To)
|
|
self:E("Task Failed")
|
|
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:E({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:SetDispatcher(Dispatcher)
|
|
self.Dispatcher=Dispatcher
|
|
end
|
|
function TASK:SetDetection(Detection,DetectedItemIndex)
|
|
self:E({DetectedItemIndex,Detection})
|
|
self.Detection=Detection
|
|
self.DetectedItemIndex=DetectedItemIndex
|
|
end
|
|
end
|
|
do
|
|
function TASK:ReportSummary(ReportGroup)
|
|
local Report=REPORT:New()
|
|
Report:Add(self:GetName())
|
|
Report:Add("State: <"..self:GetState()..">")
|
|
if self.TaskInfo["Coordinates"]then
|
|
local TaskInfoIDText=string.format("%s: ","Coordinate")
|
|
local TaskCoord=self.TaskInfo["Coordinates"].TaskInfoText
|
|
Report:Add(TaskInfoIDText..TaskCoord:ToString(ReportGroup,nil,self))
|
|
end
|
|
return Report:Text(', ')
|
|
end
|
|
function TASK:ReportOverview(ReportGroup)
|
|
self:UpdateTaskInfo()
|
|
local TaskName=self:GetName()
|
|
local Report=REPORT:New()
|
|
local Line=0
|
|
local LineReport=REPORT:New()
|
|
for TaskInfoID,TaskInfo in UTILS.spairs(self.TaskInfo,function(t,a,b)return t[a].TaskInfoOrder<t[b].TaskInfoOrder end)do
|
|
self:F({TaskInfo=TaskInfo})
|
|
if Line<math.floor(TaskInfo.TaskInfoOrder/10)then
|
|
if Line~=0 then
|
|
Report:AddIndent(LineReport:Text(", "))
|
|
else
|
|
Report:Add(TaskName..", "..LineReport:Text(", "))
|
|
end
|
|
LineReport=REPORT:New()
|
|
Line=math.floor(TaskInfo.TaskInfoOrder/10)
|
|
end
|
|
local TaskInfoIDText=string.format("%s: ",TaskInfoID)
|
|
if type(TaskInfo.TaskInfoText)=="string"then
|
|
LineReport:Add(TaskInfoIDText..TaskInfo.TaskInfoText)
|
|
elseif type(TaskInfo)=="table"then
|
|
if TaskInfoID=="Coordinates"then
|
|
local ToCoordinate=TaskInfo.TaskInfoText
|
|
LineReport:Add(TaskInfoIDText..ToCoordinate:ToString(ReportGroup,nil,self))
|
|
else
|
|
end
|
|
end
|
|
end
|
|
Report:AddIndent(LineReport:Text(", "))
|
|
return Report:Text()
|
|
end
|
|
function TASK:GetPlayerCount()
|
|
local PlayerCount=0
|
|
for TaskGroupID,PlayerGroup in pairs(self:GetGroups():GetSet())do
|
|
local PlayerGroup=PlayerGroup
|
|
if self:IsGroupAssigned(PlayerGroup)then
|
|
local PlayerNames=PlayerGroup:GetPlayerNames()
|
|
PlayerCount=PlayerCount+#PlayerNames
|
|
end
|
|
end
|
|
return PlayerCount
|
|
end
|
|
function TASK:GetPlayerNames()
|
|
local PlayerNameMap={}
|
|
for TaskGroupID,PlayerGroup in pairs(self:GetGroups():GetSet())do
|
|
local PlayerGroup=PlayerGroup
|
|
if self:IsGroupAssigned(PlayerGroup)then
|
|
local PlayerNames=PlayerGroup:GetPlayerNames()
|
|
for PlayerNameID,PlayerName in pairs(PlayerNames)do
|
|
PlayerNameMap[PlayerName]=PlayerGroup
|
|
end
|
|
end
|
|
end
|
|
return PlayerNameMap
|
|
end
|
|
function TASK:ReportDetails(ReportGroup)
|
|
self:UpdateTaskInfo()
|
|
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("Group "..PlayerGroup:GetCallsign()..": "..PlayerName)
|
|
end
|
|
local Players=PlayerReport:Text()
|
|
if Players~=""then
|
|
Report:Add(" - Players assigned:")
|
|
Report:AddIndent(Players)
|
|
end
|
|
for TaskInfoID,TaskInfo in pairs(self.TaskInfo,function(t,a,b)return t[a].TaskInfoOrder<t[b].TaskInfoOrder end)do
|
|
local TaskInfoIDText=string.format(" - %s: ",TaskInfoID)
|
|
if type(TaskInfo.TaskInfoText)=="string"then
|
|
Report:Add(TaskInfoIDText..TaskInfo.TaskInfoText)
|
|
elseif type(TaskInfo)=="table"then
|
|
if TaskInfoID=="Coordinates"then
|
|
local FromCoordinate=ReportGroup:GetUnit(1):GetCoordinate()
|
|
local ToCoordinate=TaskInfo.TaskInfoText
|
|
Report:Add(TaskInfoIDText..ToCoordinate:ToString(ReportGroup:GetUnit(1),nil,self))
|
|
else
|
|
end
|
|
end
|
|
end
|
|
local Coordinate=self:GetInfo("Coordinates")
|
|
local Velocity=self.TargetSetUnit:GetVelocity()
|
|
local Heading=self.TargetSetUnit:GetHeading()
|
|
Coordinate:SetHeading(Heading)
|
|
Coordinate:SetVelocity(Velocity)
|
|
Report:Add("Targets are"..Coordinate:GetMovingText()..".")
|
|
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
|
|
DETECTION_MANAGER={
|
|
ClassName="DETECTION_MANAGER",
|
|
SetGroup=nil,
|
|
Detection=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","Report","Started")
|
|
self:SetRefreshTimeInterval(30)
|
|
self:SetReportDisplayTime(25)
|
|
self:E({Detection=Detection})
|
|
Detection:__Start(3)
|
|
return self
|
|
end
|
|
function DETECTION_MANAGER:onafterStart(From,Event,To)
|
|
self:Report()
|
|
end
|
|
function DETECTION_MANAGER:onafterReport(From,Event,To)
|
|
self:E("onafterReport")
|
|
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:ProcessDetected(Detection)
|
|
self:E()
|
|
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)
|
|
self:E(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.Detection:FilterCategories(Unit.Category.GROUND_UNIT,Unit.Category.SHIP)
|
|
self.Detection:FilterFriendliesCategory(Unit.Category.GROUND_UNIT)
|
|
self:AddTransition("Started","Assign","Started")
|
|
self:__Start(5)
|
|
return self
|
|
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)
|
|
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)
|
|
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:E()
|
|
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:GetDetectedItem(TaskIndex)
|
|
if not DetectedItem then
|
|
local TaskText=Task:GetName()
|
|
for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do
|
|
Mission:GetCommandCenter():MessageToGroup(string.format("Obsolete A2G task %s for %s removed.",TaskText,Mission:GetName()),TaskGroup)
|
|
end
|
|
Task=self:RemoveTask(TaskIndex)
|
|
Mission:RemoveTask(Task)
|
|
self.Tasks[TaskIndex]=nil
|
|
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:E({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:UpdateTaskInfo()
|
|
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,TaskIndex)
|
|
Task:UpdateTaskInfo()
|
|
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,TaskIndex)
|
|
Task:UpdateTaskInfo()
|
|
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~=""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:UpdateTaskInfo()
|
|
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,TaskIndex)
|
|
Task:UpdateTaskInfo()
|
|
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,TaskIndex)
|
|
Task:UpdateTaskInfo()
|
|
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)
|
|
Task:SetDetection(Detection,TaskIndex)
|
|
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)
|
|
Task:SetDetection(Detection,TaskIndex)
|
|
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)
|
|
Task:SetDetection(Detection,TaskIndex)
|
|
end
|
|
end
|
|
end
|
|
if Task then
|
|
self.Tasks[TaskIndex]=Task
|
|
Task:SetTargetZone(DetectedZone)
|
|
Task:SetDispatcher(self)
|
|
Task:UpdateTaskInfo()
|
|
Mission:AddTask(Task)
|
|
TaskReport:Add(Task:GetName())
|
|
else
|
|
self:E("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~=""then
|
|
Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetName(),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:AddProcess("Planned","Accept",ACT_ASSIGN_ACCEPT:New(self.TaskBriefing),{Assigned="RouteToRendezVous",Rejected="Reject"})
|
|
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:onafterRouteToRendezVous(TaskUnit,Task)
|
|
self:E({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:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
|
|
self:__Engage(0.1)
|
|
end
|
|
function Fsm:onafterEngage(TaskUnit,Task)
|
|
self:E({self})
|
|
self:__Account(0.1)
|
|
self:__RouteToTarget(0.1)
|
|
self:__RouteToTargets(-10)
|
|
end
|
|
function Fsm:onafterRouteToTarget(TaskUnit,Task)
|
|
self:E({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:E({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
|
|
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:UpdateTaskInfo()
|
|
local TargetCoordinate=self.Detection and self.Detection:GetDetectedItemCoordinate(self.DetectedItemIndex)or self.TargetSetUnit:GetFirst():GetCoordinate()
|
|
self:SetInfo("Coordinates",TargetCoordinate,0)
|
|
local ThreatLevel,ThreatText
|
|
if self.Detection then
|
|
ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(self.DetectedItemIndex)
|
|
else
|
|
ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G()
|
|
end
|
|
self:SetInfo("Threat",ThreatText.." ["..string.rep("■",ThreatLevel).."]",11)
|
|
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:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,ReportTypes:Text(", ")),10)
|
|
else
|
|
local DetectedItemsCount=self.TargetSetUnit:Count()
|
|
local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
|
|
self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,DetectedItemsTypes),10)
|
|
end
|
|
end
|
|
function TASK_A2G_SEAD:ReportOrder(ReportGroup)
|
|
local Coordinate=self:GetInfo("Coordinates")
|
|
local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
|
|
return Distance
|
|
end
|
|
function TASK_A2G_SEAD:onafterGoal(TaskUnit,From,Event,To)
|
|
local TargetSetUnit=self.TargetSetUnit
|
|
if TargetSetUnit:Count()==0 then
|
|
self:Success()
|
|
end
|
|
self:__Goal(-10)
|
|
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:UpdateTaskInfo()
|
|
self:E({self.Detection,self.DetectedItemIndex})
|
|
local TargetCoordinate=self.Detection and self.Detection:GetDetectedItemCoordinate(self.DetectedItemIndex)or self.TargetSetUnit:GetFirst():GetCoordinate()
|
|
self:SetInfo("Coordinates",TargetCoordinate,0)
|
|
local ThreatLevel,ThreatText
|
|
if self.Detection then
|
|
ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(self.DetectedItemIndex)
|
|
else
|
|
ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G()
|
|
end
|
|
self:SetInfo("Threat",ThreatText.." ["..string.rep("■",ThreatLevel).."]",11)
|
|
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:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,ReportTypes:Text(", ")),10)
|
|
else
|
|
local DetectedItemsCount=self.TargetSetUnit:Count()
|
|
local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
|
|
self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,DetectedItemsTypes),10)
|
|
end
|
|
end
|
|
function TASK_A2G_BAI:ReportOrder(ReportGroup)
|
|
local Coordinate=self:GetInfo("Coordinates")
|
|
local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
|
|
return Distance
|
|
end
|
|
function TASK_A2G_BAI:onafterGoal(TaskUnit,From,Event,To)
|
|
local TargetSetUnit=self.TargetSetUnit
|
|
if TargetSetUnit:Count()==0 then
|
|
self:Success()
|
|
end
|
|
self:__Goal(-10)
|
|
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:UpdateTaskInfo()
|
|
local TargetCoordinate=(self.Detection and self.Detection:GetDetectedItemCoordinate(self.DetectedItemIndex))or self.TargetSetUnit:GetFirst():GetCoordinate()
|
|
self:SetInfo("Coordinates",TargetCoordinate,0)
|
|
local ThreatLevel,ThreatText
|
|
if self.Detection then
|
|
ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(self.DetectedItemIndex)
|
|
else
|
|
ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G()
|
|
end
|
|
self:SetInfo("Threat",ThreatText.." ["..string.rep("■",ThreatLevel).."]",11)
|
|
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:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,ReportTypes:Text(", ")),10)
|
|
else
|
|
local DetectedItemsCount=self.TargetSetUnit:Count()
|
|
local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
|
|
self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,DetectedItemsTypes),10)
|
|
end
|
|
end
|
|
function TASK_A2G_CAS:ReportOrder(ReportGroup)
|
|
local Coordinate=self:GetInfo("Coordinates")
|
|
local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
|
|
return Distance
|
|
end
|
|
function TASK_A2G_CAS:onafterGoal(TaskUnit,From,Event,To)
|
|
local TargetSetUnit=self.TargetSetUnit
|
|
if TargetSetUnit:Count()==0 then
|
|
self:Success()
|
|
end
|
|
self:__Goal(-10)
|
|
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.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: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)
|
|
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:E()
|
|
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:GetDetectedItem(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:GetName()),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,TaskIndex)
|
|
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,TaskIndex)
|
|
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,TaskIndex)
|
|
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)
|
|
TaskReport:Add(Task:GetName())
|
|
else
|
|
self:E("This should not happen")
|
|
end
|
|
end
|
|
if Task then
|
|
local FriendliesCount,FriendliesReport=self:GetFriendliesNearBy(DetectedItem)
|
|
Task:SetInfo("Friendlies",string.format("%d ( %s )",FriendliesCount,FriendliesReport:Text(",")),30)
|
|
local PlayersCount,PlayersReport=self:GetPlayerFriendliesNearBy(DetectedItem)
|
|
Task:SetInfo("Players",string.format("%d ( %s )",PlayersCount,PlayersReport:Text(",")),31)
|
|
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~=""then
|
|
Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetName(),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:AddProcess("Planned","Accept",ACT_ASSIGN_ACCEPT:New(self.TaskBriefing),{Assigned="RouteToRendezVous",Rejected="Reject"})
|
|
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:onafterRouteToRendezVous(TaskUnit,Task)
|
|
self:E({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:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
|
|
self:__Engage(0.1)
|
|
end
|
|
function Fsm:onafterEngage(TaskUnit,Task)
|
|
self:E({self})
|
|
self:__Account(0.1)
|
|
self:__RouteToTarget(0.1)
|
|
self:__RouteToTargets(-10)
|
|
end
|
|
function Fsm:onafterRouteToTarget(TaskUnit,Task)
|
|
self:E({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:GetCoordinate()
|
|
self:T({TargetCoordinate=Coordinate,Coordinate:GetX(),Coordinate:GetAlt(),Coordinate:GetZ()})
|
|
Task:SetTargetCoordinate(TargetUnit:GetCoordinate(),TaskUnit)
|
|
end
|
|
self:__RouteToTargetPoint(0.1)
|
|
end
|
|
end
|
|
function Fsm:onafterRouteToTargets(TaskUnit,Task)
|
|
self:E({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: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
|
|
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"
|
|
)
|
|
self:UpdateTaskInfo()
|
|
return self
|
|
end
|
|
function TASK_A2A_INTERCEPT:UpdateTaskInfo()
|
|
local TargetCoordinate=self.Detection and self.Detection:GetDetectedItemCoordinate(self.DetectedItemIndex)or self.TargetSetUnit:GetFirst():GetCoordinate()
|
|
self:SetInfo("Coordinates",TargetCoordinate,0)
|
|
self:SetInfo("Threat","["..string.rep("■",self.Detection and self.Detection:GetDetectedItemThreatLevel(self.DetectedItemIndex)or self.TargetSetUnit:CalculateThreatLevelA2G()).."]",11)
|
|
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:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,ReportTypes:Text(", ")),10)
|
|
else
|
|
local DetectedItemsCount=self.TargetSetUnit:Count()
|
|
local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
|
|
self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,DetectedItemsTypes),10)
|
|
end
|
|
end
|
|
function TASK_A2A_INTERCEPT:ReportOrder(ReportGroup)
|
|
self:F({TaskInfo=self.TaskInfo})
|
|
local Coordinate=self.TaskInfo.Coordinates.TaskInfoText
|
|
local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
|
|
return Distance
|
|
end
|
|
function TASK_A2A_INTERCEPT:onafterGoal(TaskUnit,From,Event,To)
|
|
local TargetSetUnit=self.TargetSetUnit
|
|
if TargetSetUnit:Count()==0 then
|
|
self:Success()
|
|
end
|
|
self:__Goal(-10)
|
|
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"
|
|
)
|
|
self:UpdateTaskInfo()
|
|
return self
|
|
end
|
|
function TASK_A2A_SWEEP:UpdateTaskInfo()
|
|
local TargetCoordinate=self.Detection and self.Detection:GetDetectedItemCoordinate(self.DetectedItemIndex)or self.TargetSetUnit:GetFirst():GetCoordinate()
|
|
self:SetInfo("Coordinates",TargetCoordinate,0)
|
|
self:SetInfo("Assumed Threat","["..string.rep("■",self.Detection and self.Detection:GetDetectedItemThreatLevel(self.DetectedItemIndex)or self.TargetSetUnit:CalculateThreatLevelA2G()).."]",11)
|
|
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:SetInfo("Lost Targets",string.format("%d of %s",DetectedItemsCount,ReportTypes:Text(", ")),10)
|
|
else
|
|
local DetectedItemsCount=self.TargetSetUnit:Count()
|
|
local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
|
|
self:SetInfo("Lost Targets",string.format("%d of %s",DetectedItemsCount,DetectedItemsTypes),10)
|
|
end
|
|
end
|
|
function TASK_A2A_SWEEP:ReportOrder(ReportGroup)
|
|
local Coordinate=self.TaskInfo.Coordinates.TaskInfoText
|
|
local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
|
|
return Distance
|
|
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"
|
|
)
|
|
self:UpdateTaskInfo()
|
|
return self
|
|
end
|
|
function TASK_A2A_ENGAGE:UpdateTaskInfo()
|
|
local TargetCoordinate=self.Detection and self.Detection:GetDetectedItemCoordinate(self.DetectedItemIndex)or self.TargetSetUnit:GetFirst():GetCoordinate()
|
|
self:SetInfo("Coordinates",TargetCoordinate,0)
|
|
self:SetInfo("Threat","["..string.rep("■",self.Detection and self.Detection:GetDetectedItemThreatLevel(self.DetectedItemIndex)or self.TargetSetUnit:CalculateThreatLevelA2G()).."]",11)
|
|
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:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,ReportTypes:Text(", ")),10)
|
|
else
|
|
local DetectedItemsCount=self.TargetSetUnit:Count()
|
|
local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames()
|
|
self:SetInfo("Targets",string.format("%d of %s",DetectedItemsCount,DetectedItemsTypes),10)
|
|
end
|
|
end
|
|
function TASK_A2A_ENGAGE:ReportOrder(ReportGroup)
|
|
local Coordinate=self.TaskInfo.Coordinates.TaskInfoText
|
|
local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate)
|
|
return Distance
|
|
end
|
|
function TASK_A2A_ENGAGE:onafterGoal(TaskUnit,From,Event,To)
|
|
local TargetSetUnit=self.TargetSetUnit
|
|
if TargetSetUnit:Count()==0 then
|
|
self:Success()
|
|
end
|
|
self:__Goal(-10)
|
|
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=2
|
|
self.DeployZones={}
|
|
local Fsm=self:GetUnitProcess()
|
|
Fsm:SetStartState("Planned")
|
|
Fsm:AddProcess("Planned","Accept",ACT_ASSIGN_ACCEPT:New(self.TaskBriefing),{Assigned="SelectAction",Rejected="Reject"})
|
|
Fsm:AddTransition({"Assigned","WaitingForCommand","ArrivedAtPickup","ArrivedAtDeploy","Boarded","UnBoarded","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","WaitingForCommand")
|
|
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","WaitingForCommand")
|
|
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("*","PrepareUnBoarding","AwaitUnBoarding")
|
|
Fsm:AddTransition("AwaitUnBoarding","UnBoard","UnBoarding")
|
|
Fsm:AddTransition("UnBoarding","UnBoarded","UnBoarded")
|
|
Fsm:AddTransition("Deployed","Success","Success")
|
|
Fsm:AddTransition("Rejected","Reject","Aborted")
|
|
Fsm:AddTransition("Failed","Fail","Failed")
|
|
function Fsm:onafterSelectAction(TaskUnit,Task)
|
|
local TaskUnitName=TaskUnit:GetName()
|
|
self:E({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()})
|
|
local MenuTime=timer.getTime()
|
|
TaskUnit.Menu=MENU_GROUP:New(TaskUnit:GetGroup(),Task:GetName().." @ "..TaskUnit:GetName()):SetTime(MenuTime)
|
|
local CargoItemCount=TaskUnit:CargoItemCount()
|
|
Task.SetCargo:ForEachCargo(
|
|
function(Cargo)
|
|
if Cargo:IsAlive()then
|
|
if Cargo:IsUnLoaded()then
|
|
if CargoItemCount<Task.CargoLimit then
|
|
if Cargo:IsInRadius(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
|
|
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),"Board cargo "..Cargo.Name,TaskUnit.Menu,self.MenuBoardCargo,self,Cargo):SetTime(MenuTime)
|
|
end
|
|
end
|
|
else
|
|
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),"Route to Pickup cargo "..Cargo.Name,TaskUnit.Menu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime)
|
|
end
|
|
end
|
|
end
|
|
if Cargo:IsLoaded()then
|
|
if not TaskUnit:InAir()then
|
|
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),"Unboard cargo "..Cargo.Name,TaskUnit.Menu,self.MenuUnBoardCargo,self,Cargo):SetTime(MenuTime)
|
|
end
|
|
for DeployZoneName,DeployZone in pairs(Task.DeployZones)do
|
|
if not Cargo:IsInZone(DeployZone)then
|
|
MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),"Route to Deploy cargo at "..DeployZoneName,TaskUnit.Menu,self.MenuRouteToDeploy,self,DeployZone):SetTime(MenuTime)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
)
|
|
TaskUnit.Menu:Remove(MenuTime)
|
|
self:__SelectAction(-15)
|
|
end
|
|
function Fsm:OnLeaveWaitingForCommand(TaskUnit,Task)
|
|
self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
|
|
TaskUnit.Menu:Remove()
|
|
end
|
|
function Fsm:MenuBoardCargo(Cargo)
|
|
self:__PrepareBoarding(1.0,Cargo)
|
|
end
|
|
function Fsm:MenuUnBoardCargo(Cargo,DeployZone)
|
|
self:__PrepareUnBoarding(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:E({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:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
|
|
if self.Cargo:IsAlive()then
|
|
self.Cargo:Smoke(Task:GetSmokeColor(),15)
|
|
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:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
|
|
self:__SelectAction(-0.1)
|
|
end
|
|
function Fsm:onafterRouteToDeploy(TaskUnit,Task,From,Event,To,DeployZone)
|
|
self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
|
|
self:E(DeployZone)
|
|
self.DeployZone=DeployZone
|
|
Task:SetDeployZone(self.DeployZone,TaskUnit)
|
|
self:__RouteToDeployZone(-0.1)
|
|
end
|
|
function Fsm:onafterArriveAtDeploy(TaskUnit,Task)
|
|
self:E({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:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
|
|
self:__SelectAction(-0.1)
|
|
end
|
|
function Fsm:onafterLand(TaskUnit,Task,From,Event,To,Action)
|
|
self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
|
|
if self.Cargo:IsAlive()then
|
|
if self.Cargo:IsInRadius(TaskUnit:GetPointVec2())then
|
|
if TaskUnit:InAir()then
|
|
self:__Land(-10,Action)
|
|
else
|
|
Task:GetMission():GetCommandCenter():MessageToGroup("Landed ...",TaskUnit:GetGroup())
|
|
self:__Landed(-0.1,Action)
|
|
end
|
|
else
|
|
if Action=="Pickup"then
|
|
self:__RouteToPickupZone(-0.1)
|
|
else
|
|
self:__RouteToDeployZone(-0.1)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function Fsm:onafterLanded(TaskUnit,Task,From,Event,To,Action)
|
|
self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
|
|
if self.Cargo:IsAlive()then
|
|
if self.Cargo:IsInRadius(TaskUnit:GetPointVec2())then
|
|
if TaskUnit:InAir()then
|
|
self:__Land(-0.1,Action)
|
|
else
|
|
self:__SelectAction(-0.1)
|
|
end
|
|
else
|
|
if Action=="Pickup"then
|
|
self:__RouteToPickupZone(-0.1)
|
|
else
|
|
self:__RouteToDeployZone(-0.1)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function Fsm:onafterPrepareBoarding(TaskUnit,Task,From,Event,To,Cargo)
|
|
self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
|
|
if Cargo and Cargo:IsAlive()then
|
|
self.Cargo=Cargo
|
|
self:__Board(-0.1)
|
|
end
|
|
end
|
|
function Fsm:onafterBoard(TaskUnit,Task)
|
|
self:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()})
|
|
function self.Cargo:OnEnterLoaded(From,Event,To,TaskUnit,TaskProcess)
|
|
self:E({From,Event,To,TaskUnit,TaskProcess})
|
|
TaskProcess:__Boarded(0.1)
|
|
end
|
|
if self.Cargo:IsAlive()then
|
|
if self.Cargo:IsInRadius(TaskUnit:GetPointVec2())then
|
|
if TaskUnit:InAir()then
|
|
else
|
|
self.Cargo:MessageToGroup("Boarding ...",TaskUnit:GetGroup())
|
|
self.Cargo:Board(TaskUnit,20,self)
|
|
end
|
|
else
|
|
end
|
|
end
|
|
end
|
|
function Fsm:onafterBoarded(TaskUnit,Task)
|
|
local TaskUnitName=TaskUnit:GetName()
|
|
self:E({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()})
|
|
self.Cargo:MessageToGroup("Boarded ...",TaskUnit:GetGroup())
|
|
TaskUnit:AddCargo(self.Cargo)
|
|
self:__SelectAction(1)
|
|
Task:E({CargoPickedUp=Task.CargoPickedUp})
|
|
if self.Cargo:IsAlive()then
|
|
if Task.CargoPickedUp then
|
|
Task:CargoPickedUp(TaskUnit,self.Cargo)
|
|
end
|
|
end
|
|
end
|
|
function Fsm:onafterPrepareUnBoarding(TaskUnit,Task,From,Event,To,Cargo)
|
|
self:E({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:E({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID(),From,Event,To,Cargo,DeployZone})
|
|
function self.Cargo:OnEnterUnLoaded(From,Event,To,DeployZone,TaskProcess)
|
|
self:E({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:GetPointVec2(),400,self)
|
|
else
|
|
self.Cargo:UnBoard(TaskUnit:GetPointVec2():AddX(60),400,self)
|
|
end
|
|
end
|
|
end
|
|
function Fsm:onafterUnBoarded(TaskUnit,Task)
|
|
local TaskUnitName=TaskUnit:GetName()
|
|
self:E({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()})
|
|
self.Cargo:MessageToGroup("UnBoarded ...",TaskUnit:GetGroup())
|
|
TaskUnit:RemoveCargo(self.Cargo)
|
|
local NotInDeployZones=true
|
|
for DeployZoneName,DeployZone in pairs(Task.DeployZones)do
|
|
if self.Cargo:IsInZone(DeployZone)then
|
|
NotInDeployZones=false
|
|
end
|
|
end
|
|
if NotInDeployZones==false then
|
|
self.Cargo:SetDeployed(true)
|
|
end
|
|
Task:E({CargoDeployed=Task.CargoDeployed and"true"or"false"})
|
|
Task:E({CargoIsAlive=self.Cargo:IsAlive()and"true"or"false"})
|
|
if self.Cargo:IsAlive()then
|
|
if Task.CargoDeployed then
|
|
Task:CargoDeployed(TaskUnit,self.Cargo,self.DeployZone)
|
|
end
|
|
end
|
|
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 ActRouteCargo=ProcessUnit:GetProcess("RoutingToPickup","RouteToPickupPoint")
|
|
ActRouteCargo:Reset()
|
|
ActRouteCargo:SetCoordinate(Cargo:GetCoordinate())
|
|
ActRouteCargo:SetRange(Cargo:GetBoardingRange())
|
|
ActRouteCargo:SetMenuCancel(TaskUnit:GetGroup(),"Cancel Routing to Cargo "..Cargo:GetName(),TaskUnit.Menu)
|
|
ActRouteCargo:Start()
|
|
return self
|
|
end
|
|
function TASK_CARGO:SetDeployZone(DeployZone,TaskUnit)
|
|
local ProcessUnit=self:GetUnitProcess(TaskUnit)
|
|
local ActRouteDeployZone=ProcessUnit:GetProcess("RoutingToDeploy","RouteToDeployZone")
|
|
ActRouteDeployZone:Reset()
|
|
ActRouteDeployZone:SetZone(DeployZone)
|
|
ActRouteDeployZone:SetMenuCancel(TaskUnit:GetGroup(),"Cancel Routing to Deploy Zone"..DeployZone:GetName(),TaskUnit.Menu)
|
|
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)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
|
|
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)
|
|
self:AddTransition("*","CargoPickedUp","*")
|
|
self:AddTransition("*","CargoDeployed","*")
|
|
self:E({CargoDeployed=self.CargoDeployed~=nil and"true"or"false"})
|
|
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 true
|
|
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
|
|
if Cargo:IsDeployed()then
|
|
for DeployZoneID,DeployZone in pairs(DeployZones)do
|
|
self:T({Cargo.CargoObject})
|
|
if Cargo:IsInZone(DeployZone)==false then
|
|
CargoDeployed=false
|
|
end
|
|
end
|
|
else
|
|
CargoDeployed=false
|
|
end
|
|
end
|
|
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
|
|
_EVENTDISPATCHER=EVENT:New()
|
|
_SCHEDULEDISPATCHER=SCHEDULEDISPATCHER:New()
|
|
_DATABASE=DATABASE:New()
|
|
_SETTINGS=SETTINGS:Set()
|
|
BASE:TraceOnOff(false)
|
|
env.info('*** MOOSE INCLUDE END *** ')
|