env.info('*** MOOSE STATIC INCLUDE START *** ') env.info('Moose Generation Timestamp: 20171009_1307') 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 ind0 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=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']-10 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: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=(x2self._.Polygon[i].y)and self._.Polygon[i].y or y1 y2=(y20))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 DistanceMaxThreatLevelA2G 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.xx2)and Coordinate.x or x2 y1=(Coordinate.yy2)and Coordinate.y or y2 z1=(Coordinate.yz2)and Coordinate.z or z2 local Velocity=Coordinate:GetVelocity() if Velocity~=0 then MaxVelocity=(MaxVelocity5 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:T3({"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 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(false) 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 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()=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 UnitAmount0 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=timer.getTime()))then TargetSetUnit:ForEachUnitPerThreatLevel(10,0, function(TargetUnit) self:F({TargetUnit=TargetUnit:GetName()}) if MarkingCountFLmax 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.FLcruiseFLmax 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 qaself.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 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=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(AIPatrol,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) local self=BASE:Inherit(self,AI_A2A:New(AIPatrol)) 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(AIPatrol,From,Event,To) self:F2() self:ClearTargetDistance() self:__Route(1) self.AIPatrol:OnReSpawn( function(PatrolGroup) self:E("ReSpawn") self:__Reset(1) self:__Route(5) end ) end function AI_A2A_PATROL.PatrolRoute(AIPatrol,Fsm) AIPatrol:F({"AI_A2A_PATROL.PatrolRoute:",AIPatrol:GetName()}) if AIPatrol:IsAlive()then Fsm:Route() end end function AI_A2A_PATROL:onafterRoute(AIPatrol,From,Event,To) self:F2() if From=="RTB"then return end if AIPatrol:IsAlive()then local PatrolRoute={} local CurrentCoord=AIPatrol: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]=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute",self) PatrolRoute[#PatrolRoute].task=AIPatrol:TaskCombo(Tasks) AIPatrol:OptionROEReturnFire() AIPatrol:OptionROTEvadeFire() AIPatrol:Route(PatrolRoute,0.5) end end function AI_A2A_PATROL.Resume(AIPatrol) AIPatrol:F({"AI_A2A_PATROL.Resume:",AIPatrol:GetName()}) if AIPatrol:IsAlive()then local _AI_A2A=AIPatrol:GetState(AIPatrol,"AI_A2A") _AI_A2A:__Reset(1) _AI_A2A:__Route(5) end end AI_A2A_CAP={ ClassName="AI_A2A_CAP", } function AI_A2A_CAP:New(AICap,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,PatrolAltType) local self=BASE:Inherit(self,AI_A2A_PATROL:New(AICap,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(AICap,From,Event,To) AICap:HandleEvent(EVENTS.Takeoff,nil,self) end function AI_A2A_CAP:SetEngageZone(EngageZone) self:F2() if EngageZone then self.EngageZone=EngageZone else self.EngageZone=nil end end function AI_A2A_CAP:SetEngageRange(EngageRange) self:F2() if EngageRange then self.EngageRange=EngageRange else self.EngageRange=nil end end function AI_A2A_CAP:onafterPatrol(AICap,From,Event,To) self:GetParent(self).onafterPatrol(self,AICap,From,Event,To) self:HandleEvent(EVENTS.Dead) end function AI_A2A_CAP.AttackRoute(AICap,Fsm) AICap:F({"AI_A2A_CAP.AttackRoute:",AICap:GetName()}) if AICap:IsAlive()then Fsm:__Engage(0.5) end end function AI_A2A_CAP:onbeforeEngage(AICap,From,Event,To) if self.Accomplished==true then return false end end function AI_A2A_CAP:onafterAbort(AICap,From,Event,To) AICap:ClearTasks() self:__Route(0.5) end function AI_A2A_CAP:onafterEngage(AICap,From,Event,To,AttackSetUnit) self:F({AICap,From,Event,To,AttackSetUnit}) self.AttackSetUnit=AttackSetUnit or self.AttackSetUnit local FirstAttackUnit=self.AttackSetUnit:GetFirst() if FirstAttackUnit and FirstAttackUnit:IsAlive()then if AICap:IsAlive()then local EngageRoute={} local CurrentCoord=AICap: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]=AICap:TaskAttackUnit(AttackUnit) end end if#AttackTasks==0 then self:E("No targets found -> Going back to Patrolling") self:__Abort(0.5) else AICap:OptionROEOpenFire() AICap:OptionROTEvadeFire() AttackTasks[#AttackTasks+1]=AICap:TaskFunction("AI_A2A_CAP.AttackRoute",self) EngageRoute[#EngageRoute].task=AICap:TaskCombo(AttackTasks) end AICap: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(AICap,From,Event,To) self.Accomplished=true self:SetDetectionOff() end function AI_A2A_CAP:onafterDestroy(AICap,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(AICap) AICap:F({"AI_A2A_CAP.Resume:",AICap:GetName()}) if AICap:IsAlive()then local _AI_A2A=AICap:GetState(AICap,"AI_A2A") _AI_A2A:__Reset(1) _AI_A2A:__Route(5) end end AI_A2A_GCI={ ClassName="AI_A2A_GCI", } function AI_A2A_GCI:New(AIIntercept,EngageMinSpeed,EngageMaxSpeed) local self=BASE:Inherit(self,AI_A2A:New(AIIntercept)) 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(AIIntercept,From,Event,To) AIIntercept:HandleEvent(EVENTS.Takeoff,nil,self) end function AI_A2A_GCI:onafterEngage(AIIntercept,From,Event,To) self:HandleEvent(EVENTS.Dead) end function AI_A2A_GCI.InterceptRoute(AIIntercept,Fsm) AIIntercept:F({"AI_A2A_GCI.InterceptRoute:",AIIntercept:GetName()}) if AIIntercept:IsAlive()then Fsm:__Engage(0.5) end end function AI_A2A_GCI:onbeforeEngage(AIIntercept,From,Event,To) if self.Accomplished==true then return false end end function AI_A2A_GCI:onafterAbort(AIIntercept,From,Event,To) AIIntercept:ClearTasks() self:Return() self:__RTB(0.5) end function AI_A2A_GCI:onafterEngage(AIIntercept,From,Event,To,AttackSetUnit) self:F({AIIntercept,From,Event,To,AttackSetUnit}) self.AttackSetUnit=AttackSetUnit or self.AttackSetUnit local FirstAttackUnit=self.AttackSetUnit:GetFirst() if FirstAttackUnit and FirstAttackUnit:IsAlive()then if AIIntercept:IsAlive()then local EngageRoute={} local CurrentCoord=AIIntercept:GetCoordinate() local CurrentCoord=AIIntercept: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]=AIIntercept:TaskAttackUnit(AttackUnit) end end if#AttackTasks==0 then self:E("No targets found -> Going RTB") self:Return() self:__RTB(0.5) else AIIntercept:OptionROEOpenFire() AIIntercept:OptionROTEvadeFire() AttackTasks[#AttackTasks+1]=AIIntercept:TaskFunction("AI_A2A_GCI.InterceptRoute",self) EngageRoute[#EngageRoute].task=AIIntercept:TaskCombo(AttackTasks) end AIIntercept: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(AIIntercept,From,Event,To) self.Accomplished=true self:SetDetectionOff() end function AI_A2A_GCI:onafterDestroy(AIIntercept,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 CapCount0)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 InterceptDistanceDefenderSquadron.Resources then DefendersNeeded=DefenderSquadron.Resources BreakLoop=true end while(DefendersNeeded>0)do local Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)] local DefenderGrouping=(DefenderGrouping0 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 DetectedTreatLevel0 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 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:OptionROTEvadeFire() local AttackTasks={} for DetectedUnit,Detected in pairs(self.DetectedUnits)do local DetectedUnit=DetectedUnit self:T({DetectedUnit,DetectedUnit:IsAlive(),DetectedUnit:IsAir()}) if DetectedUnit:IsAlive()and DetectedUnit:IsAir()then if self.EngageZone then if DetectedUnit:IsInZone(self.EngageZone)then self:F({"Within Zone and Engaging ",DetectedUnit}) AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit) end else if self.EngageRange then if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3())<=self.EngageRange then self:F({"Within Range and Engaging",DetectedUnit}) AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit) end else AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit) end end else self.DetectedUnits[DetectedUnit]=nil end end if#AttackTasks==0 then self:F("No targets found -> Going back to Patrolling") self:__Abort(1) self:__Route(1) self:SetDetectionActivated() else AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAP_ZONE.EngageRoute",self) EngageRoute[1].task=Controllable:TaskCombo(AttackTasks) self:SetDetectionDeactivated() end Controllable:Route(EngageRoute,0.5) end end function AI_CAP_ZONE:onafterAccomplish(Controllable,From,Event,To) self.Accomplished=true self:SetDetectionOff() end function AI_CAP_ZONE:onafterDestroy(Controllable,From,Event,To,EventData) if EventData.IniUnit then self.DetectedUnits[EventData.IniUnit]=nil end end function AI_CAP_ZONE:OnEventDead(EventData) self:F({"EventDead",EventData}) if EventData.IniDCSUnit then if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then self:__Destroy(1,EventData) end end end AI_CAS_ZONE={ ClassName="AI_CAS_ZONE", } function AI_CAS_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageZone,PatrolAltType) local self=BASE:Inherit(self,AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)) self.EngageZone=EngageZone self.Accomplished=false self:SetDetectionZone(self.EngageZone) self:AddTransition({"Patrolling","Engaging"},"Engage","Engaging") self:AddTransition("Engaging","Target","Engaging") self:AddTransition("Engaging","Fired","Engaging") self:AddTransition("*","Destroy","*") self:AddTransition("Engaging","Abort","Patrolling") self:AddTransition("Engaging","Accomplish","Patrolling") return self end function AI_CAS_ZONE:SetEngageZone(EngageZone) self:F2() if EngageZone then self.EngageZone=EngageZone else self.EngageZone=nil end end function AI_CAS_ZONE:onafterStart(Controllable,From,Event,To) self:GetParent(self).onafterStart(self,Controllable,From,Event,To) self:HandleEvent(EVENTS.Dead) self:SetDetectionDeactivated() end function AI_CAS_ZONE.EngageRoute(EngageGroup,Fsm) EngageGroup:F({"AI_CAS_ZONE.EngageRoute:",EngageGroup:GetName()}) if EngageGroup:IsAlive()then Fsm:__Engage(1,Fsm.EngageSpeed,Fsm.EngageAltitude,Fsm.EngageWeaponExpend,Fsm.EngageAttackQty,Fsm.EngageDirection) end end function AI_CAS_ZONE:onbeforeEngage(Controllable,From,Event,To) if self.Accomplished==true then return false end end function AI_CAS_ZONE:onafterTarget(Controllable,From,Event,To) 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:OptionROTEvadeFire() 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=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)" 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)=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") 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" 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].TaskInfoOrder0 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 DetectedTreatLevel0 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 DetectedTreatLevel0 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 CargoItemCount0 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 *** ')